diff --git a/game-server/app/servers/battle/handler/guildHandler.ts b/game-server/app/servers/battle/handler/guildHandler.ts index a7cb0f5cf..45cdb5301 100644 --- a/game-server/app/servers/battle/handler/guildHandler.ts +++ b/game-server/app/servers/battle/handler/guildHandler.ts @@ -1,6 +1,6 @@ import { Application, BackendSession, pinus, ChannelService } from 'pinus'; import { resResult, getRandEelm, reduceCe, getRefTime } from '../../../pubUtils/util'; -import { STATUS, GUILD_OPERATE, GUILD_AUTH, GUILD_JOB, GUILD_APPLY_TYPE, GUILD_STRUCTURE, GUILD_REC_TYPE, GUILD_STRUCTURE_NAME, MAIL_TYPE, REDIS_KEY } from '../../../consts'; +import { STATUS, GUILD_OPERATE, GUILD_AUTH, GUILD_JOB, GUILD_APPLY_TYPE, GUILD_STRUCTURE, GUILD_REC_TYPE, GUILD_STRUCTURE_NAME, MAIL_TYPE, REDIS_KEY, GUILD_DATA_NAME } from '../../../consts'; import { UserGuildModel } from '../../../db/UserGuild'; import { checkAuth, joinGuild, getGuildWithRefActive, getUserGuildWithRefActive, addActive, message, settleGuildWeekly } from '../../../services/guildService'; import { GuildModel, GuildType } from '../../../db/Guild'; @@ -15,6 +15,7 @@ import { hasStructureConsume, getStructureConsume, gameData } from '../../../pub import { GuildRecModel } from '../../../db/GuildRec'; import { sendMail } from '../../../services/mailService'; import { existsRank, initSingleRankWithServer, getRank, setRank, redisUserInfoUpdate, redisUserInfoAdd, removeFromRank, getMyRank } from '../../../services/redisService'; +import { lockData } from '../../../services/redLockService'; import { openGuildRefine } from '../../../services/guildRefineService'; import { unlockTrain } from '../../../services/guildTrainService'; @@ -448,7 +449,6 @@ export class GuildHandler { const list = result.map(cur => { let guild = cur.guild; let leader = guild.leader; - delete guild.leader; delete cur.guild; return { applyCode: cur.applyCode, ...guild, leader: leader.roleName }; }); @@ -512,7 +512,6 @@ export class GuildHandler { select.push('auth', 'activeWeekly activeUpdateTime'); } let list = await UserGuildModel.getListByGuild(code, select.join(' '), { auth: 1 }); - console.log('**', JSON.stringify(list)) let result = list.map(cur => { let role = cur.role; delete role._id; @@ -638,7 +637,7 @@ export class GuildHandler { const roleId = session.get('roleId'); const roleName = session.get('roleName'); const sid = session.get('sid'); - // const serverId = session.get('serverId'); + const serverId = session.get('serverId'); const { code, roleId: leaderRoleId } = msg; // 弹劾的权限 @@ -670,7 +669,6 @@ export class GuildHandler { const topUser = topUserGuild.role; // 交换 - // TODO redlock await UserGuildModel.updateInfo(leaderRoleId, { auth: GUILD_AUTH.MEMBER }, {}, 'auth'); // 团长撤 await UserGuildModel.updateInfo(topUserGuild.roleId, { auth: GUILD_AUTH.LEADER }, {}, 'auth'); // 最高功勋人升 let managerCntInc = topUserGuild.auth == GUILD_AUTH.MANAGER ? -1 : 0; // 管理人数 @@ -705,35 +703,44 @@ export class GuildHandler { const checkResult = await checkAuth(GUILD_OPERATE.UP_STRUCTURE, roleId, code); if (!checkResult) return resResult(STATUS.GUILD_AUTH_NOT_ENOUGH); + let res:any = await lockData(serverId, GUILD_DATA_NAME.UP_STRUCTURE, code);// 加锁 + if (!!res.err) return resResult(STATUS.REDLOCK_ERR); + const guild = await GuildModel.findByCode(code, serverId, 'lv structure'); if(!guild) { + res.releaseCallback();//解锁 return resResult(STATUS.GUILD_NOT_FOUND); } - // TODO 加写锁 const { lv, structure } = guild; const curStructure = structure.find(cur => cur.id == id); if(!curStructure) { + res.releaseCallback(); //解锁 return resResult(STATUS.GUILD_STRUCTURE_NOT_FOUND); } if(id != GUILD_STRUCTURE.ARMY_CENTER && curStructure.lv >= lv) { // 中军大帐以外建筑物 if(curStructure.lv >= lv) { + res.releaseCallback(); //解锁 return resResult(STATUS.GUILD_STRUCTURE_LV_MAX); } } if(!hasStructureConsume(id, curStructure.lv + 1)) { + res.releaseCallback(); //解锁 return resResult(STATUS.GUILD_STRUCTURE_LV_MAX); } const cost = getStructureConsume(id, curStructure.lv); const costResult = await GuildModel.costFund(code, cost); if(!costResult) { + res.releaseCallback(); //解锁 return resResult(STATUS.GUILD_FUND_NOT_ENOUGH); } const result = await GuildModel.upStructure(code, id, 'code fund structure lv'); if(!result) { + res.releaseCallback(); //解锁 return resResult(STATUS.GUILD_STRUCTURE_NOT_FOUND); } + res.releaseCallback(); //解锁 const resultStructure = result.structure.find(cur => cur.id == id); // 修改信息 diff --git a/game-server/app/services/guildService.ts b/game-server/app/services/guildService.ts index 89586b038..bde54825e 100644 --- a/game-server/app/services/guildService.ts +++ b/game-server/app/services/guildService.ts @@ -1,7 +1,7 @@ import { gameData, getGuildActiveWeekReward, getGuildActiveByIdAndType } from "../pubUtils/data"; import { GuildModel, GuildType } from "../db/Guild"; -import { resResult, shouldRefreshWeek, shouldRefresh } from "../pubUtils/util"; -import { STATUS, GUILD_OPERATE, MAIL_TYPE, GUILD_AUTH, GUILD_JOB, REDIS_KEY, } from "../consts"; +import { resResult, shouldRefresh } from "../pubUtils/util"; +import { STATUS, MAIL_TYPE, GUILD_AUTH, GUILD_JOB, REDIS_KEY, GUILD_DATA_NAME, } from "../consts"; import { RoleModel, RoleType } from "../db/Role"; import { UserGuildModel, UserGuildType } from "../db/UserGuild"; import { UserGuildApplyModel } from "../db/UserGuildApply"; @@ -13,6 +13,8 @@ import { ARMY } from "../pubUtils/dicParam"; import { sendMail } from "./mailService"; import { setRank, getMyRank, initSingleRank } from "./redisService"; import { GuildRankParam, GuildLeader } from "../pubUtils/interface"; +import { lockData, isLocked } from '../services/redLockService'; +import { ErrLogModel } from '../db/ErrLog'; /** * @description 检查该玩家是否有权限做操作 @@ -22,7 +24,7 @@ import { GuildRankParam, GuildLeader } from "../pubUtils/interface"; export async function checkAuth(func: number, roleId: string, code?: string, userGuild?: UserGuildType) { const auth = await UserGuildModel.getMyAuth(roleId, code, userGuild); const dicGuildAuth = gameData.guildAuth.get(func); - console.log(auth, dicGuildAuth) + // console.log(auth, dicGuildAuth) if(!dicGuildAuth) return false; return dicGuildAuth.includes(auth); @@ -36,44 +38,60 @@ export async function checkAuth(func: number, roleId: string, code?: string, use */ export async function joinGuild(code: string, lv: number, roleId: string, serverId: number) { - // TODO 加redlock + // 周结算锁 + let isWeeklySum = await isLocked(serverId, GUILD_DATA_NAME.WEEKLY_GUILD_SUM, code); + if(isWeeklySum) return { status: 0, resResult: resResult(STATUS.GUILD_WEEKLY_SUM) }; + + // 人数锁 + let res:any = await lockData(serverId, GUILD_DATA_NAME.JOIN_GUILD, code);// 加锁 + if (!!res.err) return { status: 0, resResult: resResult(STATUS.REDLOCK_ERR) }; const result = await RoleModel.joinGuild(roleId); if(!result) { + res.releaseCallback();//解锁 return { status: 0, resResult: resResult(STATUS.GUILD_HAS_JOIN) }; } const dicCenterBase = gameData.centerBase.get(lv); if(!dicCenterBase) { + res.releaseCallback();//解锁 return { status: 0, resResult: resResult(STATUS.DIC_DATA_NOT_FOUND) }; } const maxMemberCnt = dicCenterBase.peopleNum; const guild = await GuildModel.addGuild(code, roleId, maxMemberCnt, serverId, result.ce); if(!guild) { + res.releaseCallback();//解锁 return { status: 0, resResult: resResult(STATUS.GUILD_MEMBER_MAX) }; } const role = await RoleModel.findByRoleId(roleId); const userGuild = await UserGuildModel.createUserGuild(guild.code, role, false); if(!userGuild) { + res.releaseCallback();//解锁 return { status: 0, resResult: resResult(STATUS.GUILD_CREATE_ERROR) }; } await UserGuildApplyModel.deleteApply(roleId); // 删除玩家所有对其他公会的申请 + res.releaseCallback();//解锁 return { status: 1, guild, userGuild, roleName: role.roleName, memberCnt: guild.memberCnt } } /** - * 刷新军团活跃 + * 刷新军团日活跃 * @param guildCode 军团唯一code * @param serverId 区 */ export async function getGuildWithRefActive(guildCode: string, serverId: number) { - // TODO 加写锁 + let res:any = await lockData(serverId, GUILD_DATA_NAME.REFRESH_ACTIVE, guildCode);// 加锁 + if (!!res.err) return false; + let guild = await GuildModel.findByCode(guildCode, serverId, '+refTimeDaily'); - if(!guild) return false; + if(!guild) { + res.releaseCallback();//解锁 + return false; + } console.log(JSON.stringify(guild)); const now = new Date(); @@ -84,6 +102,7 @@ export async function getGuildWithRefActive(guildCode: string, serverId: number) activeDaily = 0; refTimeDaily = now; guild = await GuildModel.updateInfo(guildCode, { activeDaily, refTimeDaily }, {}); } + res.releaseCallback();//解锁 return guild; } @@ -113,6 +132,10 @@ export async function addActive(roleId: string, serverId: number, id: number, ty let guild = await getGuildWithRefActive(guildCode, serverId); if(!guild) return { status: 0, resResult: resResult(STATUS.GUILD_NOT_FOUND) }; + // 周结算锁 + let isWeeklySum = await isLocked(serverId, GUILD_DATA_NAME.WEEKLY_GUILD_SUM, guildCode); + if(isWeeklySum) return { status: 0, resResult: resResult(STATUS.GUILD_WEEKLY_SUM) }; + let {activeRecord} = userGuild; if(id != 0) { // 用于debug,传0时直接增加活跃 let curActiveRecord = activeRecord.find(cur => cur.id == id); @@ -168,7 +191,12 @@ export async function settleGuildWeekly() { console.log('————— settleGuildWeekly —————'); const guildList = await GuildModel.findAllGuild('code activeWeekly memberCnt serverId'); + // 周结算时,1. 不能变动memberCnt 2.玩家activeWeekly不能变动 3.公会activeWeekly不能变动 for(let { code, memberCnt, serverId } of guildList) { + let res:any = await lockData(serverId, GUILD_DATA_NAME.WEEKLY_GUILD_SUM, code);//加锁 + if (!!res.err) { + await ErrLogModel.create(`settle guild lock data error: ${res.err}`) + } const userGuildList = await UserGuildModel.getListByGuild(code, 'roleId auth activeWeekly', { activeWeekly: -1, activeUpdateTime: 1 }); let otherMemberCnt = 0; // 除了大将军以外从活跃高到底成员人数,用户计算活跃排名百分比 @@ -189,7 +217,7 @@ export async function settleGuildWeekly() { if(otherMemberCnt <= rankCnt) break; } } - await UserGuildModel.updateInfo(roleId, { job,/* activeWeekly: 0*/ }, {}); + await UserGuildModel.updateInfo(roleId, { job, activeWeekly: 0 }, {}); if(activeWeekly > ARMY.ARMY_WEEKHONOUR_LIMIT) { // 低于一定不发奖励 members.set(roleId, job); @@ -211,6 +239,7 @@ export async function settleGuildWeekly() { } await GuildModel.updateInfo(code, { activeWeekly: 0 }, {}); + res.releaseCallback();//解锁 } await initSingleRank(REDIS_KEY.GUILD_ACTIVE_RANK); await SystemConfigModel.updateSystemConfig({settleGuildWeeklyTime: nowSeconds()}); // 记录一下 diff --git a/game-server/app/services/redLockService.ts b/game-server/app/services/redLockService.ts index e16287fe1..d84c715e0 100644 --- a/game-server/app/services/redLockService.ts +++ b/game-server/app/services/redLockService.ts @@ -1,5 +1,5 @@ import { localrun } from "pinus/lib/master/starter"; -import { setLock, releaseLock } from './redlockCacheService'; +import { setLock, releaseLock, getLock } from './redlockCacheService'; var Redlock = require('redlock'); var _redlockCache; @@ -62,4 +62,10 @@ function lock(lockKey: string) { } export async function releaseCallback(lockKey: string) { releaseLock(lockKey); +} + +export async function isLocked(serverId: number, dataName: string, id: string) { + let key = 'serverId_'+serverId+'_'+dataName+'_'+id; + let lockKey = 'locks:' + key; + return getLock(lockKey); } \ No newline at end of file diff --git a/game-server/app/services/redisService.ts b/game-server/app/services/redisService.ts index 2c200785d..e321f03e5 100644 --- a/game-server/app/services/redisService.ts +++ b/game-server/app/services/redisService.ts @@ -89,7 +89,6 @@ async function setRankRedisFromDb(type: string, serverId: number) { for(let { code, icon, name, lv, leader, activeWeekly = 0, activeUpdateTime = 0 } of ranks) { let _leader = leader; let { roleName, title, sHid, headHid, lv: leaderLv } = _leader; - console.log(activeWeekly, activeUpdateTime * 1000) await client.zaddAsync(getKeyName(REDIS_KEY.GUILD_ACTIVE_RANK, serverId), encodeScoreWithTime(activeWeekly, activeUpdateTime * 1000 ), code); let rankParam = new GuildRankParam(icon, name, lv, { roleName, title, sHid, headHid, lv: leaderLv }); await redisUserInfoAdd(REDIS_KEY.GUILD_INFO, code, rankParam); diff --git a/game-server/app/services/redlockCacheService.ts b/game-server/app/services/redlockCacheService.ts index bbbc0e8d7..44d87de29 100644 --- a/game-server/app/services/redlockCacheService.ts +++ b/game-server/app/services/redlockCacheService.ts @@ -34,3 +34,8 @@ export function releaseLock(lockKey: string) { export function setLock(lockKey: string, lock: any){ userCacheMap.set(lockKey, {lock, time: nowSeconds()}) }; + +export function getLock(lockKey: string) { + var userCache = userCacheMap.get(lockKey); + return !!userCache && !!userCache.lock +} \ No newline at end of file diff --git a/shared/consts/constModules/guildConst.ts b/shared/consts/constModules/guildConst.ts index eefe7f98e..ee5c0ae81 100644 --- a/shared/consts/constModules/guildConst.ts +++ b/shared/consts/constModules/guildConst.ts @@ -124,6 +124,10 @@ export enum GUILD_DATA_NAME { GUILD = 'Guild', GUILD_REFINE = 'GuildRefine', GUILD_ASSIST_REFINE = 'GuildAssistRefine', + UP_STRUCTURE = 'GuildStructure', + JOIN_GUILD = 'JoinGuild', + REFRESH_ACTIVE = 'GuildRefActive', + WEEKLY_GUILD_SUM = 'WeeklyGuildSum', // 每周结算活跃和奖励 } export const GUILD_REPORT_NUM = 40; diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index 3b1a7fbe1..2c85934e2 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -147,6 +147,7 @@ export const STATUS = { GUILD_ACTIVE_BOX_NOT_FOUND: { code: 20918, simStr: '未找到该活跃宝箱' }, GUILD_ACTIVE_POINT_NOT_REACH: { code: 20919, simStr: '军团活跃值不足' }, GUILD_ACTIVE_BOX_HAS_RECEIVED: { code: 20920, simStr: '该活跃宝箱已领取' }, + GUILD_WEEKLY_SUM: { code: 20921, simStr: '正在周结算中' }, GUILD_SCRIPT_IS_OPENED_TODAY: { code: 20950, simStr: '今日演武场已开启' }, GUILD_SCRIPT_NOT_OPENED: { code: 20951, simStr: '演武场未开启' }, diff --git a/shared/db/ErrLog.ts b/shared/db/ErrLog.ts new file mode 100644 index 000000000..ceb8c6454 --- /dev/null +++ b/shared/db/ErrLog.ts @@ -0,0 +1,26 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; +import { genCode } from '../pubUtils/util' + +/** + * 自增 ID +*/ +@index({ name: 1 }) +export default class ErrLog extends BaseModel { + + @prop({ required: true }) + code: string; + + @prop({ required: true, default: "" }) + desc: string; + + public static async create(desc: string) { + let code = genCode(12); + await ErrLogModel.insertMany({ code, desc }); + } + +} + +export const ErrLogModel = getModelForClass(ErrLog); + +export interface ErrLogType extends Pick, keyof ErrLog>{}; \ No newline at end of file diff --git a/shared/db/Guild.ts b/shared/db/Guild.ts index 8de08bb94..005377c4b 100644 --- a/shared/db/Guild.ts +++ b/shared/db/Guild.ts @@ -3,7 +3,7 @@ import { index, getModelForClass, prop, DocumentType, Ref } from '@typegoose/typ import Role, { RoleType } from './Role'; import { genCode } from '../pubUtils/util'; import { GUILD_STRUCTURE, GUILD_STATUS, GUILD_PER_PAGE } from '../consts'; -import { getCurWeekTime } from '../pubUtils/timeUtil'; +import { getCurWeekTime, nowSeconds } from '../pubUtils/timeUtil'; class Structure { @prop({ required: true }) @@ -60,7 +60,7 @@ export default class Guild extends BaseModel { @prop({ required: true, default: 0 }) activeWeekly: number; - @prop({ required: true, default: Date.now() }) + @prop({ required: true, default: nowSeconds() }) activeUpdateTime: number; @prop({ required: true, default: new Date(), select: false }) @@ -93,6 +93,7 @@ export default class Guild extends BaseModel { delete update._id; const code = genCode(6); const result: GuildType = await GuildModel.findOneAndUpdate({ code }, update, { upsert: true, new: true }) + .select({ _id: 0, __v: 0, createdAt: 0, updatedAt: 0 }) .populate('leader', { roleId: 1, roleName: 1, sHid: 1, headHid: 1, lv: 1, loginTime: 1, ce: 1, title: 1, _id: 0 }, 'Role') .lean(); diff --git a/shared/db/UserGuildApply.ts b/shared/db/UserGuildApply.ts index af4993611..ce53b4e16 100644 --- a/shared/db/UserGuildApply.ts +++ b/shared/db/UserGuildApply.ts @@ -94,7 +94,7 @@ export default class UserGuildApply extends BaseModel { const lastApply = await this.findByCode(lastApplyCode); condition["createdAt"] = { $lt: lastApply.createdAt }; } - const list: UserGuildApplyType[] = await UserGuildApplyModel.find(condition) + const list: UserGuildApplyType[] = await UserGuildApplyModel.find(condition, {_id: 0}) .sort({ createdAt: -1 }) .select('applyCode guild') .populate({