import { KeyName, KeyNameParam, RankParam, GuildRankParam, RoleRankInfo, LineupParam, myIdInter, GeneralRankParam, ValueConfig, GuildRankInfo, RoleAndGuildRankInfo } from "../domain/rank"; import { getInfoKeyByRedisKey, ROLE_SELECT, GUILD_SELECT, REDIS_KEY, HERO_SELECT, COMPOSE_FIELD_TYPE, KEY_TO_COMPOSE_FIELD, getRedisKeyByRankType, COUNTER, RANK_TYPE, PUSH_ROUTE } from "../consts"; import { redisClient, delKeys, saveLadderDefCeByData } from "./redisService"; import { RoleType, RoleModel } from "../db/Role"; import { GuildType, GuildModel } from "../db/Guild"; import { HeroModel, HeroType, HeroUpdate } from "../db/Hero"; import { PvpDefenseModel } from "../db/PvpDefense"; import { gameData } from "../pubUtils/data"; import { getSeconds, nowSeconds } from "../pubUtils/timeUtil"; import { BackendSession, pinus } from "pinus"; import { RankFirstModel, RankFirstType, RankFirstUpdateParam } from "../db/RankFirst"; import { getRandSingleEelm } from "../pubUtils/util"; import { RANK_FIRST_REWARD_STATUS } from '../consts'; import { CounterModel } from "../db/Counter"; import { ActivityTimeLimitRankModel } from "../db/ActivityTimeLimitRank"; import { sendMessageToServerWithSuc } from "./pushService"; import { LadderMatchModel } from "../db/LadderMatch"; import { completeLadderRanks } from "./ladderService"; /** * @description 排行榜相关操作 * @export * @class Rank */ export class Rank { isInit: boolean = false; // 初始排行榜 key: REDIS_KEY; // 排行榜原始key keyName: KeyName; // 拼接之后的key infoKey: REDIS_KEY; // 玩家数据key extraKeys: string[]; isUnion: boolean; // 是否使用多个zset联合计算 limit: number = 200; // 排行榜长度 unionRankLife: number = 10; valueConfig: ValueConfig[] = []; // 值的组合方案 constructor(key: REDIS_KEY, keyParam: KeyNameParam, isUnion = false, limit = 200) { this.key = key; this.keyName = new KeyName(key, keyParam); let { infoKey, extraKey } = getInfoKeyByRedisKey(key); this.infoKey = infoKey; this.extraKeys = extraKey; this.isUnion = isUnion; this.limit = limit; this.setValueConfig(key); } public setIsInit(init: boolean) { this.isInit = init; } private setValueConfig(key: REDIS_KEY) { switch (key) { case REDIS_KEY.LADDER: this.valueConfig = [ new ValueConfig(true, 'rank', 0, false, false) ]; break; case REDIS_KEY.TOP_LINEUP_RANK: case REDIS_KEY.TOP_HERO_RANK: case REDIS_KEY.SUM_CE_RANK: case REDIS_KEY.SUM_CE_SNAPSHOT: case REDIS_KEY.HERO_RANK: this.valueConfig = [ new ValueConfig(true, 'score', 0, false, false) ]; break; case REDIS_KEY.RACE_ACTIVITY: this.valueConfig = [ new ValueConfig(true, 'distance', 0, false, false), new ValueConfig(false, 'time', 6, true, true), new ValueConfig(false, 'durability', 3, false, false), ]; break; case REDIS_KEY.GATE_ACTIVITY: case REDIS_KEY.CITY_ACTIVITY: case REDIS_KEY.USER_GATE_ACTIVITY: case REDIS_KEY.USER_CITY_ACTIVITY: this.valueConfig = [ new ValueConfig(true, 'score', 0, false, false), new ValueConfig(false, 'time', 4, true, false), ]; break; case REDIS_KEY.GUILD_LV_RANK: this.valueConfig = [ new ValueConfig(true, 'lv', 0, false, false), new ValueConfig(false, 'active', 5, false, false), new ValueConfig(false, 'time', 6, true, true) ]; break; default: this.valueConfig = [ new ValueConfig(true, 'score', 0, false, false), new ValueConfig(false, 'time', 10, true, true) ]; break; } for(let i = 0; i < this.valueConfig.length; i++) { let weight = 0; for(let j = i + 1; j < this.valueConfig.length; j++) { weight += this.valueConfig[j].len; } this.valueConfig[i].setWeight(weight); } } /** * 返回字段修改 * @param obj */ private async generFields(obj: GuildRankInfo | RoleRankInfo) { return obj; } /** * 从外部设置返回字段 * @param cb */ public setGenerFieldsFun(cb: (obj: GuildRankInfo | RoleRankInfo) => any) { this.generFields = cb; } /** * 设置合成的排行榜的生命时长 单位s * @param unionRankLife */ public setUnionRankLife(unionRankLife: number) { this.unionRankLife = unionRankLife; } /** * 该排行是否为空 * @param void */ public async existsRank() { const result = await redisClient().existsAsync(this.keyName.getName()); return result; } /** * 根据role表设置更新排行榜&玩家信息 * @param roleId 玩家id * @param score 得分 * @param timestamp 时间点 * @param role 玩家数据库数据 * @param isInc 得分是累加上的还是直接设置的 */ public async setRankWithRoleInfo(roleId: string, score: number, timestamp: number, role?: RoleType, isInc = false) { // 如果没有信息,更新玩家信息 for (let infoKey of [this.infoKey, ...this.extraKeys]) { const hasCurUser = await redisClient().hexistsAsync(infoKey, roleId); if (!hasCurUser) { await this.generParamAndSet(infoKey, { roleId }, { role }); } } let newScore = await this.setRank({ roleId }, [score, timestamp], isInc); return newScore; } /** * 根据军团表设置更新排行榜&军团信息 * @param guildCode 军团code * @param score 得分 * @param timestamp 时间点 * @param guild 军团数据库 * @param isInc 得分是累加上的还是直接设置的 */ public async setRankWithGuildInfo(guildCode: string, score: number, timestamp: number, guild?: GuildType, isInc = false) { return await this.setRankWithGuildInfoArrParam(guildCode, [score, timestamp], guild, isInc); } public async setRankWithGuildInfo2(guildCode: string, num1: number, num2: number, num3: number, guild?: GuildType, isInc = false) { return await this.setRankWithGuildInfoArrParam(guildCode, [num1, num2, num3], guild, isInc); } public async setRankWithGuildInfoArrParam(guildCode: string, scores: number[], guild?: GuildType, isInc = false) { for (let infoKey of [this.infoKey, ...this.extraKeys]) { const hasCurUser = await redisClient().hexistsAsync(infoKey, guildCode); if (!hasCurUser) { await this.generParamAndSet(infoKey, { guildCode }, { guild }); } } let newScore = await this.setRank({ guildCode }, scores, isInc); return newScore; } /** * 根据武将表设置更新排行榜&玩家信息 * @param roleId 玩家id * @param hid 武将id * @param score 得分 * @param timestamp 时间点 * @param hero 武将数据库 * @param isInc 得分是累加上的还是直接设置的 */ public async setRankWithHeroInfo(roleId: string, hid: number, score: number, timestamp: number, hero?: HeroUpdate, isInc = false) { // 如果没有信息,更新玩家信息 for (let infoKey of [this.infoKey, ...this.extraKeys]) { await this.generParamAndSet(infoKey, { roleId, hid }, { hero }); } let newScore = await this.setRank({ roleId, hid }, [score, timestamp], isInc); return newScore; } /** * 将玩家信息按顺序组成 如:roleId:hid * @param {{ string }} key redis key * @param {{ roleId?: string; guildCode?: string; hid?: number }} param 玩家信息 */ private composeFields(key: REDIS_KEY, param: myIdInter) { let type = KEY_TO_COMPOSE_FIELD.get(key); if (type == COMPOSE_FIELD_TYPE.ROLE) { return param.roleId; } else if (type == COMPOSE_FIELD_TYPE.GUILD) { return param.guildCode; } else if (type == COMPOSE_FIELD_TYPE.ROLE_HERO) { return `${param.roleId}:${param.hid}`; } else { return ''; } } /** * 将合成了的玩家顺序解开 * @param key redis key * @param field * @returns {{ roleId?: string; guildCode?: string; hid?: number }} */ private decodeFields(key: REDIS_KEY, field: string) { let type = KEY_TO_COMPOSE_FIELD.get(key); let arr = field.split(':'); if (type == COMPOSE_FIELD_TYPE.ROLE) { return { roleId: arr[0] }; } else if (type == COMPOSE_FIELD_TYPE.GUILD) { return { guildCode: arr[0] }; } else if (type == COMPOSE_FIELD_TYPE.ROLE_HERO) { return { roleId: arr[0], hid: parseInt(arr[1]) }; } else { return {}; } } /** * 将玩家数据设置进redis * @param infoKey 玩家信息的redis key * @param fields 玩家id * @param db 数据库内的数据 */ public async generParamAndSet(infoKey: string, fields: myIdInter, db: { role?: RoleType, guild?: GuildType, hero?: HeroUpdate }) { let { roleId, guildCode, hid } = fields; let { role, guild, hero } = db; if (infoKey == REDIS_KEY.USER_INFO) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); if(!role) return; } let param = new RankParam(role); await this.setUserInfo(infoKey, { roleId }, param); } else if (infoKey == REDIS_KEY.GUILD_INFO) { if (!guild) { guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK) if(!guild) return; } let param = new GuildRankParam(guild); await this.setUserInfo(infoKey, { guildCode }, param); } else if (infoKey == REDIS_KEY.TOP_LINEUP_INFO) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); if(!role) return; } let { topLineup = [] } = role; let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP); let arr = new Array(); for (let { hid } of topLineup) { let curHero = heroes.find(cur => cur.hid == hid); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } await this.setUserInfo(infoKey, { roleId }, arr); } else if (infoKey == REDIS_KEY.HERO_INFO) { if (!hero) { hero = await HeroModel.findByHidAndRole(hid, roleId, HERO_SELECT.RANK_LINEUP, true); if(!hero) return; } let arr = new Array(); if (hero) { let param = new LineupParam(hero); arr.push(param); await this.setUserInfo(infoKey, { roleId, hid }, arr); } } else if (infoKey == REDIS_KEY.DUNGEON_LINEUP) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); if(!role) return; } let { dungeonWarId, dungeonHeroes } = role; if (dungeonWarId > 0) { let cur = dungeonHeroes.find(cur => cur.battleId == dungeonWarId); let lineup = cur ? cur.heroes : []; let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP); let arr = new Array(); for (let seqId of lineup) { let curHero = heroes.find(cur => cur.seqId == seqId); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } await this.setUserInfo(infoKey, { roleId }, arr); } } else if (infoKey == REDIS_KEY.SHOW_LINEUP) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); if(!role) return; } let { showLineup, topLineup = [] } = role; let heroes = await HeroModel.findByRole(roleId, [], HERO_SELECT.RANK_LINEUP); let arr = new Array(); if (!showLineup) showLineup = topLineup.map(cur => cur.hid); for (let hid of showLineup) { let curHero = heroes.find(cur => cur.hid == hid); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } await this.setUserInfo(infoKey, { roleId }, arr); } } /** * 设置信息 * @param infoKey * @param myId * @param param */ private async setUserInfo(infoKey: REDIS_KEY, myId: myIdInter, param: RankParam | GuildRankParam | LineupParam[]) { let field = this.composeFields(infoKey, myId); let value = JSON.stringify(param); return await redisClient().hsetAsync(infoKey, field, value); } /** * 更新排行榜数据 * @param myId * @param score * @param timestamp * @param isInc */ public async setRank(myId: myIdInter, scores: number[], isInc = false) { let oldTop = await this.getRankByRank(0, 0); let oldScore = oldTop.length > 0 && oldTop[0] ? oldTop[0].num : 0; // 更新分数 let newScore = 0; if (this.isUnion) { newScore = await this.updateRankScoreAtom(myId, scores, isInc); } else { newScore = await this.updateRankScoreEncode(myId, scores, isInc) } this.checkFirstRankAndPush(myId, oldScore, newScore); return newScore; } /** * @description 使用将score和time合成一个字段更新分数 * @param myId * @param score * @param timestamp */ private async updateRankScoreEncode(myId: myIdInter, scores: number[] = [], isInc = false) { let newScore = 0; let key = this.keyName.getName(); let newScores: number[] = []; for(let index = 0; index < this.valueConfig.length; index ++) { let config = this.valueConfig[index]; let score = scores[index]; if(config.isMain) { if (isInc) { let oldScore = await this.getMyScore(myId); newScore = oldScore + score; } else { newScore = score; } newScores.push(newScore); } else { newScores.push(score); } } let scoreStr = this.encodeScore(newScores); await redisClient().zaddAsync(key, scoreStr, this.composeFields(this.key, myId)); return newScore } /** * @description 使用分两个zset更新redis中某field的值 * @param myId * @param score * @param timestamp * @param isInc * @class Rank */ private async updateRankScoreAtom(myId: myIdInter, scores: number[], isInc = false) { let newScore = 0; for(let index = 0; index < this.valueConfig.length; index ++) { let config = this.valueConfig[index]; let score = scores[index]; if(config.isMain) { let key = this.keyName.getName(); // 分数zset if (isInc) { newScore = await redisClient().zincrbyAsync(key, score, this.composeFields(this.key, myId)); } else { newScore = await redisClient().zaddAsync(key, score, this.composeFields(this.key, myId)); } } else { let key = this.keyName.getNameWithPlus(config.name); if(config.isTimestamp) score = this.handleTimestamp(score, config.len); if(config.reverse) { let pow = Math.pow(10, config.len); score = pow - 1 - score; } await redisClient().zaddAsync(key, score, this.composeFields(this.key, myId)); } } return parseInt(newScore.toString()); } /** * @description 设置到期时间 * @param time 到期时间,10位时间戳 * @class Rank */ public async setExpire(time: number) { let key = this.keyName.getName(); let timeKey = this.keyName.getTimeName(); await redisClient().expireatAsync(key, time); await redisClient().expireatAsync(timeKey, time); } public async generMyRankWithRole(roleId: string, score: number = 0, time: number = 0, role?: RoleType) { // 如果没有信息,更新玩家信息 let param: RoleRankInfo; let hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId); if (!hasCurUser) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); } param = new RoleRankInfo(role); param.setInfo(0, { roleId }, score, time); } else { const info = await redisClient().hgetAsync(this.infoKey, roleId); const userInfo = JSON.parse(info); param = new RoleRankInfo(userInfo); param.setInfo(0, { roleId }, score, time); } for (let extraKey of this.extraKeys) { await this.setExInfoToParam(extraKey, param, { roleId }, { role }); } return await this.generFields(param); } public async generMyRankWithGuild(guildCode: string, score: number, time: number, guild?: GuildType) { // 如果没有信息,更新玩家信息 let param: GuildRankInfo; let hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode); if (!hasCurUser) { if (!guild) { guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK); } param = new GuildRankInfo(guild); param.setInfo(0, { guildCode }, score, time); } else { const info = await redisClient().hgetAsync(this.infoKey, guildCode); const guildInfo = JSON.parse(info); param = new GuildRankInfo(guildInfo); param.setInfo(0, { guildCode }, score, time); } for (let extraKey of this.extraKeys) { await this.setExInfoToParam(extraKey, param, { guildCode }, { guild }); } return await this.generFields(param); } public async generMyRankWithGuild2(guildCode: string, score: number, time: number, active: number, guild?: GuildType) { // 如果没有信息,更新玩家信息 let param: GuildRankInfo; let hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode); if (!hasCurUser) { if (!guild) { guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK); } param = new GuildRankInfo(guild); param.setInfo2(0, { guildCode }, score, time, active); } else { const info = await redisClient().hgetAsync(this.infoKey, guildCode); const guildInfo = JSON.parse(info); param = new GuildRankInfo(guildInfo); param.setInfo2(0, { guildCode }, score, time, active); } for (let extraKey of this.extraKeys) { await this.setExInfoToParam(extraKey, param, { guildCode }, { guild }); } return await this.generFields(param); } public async generMyRankWithHero(roleId: string, hid: number, score: number, time: number, hero?: HeroType, role?: RoleType) { // 如果没有信息,更新玩家信息 let param: RoleRankInfo; let hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId); if (!hasCurUser) { if (!role) { role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); } param = new RoleRankInfo(role); param.setInfo(0, { roleId, hid }, score, time); } else { const info = await redisClient().hgetAsync(this.infoKey, roleId); const userInfo = JSON.parse(info); param = new RoleRankInfo(userInfo); param.setInfo(0, { roleId, hid }, score, time); } for (let extraKey of this.extraKeys) { await this.setExInfoToParam(extraKey, param, { roleId, hid }, { hero, role }); } return await this.generFields(param); } private async setExInfoToParam(extraKey: string, param: RoleRankInfo | GuildRankInfo, myId: myIdInter, db: { role?: RoleType, guild?: GuildType, hero?: HeroType }) { let { role, guild, hero } = db; if (extraKey == REDIS_KEY.TOP_LINEUP_INFO) { param = param; let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId)); if (!hasCurUser) { if (!role) { role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true); } let { topLineup } = role; let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.HERO_DETAIL); let arr = new Array(); for (let { hid } of topLineup) { let curHero = heroes.find(cur => cur.hid == hid); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } param.setTopLine(arr); } else { const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId)); const infoObj = JSON.parse(info); param.setTopLine(infoObj); } } else if (extraKey == REDIS_KEY.HERO_INFO) { param = param; let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId)); if (!hasCurUser) { if (!hero) { hero = await HeroModel.findByHidAndRole(myId.hid, myId.roleId, HERO_SELECT.RANK_LINEUP); } let arr = new Array(); let lineParam = new LineupParam(hero); arr.push(lineParam); if (this.key == REDIS_KEY.HERO_RANK) { param.setSingleHero(arr); } else { param.setTopLine(arr); } } else { const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId)); const infoObj = JSON.parse(info); if (this.key == REDIS_KEY.HERO_RANK) { param.setSingleHero(infoObj); } else { param.setTopLine(infoObj); } } } else if (extraKey == REDIS_KEY.DUNGEON_LINEUP) { param = param; let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId)); if (!hasCurUser) { if (!role) { role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true); } let { dungeonWarId, dungeonHeroes } = role; let cur = dungeonHeroes.find(cur => cur.battleId == dungeonWarId); let lineup = cur ? cur.heroes : []; let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.RANK_LINEUP); let arr = new Array(); for (let seqId of lineup) { let curHero = heroes.find(cur => cur.seqId == seqId); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } param.setTopLine(arr); } else { const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId)); const infoObj = JSON.parse(info); param.setTopLine(infoObj); } } else if (extraKey == REDIS_KEY.SHOW_LINEUP) { param = param; let hasCurUser = await redisClient().hexistsAsync(extraKey, this.composeFields(extraKey, myId)); if (!hasCurUser) { if (!role) { role = await RoleModel.findByRoleId(myId.roleId, ROLE_SELECT.RANK, true); } let { showLineup, topLineup = [] } = role; let heroes = await HeroModel.findByRole(myId.roleId, [], HERO_SELECT.HERO_DETAIL); let arr = new Array(); if (!showLineup) showLineup = topLineup.map(cur => cur.hid); for (let hid of showLineup) { let curHero = heroes.find(cur => cur.hid == hid); if (curHero) { let param = new LineupParam(curHero); arr.push(param); } } param.setTopLine(arr); } else { const info = await redisClient().hgetAsync(extraKey, this.composeFields(extraKey, myId)); const infoObj = JSON.parse(info); param.setTopLine(infoObj); } } } public async getRankListWithMyRank(myId: myIdInter, isReverse = false) { let ranks = await this.getRankByRange('+inf', '-inf', isReverse); let newRanks = [], newMyRank: any; let myRank = ranks.find(cur => { return cur.isMyInfo(myId); }); if (this.generFields) { for (let rank of ranks) { let n = await this.generFields(rank); newRanks.push(n); } if (myRank) { newMyRank = await this.generFields(myRank); } } return { myRank: newMyRank, ranks: newRanks } } /** * 获取排行榜原始数据 * @param from * @param to */ public async getRankDataByRankWithoutDetail(from: number | string = '+inf', to: number | string = '-inf') { let key = this.keyName.getName(); if (this.isUnion) { key = await this.generateUnionRank(); } const rankFromDb = await redisClient().zrevrangebyscoreAsync(key, from, to, "WITHSCORES"); let ranks: { rank: number, myId: {roleId?: string, guildCode?: string, hid?: number}, scores: number[] }[] = []; let num = 0; for (let ii = 0; ii < rankFromDb.length; ii += 2) { if (num >= this.limit) break; const field = rankFromDb[ii]; let myId = this.decodeFields(this.key, field); const scores = this.decodeScore(rankFromDb[ii + 1]); ranks.push({ rank: num, myId, scores }); num++; } return ranks } // 获取区间里的排行榜,格式 {rank, field, score}[] public async getRankByRangeRaw(max: number | string = '+inf', min: number | string = '-inf', isReverse = true) { let ranks: { rank: number, field: string, scores: number[] }[] = []; let key = this.keyName.getName(); if (this.isUnion) { key = await this.generateUnionRank(); } const rankFromDb = isReverse? (await redisClient().zrevrangebyscoreAsync(key, max, min, "WITHSCORES")): (await redisClient().zrangebyscoreAsync(key, min, max, "WITHSCORES")); let num = 0; for (let ii = 0; ii < rankFromDb.length; ii += 2) { if (num >= this.limit) break; ranks.push({ rank: Math.floor(ii / 2) + 1, field: rankFromDb[ii], scores: this.decodeScore(rankFromDb[ii + 1]) }); num++; } return ranks } public async getRankByRange(max: number | string = '+inf', min: number | string = '-inf', isReverse = true) { let raws = await this.getRankByRangeRaw(max, min, isReverse); let ranks = new Array(); for (let { rank, field, scores } of raws) { let param = await this.getParam(rank, field, scores); if(param) ranks.push(param); } return ranks } public async getParam(rank: number, field: string, scores: number[]) { let myId = this.decodeFields(this.key, field); const info = await redisClient().hgetAsync(this.infoKey, this.composeFields(this.infoKey, myId)); const userInfo = JSON.parse(info); let param: RoleRankInfo | GuildRankInfo; if (this.infoKey == REDIS_KEY.USER_INFO) { if (nowSeconds() - userInfo.updatedAt > 2 * 30 * 24 * 60 * 60) { return null; } param = new RoleRankInfo(userInfo); param.setInfo(rank, myId, scores[0], scores[1]); } else if (this.infoKey == REDIS_KEY.GUILD_INFO) { if(this.key == REDIS_KEY.GUILD_LV_RANK|| this.key == REDIS_KEY.RACE_ACTIVITY) { param = new GuildRankInfo(userInfo); param.setInfo2(rank, myId, scores[0], scores[1], scores[2]); } else { param = new GuildRankInfo(userInfo); param.setInfo(rank, myId, scores[0], scores[1]); } } for (let extraKey of this.extraKeys) { await this.setExInfoToParam(extraKey, param, myId, {}); } return param } public async getRankByRank(start: number, end: number) { let ranks = new Array(); let key = this.keyName.getName(); if (this.isUnion) { key = await this.generateUnionRank(); } const rankFromDb = await redisClient().zrevrangeAsync(key, start, end, "WITHSCORES"); // console.log(key, start, end, rankFromDb) let num = 0; for (let ii = 0; ii < rankFromDb.length; ii += 2) { if (num >= this.limit) break; let param = await this.getParam(Math.floor(ii / 2) + 1, rankFromDb[ii], this.decodeScore(rankFromDb[ii + 1])); if(param) ranks.push(param); num++; } return ranks } // 获取我的排名 public async getMyRank(myId: myIdInter) { let key = this.keyName.getName(); if (this.isUnion) { key = await this.generateUnionRank(); } // console.log('**getMyRank',key, this.composeFields(this.key, myId), this.key, myId) let myRank = await redisClient().zrevrankAsync(key, this.composeFields(this.key, myId)); if (!myRank && myRank != 0) { return 0; } else { return myRank + 1; } } // 获取排名第几名的信息 public async getUserByRank(rank: number) { let key = this.keyName.getName(); let myRank = await redisClient().zrevrangeAsync(key, rank - 1, rank - 1); return myRank; } // 获取排名第几名的信息 public async getMyScore(myId: myIdInter) { let key = this.keyName.getName(); let score = await redisClient().zscoreAsync(key, this.composeFields(this.key, myId)); if (!score) score = 0; if (!this.isUnion) { let result = this.decodeScore(score.toString()); score = result[0]; } return parseInt(score.toString()); } // 从排行榜中移除 public async removeFromRank(myId: myIdInter) { let key = this.keyName.getName(); await redisClient().zremAsync(key, this.composeFields(this.key, myId)); return true; } private async generateUnionRank() { let unionKey = this.keyName.getUnionName(); // 联合的key let keys = [], weights = []; this.valueConfig.forEach(({ isMain, name, weight }) => { if(isMain) { keys.push(this.keyName.getName()); } else { keys.push(this.keyName.getNameWithPlus(name)); } weights.push(Math.pow(10, weight)) }) await redisClient().zunionstoreAsync(unionKey, 2, ...keys, 'WEIGHTS', ...weights); await redisClient().expireAsync(unionKey, this.unionRankLife); // 10秒更新一次 return unionKey; } // 有序排行综合时间和得分排序 private encodeScore(scores: number[]) { let encodeResult = 0; // console.log('#### decoencodeResultdeScore', this.key, scores, this.valueConfig); this.valueConfig.forEach(({ name, reverse, isTimestamp, len, weight }, index) => { let score = scores[index]; // console.log('*** 1', name, len, weight, score, encodeResult) if(isTimestamp) score = this.handleTimestamp(score, len); // console.log('*** 2', name, len, weight, score, encodeResult) if(!reverse) { encodeResult += score * Math.pow(10, weight); } else { let pow = Math.pow(10, len); encodeResult += (pow - 1 - score) * Math.pow(10, weight); } // console.log('*** 3', name, len, weight, score, encodeResult) }) return encodeResult } private decodeScore(num: string) { let _num = parseInt(num); let scores: number[] = []; this.valueConfig.forEach(({ name, reverse, isTimestamp, len, weight }) => { let pow = Math.pow(10, weight); let score = Math.floor(_num / pow); // console.log('*** 1', name, len, weight, score) _num -= pow * score; if(isTimestamp) score = this.handleTimestamp(score, len); // console.log('*** 2', name, len, weight, score) if(reverse) { let pow = Math.pow(10, len); score = pow - 1 - score; } // console.log('*** 3', name, len, weight, score) scores.push(score); }) // console.log('#### decodeScore', this.key, scores, this.valueConfig); return scores; } private handleTimestamp(timestamp: number = 0, len: number) { let l = timestamp.toString().length; if (l > len) { timestamp = Math.floor(timestamp / Math.pow(10, l - len)); } return timestamp } private async getFirstUserInfo(myId: myIdInter) { const info = await redisClient().hgetAsync(this.infoKey, this.composeFields(this.infoKey, myId)); const playerInfo = JSON.parse(info); if (this.infoKey == REDIS_KEY.USER_INFO) { let userInfo = new RankParam(playerInfo); return { userInfo } } else if (this.infoKey == REDIS_KEY.GUILD_INFO) { let guildInfo = new GuildRankInfo(playerInfo); return { guildInfo } } return false } // 首通 private async checkFirstRankAndPush(myId: myIdInter, oldScore: number, newScore: number) { let ts = this; if (ts.isInit) return; let serverId = this.keyName.serverId; let dicRank = gameData.rank.find(({ id }) => { let redisKey = getRedisKeyByRankType(id); return redisKey == ts.key }); if(!dicRank) return; let infos = await this.getFirstUserInfo(myId); if(!infos) return; let { userInfo, guildInfo } = infos let rankFirstRecs: (RankFirstType & { status: number })[] = []; for(let [id, { rankId, condition }] of gameData.generalRankReward) { // console.log(rankId, dicRank.id, oldScore, condition, newScore); let hasRankFirst = await getRankFirstRewardById(serverId, id); if(!hasRankFirst && dicRank.id == rankId && oldScore < condition && newScore >= condition) { let rankFirstRec = await RankFirstModel.createRankFirst(serverId, id, rankId, { userInfo, guildInfo }); if(rankFirstRec && getSeconds(rankFirstRec.createdAt) == getSeconds(rankFirstRec.updatedAt)) { await setRankFirst(rankFirstRec); rankFirstRecs.push({ ... rankFirstRec, status: 1}); } } } if(rankFirstRecs.length > 0) { await sendMessageToServerWithSuc(serverId, PUSH_ROUTE.RANK_TOP_UPDATE, { ranks: rankFirstRecs }, true); } } } /** * 从其他服获取首通信息 * @param serverId * @param id dic_zyz_rankingType的id * @returns */ async function getRankFirstRewardById(serverId: number, id: number) { let rankFirst: RankFirstType; if(pinus.app.getServerType() == 'role') { rankFirst = pinus.app.get('rankFirstRewards').get(serverId)?.get(id); } else { let roleServers = pinus.app.getServersByType('role'); let server = getRandSingleEelm(roleServers); rankFirst = await pinus.app.rpc.role.roleRemote.getRankFirstById.toServer(server.id, serverId, id); } return rankFirst; } async function setRankFirst(rankFirstRec: RankFirstType) { let roleServers = pinus.app.getServersByType('role'); for(let server of roleServers) { pinus.app.rpc.role.roleRemote.setRankFirst.toServer(server.id, rankFirstRec); } } /** * 从数据库内获取排行榜存入redis * @param type 排行榜类型 * @param serverId 分服 */ export async function setRankRedisFromDb(type: string, args?: { serverId?: number }) { if (type == REDIS_KEY.TOWER_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('tower', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { // console.log(roleId); if(role.towerLv > 1) { await r.setRankWithRoleInfo(role.roleId, role.towerLv - 1, role.towerUpTime ? role.towerUpTime.getTime() : 0, role); } } } else if (type == REDIS_KEY.GUILD_ACTIVE_RANK) { let serverId = args.serverId; let ranks = await GuildModel.getRank(type, serverId); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let guild of ranks) { await r.setRankWithGuildInfo(guild.code, guild.activeWeekly, guild.activeUpdateTime, guild); } } else if (type == REDIS_KEY.GUILD_LV_RANK) { let serverId = args.serverId; let ranks = await GuildModel.getRank(type, serverId); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let guild of ranks) { await r.setRankWithGuildInfoArrParam(guild.code, [guild.lv, guild.activeWeekly, guild.activeUpdateTime], guild); } } else if (type == REDIS_KEY.PVP_RANK) { let seasonNum = await CounterModel.getCounter(COUNTER.PVP_SEASON_NUM); if(seasonNum > 0) { let keyName = new KeyName(type, { seasonNum }) await delKeys(keyName.getName()); let ranks = await PvpDefenseModel.getRank(seasonNum);//获得全服前1000名的排名,加入到redis中 let r = new Rank(type, { seasonNum }); r.setIsInit(true); for (let { roleId, role: _role, score, updatedAt } of ranks) { let role = _role; if (!role) { continue; } await r.setRankWithRoleInfo(roleId, score, updatedAt.getTime(), role); } } } else if (type == REDIS_KEY.TOP_LINEUP_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('topLineup', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { // console.log(roleId); await r.setRankWithRoleInfo(role.roleId, role.topLineupCe, role.updatedAt.getTime(), role); } } else if (type == REDIS_KEY.TOP_HERO_RANK) { let serverId = args.serverId; let ranks = await HeroModel.getAllRank(serverId, HERO_SELECT.RANK_LINEUP); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let hero of ranks) { await r.setRankWithHeroInfo(hero.roleId, hero.hid, hero.ce, hero.updatedAt.getTime(), hero); } } else if (type == REDIS_KEY.HERO_NUM_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('heroNum', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.heroNum, role.heroNumUpdatedAt, role); } } else if (type == REDIS_KEY.USER_LV) { let serverId = args.serverId; let ranks = await RoleModel.getRank('lv', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.lv, role.updatedAt.getTime(), role); } } else if (type == REDIS_KEY.SUM_CE_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('ce', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.ce, role.updatedAt.getTime(), role); } } else if (type == REDIS_KEY.DUNGEON_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('dungeon', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.dungeonWarId, role.dungeonUpdatedAt, role); } } else if (type == REDIS_KEY.MAIN_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('main', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.mainWarId, role.mainUpdatedAt, role); } } else if (type == REDIS_KEY.MAIN_ELITE_RANK) { let serverId = args.serverId; let ranks = await RoleModel.getRank('mainElite', serverId, ROLE_SELECT.RANK); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let role of ranks) { await r.setRankWithRoleInfo(role.roleId, role.mainEliteWarId, role.mainEliteUpdatedAt, role); } } else if (type == REDIS_KEY.HERO_RANK) { let serverId = args.serverId; for (let { actorId: hid } of gameData.recruit) { let ranks = await HeroModel.getRank(hid, serverId, HERO_SELECT.RANK_LINEUP); let r = new Rank(type, { serverId, hid }); r.setIsInit(true); for (let hero of ranks) { await r.setRankWithHeroInfo(hero.roleId, hid, hero.ce, hero.updatedAt.getTime(), hero); } } } else if (type == REDIS_KEY.LADDER) { let serverId = args.serverId; let ranks = await LadderMatchModel.findAll(serverId); let r = new Rank(type, { serverId }); r.setIsInit(true); for (let rank of ranks) { let role = rank.role; await r.setRankWithRoleInfo(rank.roleId, rank.rank, 0, role); await saveLadderDefCeByData(rank.roleId, rank); } } } /** * 获取排行榜总览 * @param role 玩家数据 * @param serverId 所在服务器 */ export async function getGeneralRank(role: RoleType & { rankReceived: number[] }, serverId: number) { let { rankReceived = [] } = role; let result: (GeneralRankParam & {general: number})[] = []; for (let { id, general } of gameData.rank) { let redisKey = getRedisKeyByRankType(id); if (redisKey) { let received = rankReceived.filter(rewardId => { let dic = gameData.generalRankReward.get(rewardId); return dic && dic.rankId == id; }); let r = new Rank(redisKey, { serverId }, false, 1); let ranks = await r.getRankByRange(); // if (ranks.length > 0) { let param = new GeneralRankParam(id, ranks[0] || new RoleRankInfo({}), general, received); result.push({...param, general}); } } return result; } /** * 获取排行榜总览 * @param role 玩家数据 * @param serverId 所在服务器 */ export async function getRankFirstReward(role: RoleType & { rankReceived: number[] }, serverId: number) { let { rankReceived = [] } = role; let result: (RankFirstUpdateParam & {status: number})[] = []; for (let [id, { rankId: type } ] of gameData.generalRankReward) { let rankFirst = await getRankFirstRewardById(serverId, id); if(rankFirst) { let status = rankReceived.includes(id)?RANK_FIRST_REWARD_STATUS.RECEIVED: RANK_FIRST_REWARD_STATUS.CAN_RECEIVE; result.push({...rankFirst, status}); } else { result.push({ id, type, status: RANK_FIRST_REWARD_STATUS.WAITING }); } } return result; } export async function getRankInHandler(redisKey: REDIS_KEY, type: RANK_TYPE, keyParam: KeyNameParam, session: BackendSession) { const roleId = session.get('roleId'); const serverId = session.get('serverId'); const guildCode = session.get('guildCode'); let r = new Rank(redisKey, keyParam); r.setGenerFieldsFun((obj => { let result = new RoleAndGuildRankInfo(obj.rank, obj.num); if(type == RANK_TYPE.LADDER) { result = new RoleAndGuildRankInfo(obj.num, 0); } if(obj instanceof GuildRankInfo) { result.setGuildInfo(obj); } if(obj instanceof RoleRankInfo) { result.setUserInfo(obj); } return result })); let { ranks, myRank } = await r.getRankListWithMyRank({ roleId, guildCode }, type != RANK_TYPE.LADDER); if (!myRank) { let role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true); if (type == RANK_TYPE.TOP_LINTUP) { myRank = await r.generMyRankWithRole(roleId, role.topLineupCe, 0, role); } else if (type == RANK_TYPE.TOP_HERO) { let hero = await HeroModel.getMyTopHero(roleId, HERO_SELECT.RANK_LINEUP); myRank = await r.generMyRankWithHero(roleId, hero.hid, hero.ce, 0, hero, role); } else if (type == RANK_TYPE.HERO_NUM) { myRank = await r.generMyRankWithRole(roleId, role.heroNum, role.heroNumUpdatedAt, role); } else if (type == RANK_TYPE.USER_LV) { myRank = await r.generMyRankWithRole(roleId, role.lv, role.updatedAt.getTime(), role); } else if (type == RANK_TYPE.SUM_CE) { myRank = await r.generMyRankWithRole(roleId, role.ce, role.updatedAt.getTime(), role); } else if (type == RANK_TYPE.TOWER) { myRank = await r.generMyRankWithRole(roleId, role.towerLv - 1, role.towerUpTime?.getTime() || 0, role); // } else if (type == RANK_TYPE.DUNGEON) { // myRank = await r.generMyRankWithRole(roleId, role.dungeonWarId, role.dungeonUpdatedAt, role); } else if (type == RANK_TYPE.MAIN) { myRank = await r.generMyRankWithRole(roleId, role.mainWarId, role.mainUpdatedAt, role); } else if (type == RANK_TYPE.MAIN_ELITE) { myRank = await r.generMyRankWithRole(roleId, role.mainEliteWarId, role.mainEliteUpdatedAt, role); } else if (type == RANK_TYPE.GUILD_LV) { if(role.hasGuild) { let guild = await GuildModel.findByCode(guildCode, serverId); myRank = await r.generMyRankWithGuild2(roleId, guild.lv, guild.activeWeekly, guild.lvUpdateTime, guild); } } else if (type == RANK_TYPE.GUILD_FUND) { if(role.hasGuild) { let limitRank = await ActivityTimeLimitRankModel.findByCode(serverId, keyParam.activityId, guildCode); myRank = await r.generMyRankWithGuild(guildCode, limitRank?.score||0, limitRank?.time); } } else if (type == RANK_TYPE.LADDER) { myRank = await r.generMyRankWithRole(roleId, 0, 0, role); } } if(type == RANK_TYPE.LADDER) { ranks = completeLadderRanks(ranks); } return { ranks, myRank } }