diff --git a/game-server/app.ts b/game-server/app.ts index c86bd88d2..b94c5ff33 100644 --- a/game-server/app.ts +++ b/game-server/app.ts @@ -15,6 +15,8 @@ import { preload } from './preload'; var checkEventFilter = require('./app/servers/battle/filter/checkEventFilter'); import { connectRedis } from './config/redis'; import * as timeTaskService from './app/services/timeTaskService'; +import * as redlockCacheService from './app/services/redlockCacheService'; +import * as redLockService from './app/services/redLockService'; // TODO 需要整理。 import _pinus = require('pinus'); @@ -60,7 +62,8 @@ app.configure(function() { const redisClient = connectRedis(app.get('database').redis); app.set('redis', redisClient); - + redLockService.initRedlock(redisClient); + redlockCacheService.init(); // TODO 重启 1 次只需要初始化 1 次,判断方法可以优化 if (app.serverId === 'master-server-1') { redisService.initAllRank(); diff --git a/game-server/app/servers/battle/handler/guildBossHandler.ts b/game-server/app/servers/battle/handler/guildBossHandler.ts new file mode 100644 index 000000000..2078e72c6 --- /dev/null +++ b/game-server/app/servers/battle/handler/guildBossHandler.ts @@ -0,0 +1,118 @@ +import { Application, BackendSession, pinus } from 'pinus'; +import { resResult, genCode } from '../../../pubUtils/util'; +import { STATUS } from '../../../consts'; +import { BossScriptModel } from '../../../db/BossScript'; +import { BattleRecordModel } from '../../../db/BattleRecord'; +import { nowSeconds, getTodayZeroPoint } from '../../../pubUtils/timeUtil'; +import { getBossScriptInfo, bossResult, checkMemberExists, pushBossHpMessage, getBossScriptWhenEnd, addBossScript } from '../../../services/guildBossService'; +import { findWhere } from 'underscore' +import { GUILD_DATA_NAME } from '../../../consts/constModules/guildConst'; +export default function (app: Application) { + return new GuildHandler(app); +} + +export class GuildHandler { + constructor(private app: Application) { + } + + // 获得boss关卡 + async getBossScript(msg: { code: string }, session: BackendSession) { + const { code } = msg; + //TODO校验 + const roleId = session.get('roleId'); + let bossScript = await BossScriptModel.findBossScript(code); + if (!bossScript) { + return resResult(STATUS.SUCCESS, {type: 1});//1:等待团长开启,2:今日已开启,且boss通关,3:开启中 + } + let result = await getBossScriptInfo(bossScript, roleId); + + return resResult(STATUS.SUCCESS, result); + } + + //开启演武场 + async openBossScript(msg: { code: string }, session: BackendSession) { + const { code } = msg; + const roleId = session.get('roleId'); + //TODO检查权限 + let bossScript = await BossScriptModel.findBossScript(code); + if (!!bossScript && ( bossScript.bossHp > 0 || bossScript.time >= getTodayZeroPoint() )) { + return resResult(STATUS.GUILD_SCRIPT_IS_OPENED_TODAY); + } + //TODO随机地图 + let bossHp = 1000; + let warId = 1; + await BossScriptModel.openBossScript(code, bossHp, warId); + let result = {warId, ranks: [], myRank: {}, bossHp, type: 3}; + return resResult(STATUS.SUCCESS, result); + } + + async bossScriptStart(msg: { code: string }, session: BackendSession) { + const { code } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + //TODO校验 + let bossScript = await BossScriptModel.findBossScript(code); + if (!bossScript) + return resResult(STATUS.GUILD_SCRIPT_NOT_OPENED); + if (bossScript.bossHp <= 0) + return resResult(STATUS.GUILD_SCRIPT_IS_COMPLETE); + let myRank = findWhere(bossScript.ranks, {roleId}); + if (!!myRank && myRank.time > getTodayZeroPoint()) + return resResult(STATUS.GUILD_SCRIPT_IS_BATTLED); + let { warId, ranks } = bossScript; + const battleCode = genCode(8); // 关卡唯一值 + //TODO查看地图字典 + let warInfo; + await BattleRecordModel.updateBattleRecordByCode(battleCode, { + $set: { + roleId, roleName, battleId: warId, + status: 0, + warName: warInfo.gk_name, + warType: warInfo.warType, + record: { heroes:[],recordNum: bossScript.num, bossHp: bossScript.bossHp}, + } + }, true); + const serverId = session.get('serverId'); + await addBossScript(code, serverId, roleId); + if (!findWhere(ranks, {roleId})) { + await BossScriptModel.pushRanks(code, {roleId, score: 0, time:nowSeconds()}); + } + return resResult(STATUS.SUCCESS, { battleCode }); + } + + async action (msg: { code: string, damage: number, battleCode: string }, session: BackendSession ) { + const { code, battleCode, damage } = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + let flag = await checkMemberExists(code, serverId, roleId, battleCode); + if (!flag) { + return; + } + //记录伤害 + let bossScript = await BossScriptModel.updateBossHp(code, damage, roleId); + if (!bossScript) {//进入结算 + let flag = await bossResult(code, serverId, GUILD_DATA_NAME.BOSS_SCRIPT, roleId, damage); + if (!flag) { + return resResult(STATUS.WRONG_PARMS); + } + } else { + pushBossHpMessage(code, serverId, bossScript.bossHp); + } + return resResult(STATUS.SUCCESS, { bossHp: bossScript.bossHp>0?bossScript.bossHp:0 }); + } + + async bossScriptEnd(msg: { code: string, battleCode: string }, session: BackendSession) { + const { code, battleCode } = msg; + const roleId = session.get('roleId'); + const battleRecord = await BattleRecordModel.getBattleRecordByCode(battleCode, true); + if(!battleRecord || battleRecord.status != 0 || roleId != battleRecord.roleId) { + return resResult(STATUS.WRONG_PARMS); + } + await BattleRecordModel.updateBattleRecordByCode(battleCode, { + $set: { status: 1 }//战斗结束统一设置成1 + }, true); + let bossScript = await BossScriptModel.findBossScript(code); + let result = await getBossScriptWhenEnd(bossScript, roleId, battleRecord.record.recordNum); + return resResult(STATUS.SUCCESS, result); + } +} \ No newline at end of file diff --git a/game-server/app/servers/battle/handler/guildTrainHandler.ts b/game-server/app/servers/battle/handler/guildTrainHandler.ts new file mode 100644 index 000000000..f44a39bed --- /dev/null +++ b/game-server/app/servers/battle/handler/guildTrainHandler.ts @@ -0,0 +1,164 @@ +import { Application, BackendSession, pinus } from 'pinus'; +import { resResult, genCode } from '../../../pubUtils/util'; +import { STATUS, GUILD_OPERATE, GUILD_AUTH, GUILD_JOB } from '../../../consts'; +import { GuildTrainModel } from '../../../db/GuildTrain'; +import { BattleRecordModel } from '../../../db/BattleRecord'; +import { nowSeconds, getTodayZeroPoint } from '../../../pubUtils/timeUtil'; +import { getUserGuild, getGuildTrainInfo } from '../../../services/guildTrainService'; +import { findIndex, findWhere, indexBy } from 'underscore' +import { lockData } from '../../../services/redlockService'; +import { GUILD_DATA_NAME } from '../../../consts/constModules/guildConst'; +import { UserGuildModel } from '../../../db/UserGuild'; +export default function (app: Application) { + return new GuildTrainHandler(app); +} + +export class GuildTrainHandler { + constructor(private app: Application) { + + } + + async getTrainScript(msg: { code: string }, session: BackendSession) { + const { code } = msg; + const roleId = session.get('roleId'); + let guildTrains = await GuildTrainModel.findGuildTrain(code); + let userGuild = await getUserGuild(roleId, code); + if (!userGuild) + return resResult(STATUS.WRONG_PARMS); + if (!guildTrains) + guildTrains = []; + let { trainCount, trainRewards } = userGuild; + let result = getGuildTrainInfo(guildTrains, roleId, trainCount, trainRewards); + return resResult(STATUS.SUCCESS, result); + } + + async trainScriptStart(msg: { code: string, trainId: number, hid: number }, session: BackendSession) { + const { code, trainId, hid } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + let userGuild = await getUserGuild(roleId, code); + if (!userGuild) + return resResult(STATUS.WRONG_PARMS); + if (userGuild.trainCount > 2) //TODO检查次数 + return resResult(STATUS.GUILD_TRAIN_BATTLE_COUNT_NOT_ENOUGH); + let guildTrain = await GuildTrainModel.findTrainByTrainIdNotLock(code, trainId); + if (!guildTrain) + return resResult(STATUS.GUILD_TRAIN_SCRIPT_NOT_OPENED); + let trainScript = findWhere(guildTrain.trainScripts, { hid }); + if (!trainScript) + return resResult(STATUS.WRONG_PARMS); + //TODO 判断是否压制成功 + if (trainScript.progress >= 1000) { + return resResult(STATUS.GUILD_TRAIN_IS_COMPLETE); + } + const battleCode = genCode(8); // 关卡唯一值 + //TODO查看地图字典 + let warInfo; + let warId; + await BattleRecordModel.updateBattleRecordByCode(battleCode, { + $set: { + roleId, roleName, battleId: warId, + status: 0, + warName: warInfo.gk_name, + warType: warInfo.warType, + record: { heroes:[], trainId, hid}, + } + }, true); + return resResult(STATUS.SUCCESS, { battleCode }); + } + + async trainScriptEnd(msg: { code: string, battleCode: string, isSuccess: boolean}, session: BackendSession) { + const { code, battleCode, isSuccess} = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + const battleRecord = await BattleRecordModel.getBattleRecordByCode(battleCode, true); + if(!battleRecord || battleRecord.status != 0 || roleId != battleRecord.roleId) { + return resResult(STATUS.WRONG_PARMS); + } + let userGuild = await getUserGuild(roleId, code); + if (!userGuild) + return resResult(STATUS.WRONG_PARMS); + let time = battleRecord.createdAt.getTime()/1000; + if (time > getTodayZeroPoint()) { + if (userGuild.trainTime > getTodayZeroPoint()) { + userGuild = await UserGuildModel.updateInfo(roleId, {trainCount: userGuild.trainCount + 1}); + } else { + userGuild = await UserGuildModel.updateInfo(roleId, {trainCount: 1, trainTime: nowSeconds()}); + } + } + await BattleRecordModel.updateBattleRecordByCode(battleCode, { + $set: { status: isSuccess?1:2 } + }, true); + + let trainId = battleRecord.record.trainId; + let hid = battleRecord.record.hid; + let res:any = await lockData(serverId, GUILD_DATA_NAME.TRAIN, code + '_' + trainId);//加锁 + if (!!res.err) + return true; + let guildTrain = await GuildTrainModel.findTrainByTrainIdNotLock(code, trainId); + if (!guildTrain) { + res.releaseCallback();//解锁 + return resResult(STATUS.GUILD_TRAIN_SCRIPT_NOT_OPENED); + } + let trainScript = findWhere(guildTrain.trainScripts,{hid}); + if (!trainScript) + return resResult(STATUS.WRONG_PARMS); + let addScore; + //结算奖励 TODO + + + //是否压制 + let { isComplete, reports, ranks } = guildTrain; + let index = findIndex(ranks, {roleId}); + if (index !== -1) { + ranks[index].score += addScore; + } + let report = {roleId, trainId, hid, score: addScore, time: nowSeconds(), isSuccessed: isSuccess, type: 1};//1表示普通战报, 2表示系统战报即:被成功压制 + if (trainScript.progress < 1000 ) { + if (trainScript.progress + addScore >= 1000) { + reports.push(...[{isSuccessed: true, type: 2, time:nowSeconds(), score: addScore, roleId, trainId, hid},report]); + await GuildTrainModel.updateGuildTrain(code, trainId, {reports, ranks}); + let needLockNext = false; + if (!isComplete) { + isComplete = true; + guildTrain.trainScripts.forEach(({hid: otherHid, progress})=>{ + if (hid != otherHid && progress == 1000) { + isComplete = false; + } + }); + if (isComplete) {//解锁下一关 + needLockNext = true; + } + } + let progress = 1000; + GuildTrainModel.updateGuildTrainProgress(code, trainId, hid, progress, ranks, reports, isComplete); + if (needLockNext) { + + } + res.releaseCallback();//解锁 + } else { + let progress = trainScript.progress + addScore; + GuildTrainModel.updateGuildTrainProgress(code, trainId, hid, progress, ranks, reports, isComplete); + res.releaseCallback();//解锁 + } + } else { + reports.push(report); + GuildTrainModel.updateGuildTrain(code, trainId, {reports, ranks}); + res.releaseCallback();//解锁 + } + let guildTrains = await GuildTrainModel.findGuildTrain(code); + if (!guildTrains) + guildTrains = []; + let { trainCount, trainRewards } = userGuild; + let result = getGuildTrainInfo(guildTrains, roleId, trainCount, trainRewards); + return resResult(STATUS.SUCCESS, result); + } + + async getTrainScriptBox(msg: { code: string }, session: BackendSession) { + + } + + async getTrainLvUpRewards(msg: { trainId: number }, session: BackendSession) { + + } +} \ No newline at end of file diff --git a/game-server/app/services/guildBossService.ts b/game-server/app/services/guildBossService.ts new file mode 100644 index 000000000..1a0db1437 --- /dev/null +++ b/game-server/app/services/guildBossService.ts @@ -0,0 +1,177 @@ +import { BossScriptType, BossScriptModel } from '../db/BossScript'; +import { getTodayZeroPoint, nowSeconds } from '../pubUtils/timeUtil'; +import { lockData } from '../services/redLockService'; +import { findIndex, indexBy, values } from 'underscore'; +import { MailModel, MailType } from '../db/Mail'; +import { getRedis, sismemberAsync, smembersAsync, saddAsync, delAsync, sremAsync } from '../services/redisService'; +import { pinus } from 'pinus'; +import { STATUS } from '../consts/statusCode'; +import { deepCopy, resResult } from '../pubUtils/util'; +import { BattleRecordModel } from '../db/BattleRecord'; +/** + * + * @param bossScript + * @param roleId + */ +export async function getBossScriptInfo(bossScript: BossScriptType, roleId:string) { + let { warId, ranks, bossHp, winWarId, winTime, roleIdRecords, guildCode } = bossScript; + ranks.sort(function(a, b) { + return b.score - a.score + a.time - b.time; + }); + let result:any = {}; + //1:等待团长开启,2:今日已开启,且boss通关,3:开启中 + if ( bossScript.bossHp <= 0 ) { + if (!!winWarId && winTime > getTodayZeroPoint() && roleIdRecords.indexOf(roleId) == -1) { + result = { winWarId }; + await BossScriptModel.recordRoleIdWhenCheck(guildCode, roleId); + } + if (bossScript.time < getTodayZeroPoint()) { + result.type = 1; + return result; + } else { + result.type = 2; + return result; + } + } + let isBattled = false; + let myRank = {}; + + ranks.forEach(({roleId: battleRoleId, score, time}, index) => { + if (roleId == battleRoleId) { + myRank = {roleId, score, rankLv: index + 1}; + if (time >= getTodayZeroPoint()) + isBattled = true; + } + return {roleId, score, rankLv: index + 1}; + }); + return {warId, ranks, myRank, bossHp, type: 3, isBattled}; +} +/** + * 战斗结束返回 + * @param bossScript + * @param roleId + * @param battleNum + */ +export async function getBossScriptWhenEnd(bossScript: BossScriptType, roleId:string, battleNum:number) { + let { warId, ranks, bossHp, winWarId, guildCode, recordRanks, winNum } = bossScript; + let pushRanks; + let result:any = {}; + if (battleNum == winNum) { + pushRanks = deepCopy(recordRanks); + result = {warId: winWarId, bossHp: 0, type: 3}; + await BossScriptModel.recordRoleIdWhenCheck(guildCode, roleId); + } else { + pushRanks = deepCopy(ranks); + result = {warId, bossHp, type: 3}; + } + pushRanks.sort(function(a, b) { + return b.score - a.score + a.time - b.time; + }); + let isBattled = false; + let myRank = {}; + pushRanks.forEach(({roleId: battleRoleId, score, time}, index) => { + if (roleId == battleRoleId) { + myRank = {roleId, score, rankLv: index + 1}; + if (time >= getTodayZeroPoint()) + isBattled = true; + } + return {roleId, score, rankLv: index + 1}; + }); + result.myRank = myRank; + result.ranks = pushRanks; + return result; +} + +/** + * 结算战斗 + * @param code + * @param serverId + * @param dataName + * @param damage + * @param roleId + */ +export async function bossResult(code: string, serverId: string, dataName: string, roleId: string, damage: number) { + let res:any = await lockData(serverId, dataName, code);//加锁 + if (!!res.err) + return true; + let {winSettled, ranks, num, warId, bossHp} = await BossScriptModel.findBossScript(code);//锁定关卡信息 + if (winSettled) { + res.releaseCallback();//解锁 + await BossScriptModel.recordRoleIdWhenCheck(code, roleId); + return true; + } + let index = findIndex(ranks, {roleId}); + if (index == -1) { + res.releaseCallback();//解锁 + return false; + } + ranks[index].score += bossHp; + if (bossHp > damage) { + res.releaseCallback();//解锁 + return true; + } + let { recordRanks } = await BossScriptModel.updateBossScript(code, { bossHp: 0, winSettled: true, ranks, recordRanks: ranks, winNum: num, winWarId: warId, roleIdRecords:[roleId]}, ); + await pushBossHpMessage(code, serverId, 0, true); + res.releaseCallback();//数据修改解锁 + recordRanks.sort(function(a, b) { + return b.score - a.score + a.time - b.time; + }); + let mails = new Array(); + let pushMessage = []; + recordRanks.forEach(async function({roleId, score}, index){ + let rankLv = index + 1; + const doc = new MailModel(); + let goods; + const mail = Object.assign(doc.toJSON(), {roleId, goods, sendName: '系统', mailId: 1, sendTime: nowSeconds(), content:'恭喜玩家获得演武台第' + rankLv +'名 奖励如下:'}); + mails.push(mail); + let key = 'login_roleId_' + roleId; + let sid = await getRedis(key); + if (!!sid) { + pushMessage.push({route: 'onMailsAdd', data:[mail], uids: [{uid:roleId, sid}]}); + } + }); + await MailModel.addMails(mails); + pushMessage.forEach(({route, data, uids })=>{ + pinus.app.channelService.pushMessageByUids(route, resResult(STATUS.SUCCESS, {mails:data}), uids); + }); + return true; +} + +export async function addBossScript(code: string, serverId:string, roleId: string) { + let loginKey = 'login_roleId_' + roleId; + let sid = await getRedis(loginKey); + let key = 'serverId_' + serverId + 'guildCode_' + code; + let value = roleId+ '|' + sid; + saddAsync(key, [value]); +} + +export async function pushBossHpMessage(code: string, serverId:string, bossHp:number, isDelKey?: boolean ) { + let key = 'serverId_' + serverId + 'guildCode_' + code; + let members = await smembersAsync(key); + members.forEach(member=>{ + let arr = member.split('|'); + let uid = arr[0]; + let sid = arr[1]; + pinus.app.channelService.pushMessageByUids('onBossHpUpdate', resResult(STATUS.SUCCESS, {bossHp}), [{uid, sid}]); + }); + if (isDelKey) { + delAsync(key); + } +} + +export async function checkMemberExists(code: string, serverId:string, roleId: string, battleCode:string ) { + let loginKey = 'login_roleId_' + roleId; + let sid = await getRedis(loginKey); + let key = 'serverId_' + serverId + 'guildCode_' + code ; + let value = roleId+ '|' + sid; + let flag = await sismemberAsync(key, value); + if (!flag) { + const battleRecord = await BattleRecordModel.getBattleRecordByCode(battleCode, true); + if(!battleRecord || battleRecord.status != 0 || roleId != battleRecord.roleId) { + return false; + } + addBossScript(code, serverId, roleId); + flag = true; + } + return flag; +} \ No newline at end of file diff --git a/game-server/app/services/guildTrainService.ts b/game-server/app/services/guildTrainService.ts new file mode 100644 index 000000000..5813fba6b --- /dev/null +++ b/game-server/app/services/guildTrainService.ts @@ -0,0 +1,64 @@ +import { UserGuildModel } from '../db/UserGuild'; +import { getJobInfoById } from '../pubUtils/gamedata'; +import { getTodayZeroPoint, nowSeconds } from '../pubUtils/timeUtil'; +import { GUILD_REPORT_NUM } from '../consts/constModules/guildConst'; +import { GuildTrainType, GuildTrainModel } from '../db/GuildTrain'; +import { findWhere } from 'underscore'; +export async function getUserGuild(roleId: string, code: string) { + let userGuild = await UserGuildModel.getMyGuild(roleId,'trainCount trainTime trainRewards'); + if (!userGuild||userGuild.guildCode != code) + return; + let { trainCount, trainTime} = userGuild; + if (trainTime < getTodayZeroPoint()) { + trainCount = 0; + } + userGuild = await UserGuildModel.updateInfo(roleId, {trainCount, trainTime: nowSeconds()}); + return userGuild; +} + +export async function recordUserGuild(roleId: string, code: string) { + let userGuild = await UserGuildModel.getMyGuild(roleId,'trainCount trainTime trainRewards'); + if (!userGuild||userGuild.guildCode != code) + return; + let { trainCount, trainTime} = userGuild; + if (trainTime < getTodayZeroPoint()) { + trainCount = 0; + } + userGuild = await UserGuildModel.updateInfo(roleId, {trainCount, trainTime: nowSeconds()}); + return userGuild; +} + + +export function getGuildTrainInfo (guildTrains: Array, roleId: string, trainCount:number, trainRewards: Array) { + guildTrains.forEach(({trainId, isComplete, trainScripts, ranks, reports})=>{ + ranks.sort(function(a, b) { + return b.score - a.score; + }); + let myRank = {}; + ranks.forEach(({roleId: rankRoleId, score}, index)=>{ + if (roleId == rankRoleId) + myRank = {roleId: rankRoleId, score, rankLv: index+1}; + return {roleId: rankRoleId, score, rankLv: index+1}; + }); + let guildTrain = findWhere(guildTrains, { trainId: trainId - 1}); + if (!!guildTrain) { + let lenNum = guildTrain.reports.length; + if (lenNum < GUILD_REPORT_NUM) + reports = [...guildTrain.reports, ...reports] + else { + let trainReports = guildTrain.reports.splice(lenNum - GUILD_REPORT_NUM - 1, GUILD_REPORT_NUM); + reports = [...trainReports, ...reports] + } + } + return {trainId, isComplete, trainScripts, reports, myRank, ranks}; + }); + return { list: guildTrains, trainCount, trainRewards}; +} + +export async function lockTrain(code: string, trainId: number) { + let guildTrain = await GuildTrainModel.findTrainByTrainIdNotLock(code, trainId); + if (!!guildTrain) { + return; + } + +} \ No newline at end of file diff --git a/game-server/app/services/redLockService.ts b/game-server/app/services/redLockService.ts index 7dd35ead6..28c587a2e 100644 --- a/game-server/app/services/redLockService.ts +++ b/game-server/app/services/redLockService.ts @@ -1,5 +1,11 @@ +import { localrun } from "pinus/lib/master/starter"; +import { setLock, releaseLock } from './redlockCacheService'; var Redlock = require('redlock'); -var RedisService = module.exports; +var _redlockCache; + +export function initRedlock (redisClient) { + _redlockCache = new RedlockService(redisClient); +} export class RedlockService { _redisClient: any; @@ -36,8 +42,22 @@ export class RedlockService { console.error('A redis error has occurred:', err); }); } + +} - getWithModify() { - - } +export async function lockData(serverId: string, dataName: string, id: string ) { + let key = 'serverId_'+serverId+'_'+dataName+'_'+id; + let lockKey = 'locks:' + key; + console.log(' lockKey = '+ lockKey); + _redlockCache.redlock.lock(lockKey, _redlockCache.ttl).then(function(lock) { + setLock(lockKey, lock); + return {err: null, releaseCallback: releaseCallback.bind(null, lockKey)}; + }).catch(function(err) { + console.error(err); + return { err }; + }); +} + +export async function releaseCallback(lockKey: string) { + releaseLock(lockKey); } \ No newline at end of file diff --git a/game-server/app/services/redisService.ts b/game-server/app/services/redisService.ts index 36940c465..29c17a125 100644 --- a/game-server/app/services/redisService.ts +++ b/game-server/app/services/redisService.ts @@ -9,6 +9,7 @@ import { PvpDefenseModel } from '../db/PvpDefense'; import { SystemConfigModel } from '../db/SystemConfig'; import { RankParam } from '../pubUtils/interface'; import { accessSync } from 'fs'; +import { values } from 'underscore'; /** * 在服务重新启动时,将信息存入redis */ @@ -250,4 +251,32 @@ export async function resetPvpRanks() { } } +// 排行榜是否存在 +export async function smembersAsync(key: string) { + const client: Redis.RedisClient = pinus.app.get('redis'); + const result = await client.smembersAsync(key); + return result; +} + +export async function saddAsync(key: string, values: Array) { + const client: Redis.RedisClient = pinus.app.get('redis'); + const result = await client.saddAsync(key, values); + return result; +} + +export async function sismemberAsync(key: string, value: string) { + const client: Redis.RedisClient = pinus.app.get('redis'); + const result = await client.sismemberAsync(key, value); + return result; +} + +export async function delAsync(key:string) { + const client: Redis.RedisClient = pinus.app.get('redis'); + await client.delAsync(key); +} + +export async function sremAsync(key:string, member:string) { + const client: Redis.RedisClient = pinus.app.get('redis'); + await client.sremAsync(key, member); +} /**************** 寻宝相关 end */ diff --git a/game-server/app/services/redlockCacheService.ts b/game-server/app/services/redlockCacheService.ts new file mode 100644 index 000000000..bbbc0e8d7 --- /dev/null +++ b/game-server/app/services/redlockCacheService.ts @@ -0,0 +1,36 @@ +import { fromCallback } from 'bluebird'; +import { scheduleJob } from 'node-schedule'; +import { nowSeconds } from '../pubUtils/timeUtil'; +import { RedlockService } from '../services/redLockService'; +interface UserCache { + time: number; + lock: any; +} +var userCacheMap = new Map(); + +export function init() { + scheduleJob("0/5 * * * * *", clearDirtyData, {name:'clearDirtyData'}); +} + +export function clearDirtyData() { + userCacheMap.forEach(function(userCache, key) { + if(nowSeconds() > userCache.time + 10){ + console.log('show lock =' + JSON.stringify(userCache.lock)); + userCacheMap.delete(key); + } + }) +} + +export function releaseLock(lockKey: string) { + var userCache = userCacheMap.get(lockKey); + if (!!userCache && userCache.lock){ + // unlock your resource when you are done + userCache.lock.unlock(); + } + userCacheMap.delete(lockKey); +} + + +export function setLock(lockKey: string, lock: any){ + userCacheMap.set(lockKey, {lock, time: nowSeconds()}) +}; diff --git a/game-server/config/redis.ts b/game-server/config/redis.ts index ed673cc6f..4938cad3b 100644 --- a/game-server/config/redis.ts +++ b/game-server/config/redis.ts @@ -24,7 +24,7 @@ declare module 'redis' { hgetAsync(key: string, field: string): Promise; // 获取存储在哈希表中指定字段的值。 hexistsAsync(key: string, field: string): Promise; - + // 移除并返回集合中的一个随机元素 spopAsync(key: string, count?: number): Promise; // 移除集合中一个或多个成员 @@ -48,6 +48,11 @@ declare module 'redis' { zrevrankAsync(key: string, field: string): Promise; // 命令返回有序集中,指定区间内的成员,从大到小 zrevrangeAsync(key: string, start: number, end: number): Promise; + // 显示集合成员 + smembersAsync(key: string): Promise; + // 增加集合成员 + saddAsync(key: string, values: string[]): Promise; + } export interface Multi extends Commands { execAsync(...args: any[]): Promise; diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index 8eb67eecc..83348031b 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -6,6 +6,8 @@ export const STATUS = { INTERNAL_ERR: { code: 3, simStr: '内部错误' }, CONNECTOR_ERR: { code: 4, simStr: '连接服配置错误'}, LOGIN_ERR: { code: 5, simStr: '检测到您的账号异地登录,已被迫下线'}, + REDLOCK_ERR: { code: 6, simStr: 'redlock错误' }, + // 账号相关状态 10000 - 19999 SMS_IN_60S: { code: 10001, simStr: '60秒内只能发送一次' }, SMS_CNT_LIMIT: { code: 10002, simStr: '今日短信条数已达上限' }, @@ -208,6 +210,14 @@ export const STATUS = { ROLE_SCHOOL_POSITION_UNLOCK_NOT_NEED: {code: 30605, simStr: '该位置已解锁'}, ROLE_SCROLL_REACH_MAX: {code: 30606, simStr: '已经升到可以升的最高等级'}, + //军团30800-30899 + GUILD_SCRIPT_IS_OPENED_TODAY: {code: 30800, simStr: '今日演武场已开启'}, + GUILD_SCRIPT_NOT_OPENED: {code: 30801, simStr: '演武场未开启'}, + GUILD_SCRIPT_IS_BATTLED: {code: 30802, simStr: '今日已挑战过演武场'}, + GUILD_SCRIPT_IS_COMPLETE: {code: 30803, simStr: '演武场已挑战完成'}, + GUILD_TRAIN_SCRIPT_NOT_OPENED: {code: 30910, simStr: '试炼关卡未开启'}, + GUILD_TRAIN_BATTLE_COUNT_NOT_ENOUGH: {code: 30911, simStr: '试炼挑战次数已用完'}, + GUILD_TRAIN_IS_COMPLETE: {code: 30912, simStr: '试炼关卡已经被压制'}, // 社交相关状态 40000 - 49999 // 运营模块相关状态 50000 - 59999 // GM后台相关状态 60000 - 69999 diff --git a/shared/db/BattleRecord.ts b/shared/db/BattleRecord.ts index 948c90a17..2e6885a96 100644 --- a/shared/db/BattleRecord.ts +++ b/shared/db/BattleRecord.ts @@ -8,62 +8,70 @@ import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoos @index({ battleCode: 1 }) class Record { - @prop({ required: true, type: Number }) - heroes: Array ; // 武将id - @prop({ required: false }) - oppRoleId?: string; // pvp对手 - @prop({ required: false }) - pos?: number; // pvp位置 + @prop({ required: true, type: Number }) + heroes: Array; // 武将id + @prop({ required: false }) + oppRoleId?: string; // pvp对手 + @prop({ required: false }) + pos?: number; // pvp位置 + @prop({ required: false }) + recordNum?: number; + @prop({ required: false }) + bossHp?: number; + @prop({ required: false }) + trainId?:number; + @prop({ required: false }) + hid?:number; } export default class BattleRecord extends BaseModel { - @prop({ required: true }) - roleId: string; // 角色 id - @prop({ required: true }) - roleName: string; // 角色名称 + @prop({ required: true }) + roleId: string; // 角色 id + @prop({ required: true }) + roleName: string; // 角色名称 - @prop({ required: true }) - battleCode: string; // 关卡记录唯一标识 - @prop({ required: true }) - battleId: number; // 关卡 id - @prop({ required: true }) - warName: string; // 关卡 名 - @prop({ required: true }) - warType: number; // 关卡 类型 + @prop({ required: true }) + battleCode: string; // 关卡记录唯一标识 + @prop({ required: true }) + battleId: number; // 关卡 id + @prop({ required: true }) + warName: string; // 关卡 名 + @prop({ required: true }) + warType: number; // 关卡 类型 - @prop({ required: true, default: 0 }) - status: number; // 关卡状态 0-挑战中 1-挑战成功 2-挑战失败 - @prop({ required: true, default: 0 }) - star: number; // 关卡状态 0-挑战中 1-挑战成功 2-挑战失败 - @prop({ required: true, _id: false }) - record: Record; // 使用的武将等记录 + @prop({ required: true, default: 0 }) + status: number; // 关卡状态 0-挑战中 1-挑战成功 2-挑战失败 + @prop({ required: true, default: 0 }) + star: number; // 关卡状态 0-挑战中 1-挑战成功 2-挑战失败 + @prop({ required: true, _id: false }) + record: Record; // 使用的武将等记录 - public static async getBattleRecordByCode(battleCode: string, lean = true) { - const result: BattleRecordType = await BattleRecordModel.findOne({ battleCode }).lean(lean); - return result; - } + public static async getBattleRecordByCode(battleCode: string, lean = true) { + const result: BattleRecordType = await BattleRecordModel.findOne({ battleCode }).lean(lean); + return result; + } - public static async getBattleRecordByIdAndStatus(roleId: string, battleId: number, status: number, lean = true) { - const result: BattleRecordType = await BattleRecordModel.findOne({ roleId, battleId, status }).lean(lean); - return result; - } + public static async getBattleRecordByIdAndStatus(roleId: string, battleId: number, status: number, lean = true) { + const result: BattleRecordType = await BattleRecordModel.findOne({ roleId, battleId, status }).lean(lean); + return result; + } - public static async getBattleRecordByIdAndStar(roleId: string, battleId: number, star: number, lean = true) { - const result: BattleRecordType = await BattleRecordModel.findOne({ roleId, battleId, star: {$lte: star} }).lean(lean); - return result; - } + public static async getBattleRecordByIdAndStar(roleId: string, battleId: number, star: number, lean = true) { + const result: BattleRecordType = await BattleRecordModel.findOne({ roleId, battleId, star: { $lte: star } }).lean(lean); + return result; + } - public static async updateBattleRecordByCode( battleCode: string, params: object, lean = true) { - const result: BattleRecordType = await BattleRecordModel.findOneAndUpdate({ battleCode }, params, {new: true, upsert: true}).lean(lean); - return result; - } + public static async updateBattleRecordByCode(battleCode: string, params: object, lean = true) { + const result: BattleRecordType = await BattleRecordModel.findOneAndUpdate({ battleCode }, params, { new: true, upsert: true }).lean(lean); + return result; + } - public static async deleteAccount(roleId: string) { - let result = await BattleRecordModel.deleteMany({roleId}); - return result||{}; - } + public static async deleteAccount(roleId: string) { + let result = await BattleRecordModel.deleteMany({ roleId }); + return result || {}; + } } export const BattleRecordModel = getModelForClass(BattleRecord); -export interface BattleRecordType extends Pick, keyof BattleRecord>{} \ No newline at end of file +export interface BattleRecordType extends Pick, keyof BattleRecord> { } \ No newline at end of file diff --git a/shared/db/BossScript.ts b/shared/db/BossScript.ts new file mode 100644 index 000000000..5b33df7fc --- /dev/null +++ b/shared/db/BossScript.ts @@ -0,0 +1,97 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; +import { nowSeconds } from '../pubUtils/timeUtil'; + +class Rank { + @prop({ required: true }) + roleId: string; + @prop({ required: true }) + score: number; + @prop({ required: true }) + time: number; +} + +class LastRecord { + @prop({ required: true, default: 0 }) + warId: number; + @prop({ required: true}) + roleIds: Array; +} + +@index({ guildCode: 1 }) +export default class BossScript extends BaseModel { + @prop({ required: true }) + guildCode: string; + + @prop({ required: true }) + warId: number; + + @prop({ required: true }) + bossHp: number; + + @prop({ required: true, type: Rank, default:[]}) + ranks:Array; + + @prop({ required: true }) + time: number; + + @prop({ required: true, default: 0}) + num: number; + + @prop({ required: true, default:[]}) + roleIdRecords: Array; //记录提示过胜利boss关的玩家 + + @prop({ required: true }) + winWarId: number; //记录上一次通关的关卡 + + @prop({ required: true }) + winTime: number; //记录上一次通关的时间 + + @prop({ required: true, default: 0}) + winNum: number; + + @prop({ required: true, type: Rank, default:[]}) + recordRanks:Array; + + @prop({ required: true, default: false }) + winSettled: boolean; //胜利是否结算过 + public static async findBossScript(guildCode: string, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOne({ guildCode }).lean(lean); + return bossScript; + } + + public static async openBossScript(guildCode: string, bossHp: number, warId: number, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode },{ranks:[], time: nowSeconds(), bossHp, warId, $inc: { recordNum: 1 }}, {new: true, upsert: true}).lean(lean); + return bossScript; + } + //记录玩家boss通关后首次查看boss关卡 + public static async recordRoleIdWhenCheck(guildCode: string, roleId: string, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode }, { $push:{ roleIdRecords: roleId } }).lean(lean); + return bossScript; + } + + public static async pushRecordRanks(guildCode: string, recordRank: Rank, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode }, { $push:{ recordRanks: recordRank } }).lean(lean); + return bossScript; + } + + public static async pushRanks(guildCode: string, rank: Rank, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode }, { $push:{ recordRanks: rank } }).lean(lean); + return bossScript; + } + + public static async updateBossScript(guildCode: string, update:BossScriptTypeParam, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode }, { $set:update }).lean(lean); + return bossScript; + } + + public static async updateBossHp(guildCode: string, hp: number, roleId: string, lean = true) { + const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode, 'ranks.roleId': roleId, bossHp: { $gt: 0 } }, { $inc: { bossHp: - hp ,'ranks.$.score': hp}, }).lean(lean); + return bossScript; + } +} + +export const BossScriptModel = getModelForClass(BossScript); + +export interface BossScriptType extends Pick, keyof BossScript> {}; +export type BossScriptTypeParam = Partial; // 将所有字段变成可选项 diff --git a/shared/db/Guild.ts b/shared/db/Guild.ts index 2a4e22d15..bab69a999 100644 --- a/shared/db/Guild.ts +++ b/shared/db/Guild.ts @@ -11,7 +11,7 @@ class Structure { lv: number; } -@index({ roleId: 1 }) +@index({ code: 1 }) export default class Guild extends BaseModel { @prop({ required: true }) diff --git a/shared/db/GuildTrain.ts b/shared/db/GuildTrain.ts new file mode 100644 index 000000000..41b80710b --- /dev/null +++ b/shared/db/GuildTrain.ts @@ -0,0 +1,90 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType, Ref } from '@typegoose/typegoose'; + +class TrainBox { + +} + +class TrainScript { + @prop({ required: true }) + hid: number; + @prop({ required: true }) + progress: number; + @prop({ required: true }) + time: number; + @prop({ required: true, default: [], type: TrainBox}) + trainBoxs: Array; +} + +class Rank { + @prop({ required: true }) + roleId: string; + @prop({ required: true }) + score: number; +} + +class Report { + @prop({ required: true }) + roleId: string; + @prop({ required: true }) + trainId: number;//挑战的试炼关 + @prop({ required: true }) + hid: number;//据点 + @prop({ required: true }) + score: number; + @prop({ required: true }) + time: number; + @prop({ required: true }) + isSuccessed: boolean; + @prop({ required: true }) + type: number;//1表示普通战报, 2表示系统战报即:被成功压制 +} +@index({guildCode:1, trainId:1}) +export default class GuildTrain extends BaseModel { + @prop({ required: true }) + guildCode: string; + @prop({ required: true }) + trainId: number; + @prop({ required: true }) + isComplete: boolean; + @prop({ required: true, default: [], type: TrainScript}) + trainScripts: Array; + @prop({ required: true, default: [], type: Rank }) + ranks: Array; + @prop({ required: true, default: [], type: Report }) + reports: Array; + @prop({ required: true, default: false}) + locked: boolean; + + public static async findGuildTrain(guildCode: string, locked = false, lean = true) { + const guildTrain: GuildTrainType[] = await GuildTrainModel.findOne({ guildCode, locked }).lean(lean); + return guildTrain; + } + + public static async findTrainByTrainIdNotLock(guildCode: string, trainId: number, locked = false, lean = true) { + const guildTrain: GuildTrainType = await GuildTrainModel.findOne({ guildCode, trainId, locked }).lean(lean); + return guildTrain; + } + + public static async updateGuildTrain(guildCode: string, trainId: number, update: GuildTrainTypeParam, lean = true) { + const guildTrain: GuildTrainType = await GuildTrainModel.findOneAndUpdate({ guildCode, trainId}, { $set: update }).lean(lean); + return guildTrain; + } + + public static async updateGuildTrainProgress(guildCode: string, trainId: number, hid:number, progress: number, ranks:Array, reports: Array, isComplete: boolean,lean = true) { + const guildTrain: GuildTrainType = await GuildTrainModel.findOneAndUpdate({ guildCode, trainId, 'trainScripts.hid':hid}, + { $set: {'trainScripts.$.progress': progress, ranks, reports, isComplete} }).lean(lean); + return guildTrain; + } + + public static async openGuildTrain() { + // const bossScript: BossScriptType = await BossScriptModel.findOneAndUpdate({ guildCode },{ranks:[], time: nowSeconds(), bossHp, warId, $inc: { recordNum: 1 }}, {new: true, upsert: true}).lean(lean); + } + +} + + +export const GuildTrainModel = getModelForClass(GuildTrain); + +export interface GuildTrainType extends Pick, keyof GuildTrain> { }; +export type GuildTrainTypeParam = Partial; // 将所有字段变成可选项 diff --git a/shared/db/Item.ts b/shared/db/Item.ts index 56c35d82e..245542dff 100644 --- a/shared/db/Item.ts +++ b/shared/db/Item.ts @@ -81,22 +81,7 @@ export default class Item extends BaseModel { return { hasError: false, result } } } - // public static async decreaseItems(roleId: string, items: Array<{ id: number, count: number, ratio?: number}>, lean = true) { - // const transaction = new Transaction(); - // let hasError: boolean = false, result = new Array(); - // try { - // for (let { id, count, ratio } of items) { - // if (!ratio) ratio = -1; - // await transaction.update("Item", { roleId, id, count: { $gte: count } }, { $inc: { count: ratio * count } }, { new: true}); - // } - // await transaction.run(); - // return {hasError, result}; - // } catch (error) { - // await transaction.rollback().catch(console.error); - // transaction.clean(); - // return {error}; - // } - // } + public static async deleteAccount(roleId: string) { let result = await ItemModel.deleteMany({ roleId }); diff --git a/shared/db/Mail.ts b/shared/db/Mail.ts index a99172ff1..5fd71926b 100644 --- a/shared/db/Mail.ts +++ b/shared/db/Mail.ts @@ -23,11 +23,14 @@ export default class Mail extends BaseModel { @prop({ required: true }) sendName: string; + @prop({ required: true }) + content: string; + public static async addMails( mails: Array) { await MailModel.insertMany(mails); } - public static async addMail(params:{roleId: string, goods: Array, sendName: string, mailId: number, sendTime?: number}) { + public static async addMail(params:{roleId: string, goods: Array, sendName: string, mailId: number, sendTime?: number, content?:string}) { const doc = new MailModel(); const mail = Object.assign(doc.toJSON(), params); await MailModel.create(mail); diff --git a/shared/db/UserGuild.ts b/shared/db/UserGuild.ts index ab7418b6f..b64803147 100644 --- a/shared/db/UserGuild.ts +++ b/shared/db/UserGuild.ts @@ -42,6 +42,15 @@ export default class UserGuild extends BaseModel { @prop({ required: true, default: new Date() }) refTimeWeekly: number; + @prop({ required: true, default: 0 }) + trainCount: number;//每日挑战训练场的次数 + + @prop({ required: true, default: 0 }) + trainTime: number;//上次刷新挑战训练场次数的时间 + + @prop({ required: true, default: [] }) + trainRewards: Array;//领取过的进阶等级 + public static async getMyAuth(roleId: string, guildCode?: string, userGuild?: UserGuildType) { let myGuild: UserGuildType; if(!userGuild) {