排行榜:旧数据完成

This commit is contained in:
luying
2021-04-12 09:47:15 +08:00
parent 99bc61b2fc
commit 34e02dea89
23 changed files with 1014 additions and 718 deletions
+305
View File
@@ -0,0 +1,305 @@
import { KeyName, KeyNameParam, RankParam, GuildRankParam, RoleRankInfo, GuildRankInfo, GuildLeader } from "../domain/rank";
import { REDIS_RANK_TO_INFO, ROLE_SELECT, GUILD_SELECT, REDIS_KEY } from "../consts";
import { redisClient, setUserInfo } from "./redisService";
import { RoleType, RoleModel } from "../db/Role";
import { GuildType, GuildModel } from "../db/Guild";
/**
* @description 排行榜相关操作
* @export
* @class Rank
*/
export class Rank {
key: string; // 排行榜原始key
keyName: KeyName; // 拼接之后的key
infoKey: string; // 玩家数据key
isUnion: boolean; // 是否使用多个zset联合计算
limit: number = 200; // 排行榜长度
timelen: number = 10; // 给时间位留的长度
unionRankLife: number = 10;
constructor(key: string, keyParam: KeyNameParam, isUnion = false, timelen = 10, limit = 200) {
this.key = key;
this.keyName = new KeyName(key, keyParam);
this.infoKey = REDIS_RANK_TO_INFO.get(key);
this.isUnion = isUnion;
this.timelen = timelen;
this.limit = limit;
}
private async generFields(obj: GuildRankInfo|RoleRankInfo) {
return obj;
}
public setGenerFieldsFun(cb: (obj: GuildRankInfo|RoleRankInfo) => any) {
this.generFields = cb;
}
// 合成的排行榜的生命时长 单位s
public setUnionRankLife(unionRankLife: number) {
this.unionRankLife = unionRankLife;
}
public async existsRank() {
const result = await redisClient().existsAsync(this.keyName.getName());
return result;
}
public async setRankWithRoleInfo(roleId: string, score: number, timestamp: number, role?: RoleType, needReduceCe = false, isInc = true ) {
// 如果没有信息,更新玩家信息
const hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId);
if(!hasCurUser) {
if(!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
needReduceCe = true;
}
let param = new RankParam(role, needReduceCe);
await setUserInfo(this.infoKey, roleId, param);
}
let newScore = await this.setRank(roleId, score, timestamp, isInc);
return newScore;
}
public async setRankWithGuildInfo(guildCode: string, score: number, timestamp: number, guild?: GuildType, isInc = true) {
const hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode);
if(!hasCurUser) {
if(!guild) {
guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK)
}
let param = new GuildRankParam(guild);
await setUserInfo(this.infoKey, guildCode, param);
}
let newScore = await this.setRank(guildCode, score, timestamp, isInc);
return newScore;
}
public async setRank(myId: string, score: number, timestamp: number, isInc = false) {
// 更新分数
let newScore = score;
if(this.isUnion) {
newScore = await this.updateRankScoreAtom(myId, score, timestamp, isInc);
} else {
newScore = await this.updateRankScoreEncode(myId, score, timestamp, isInc)
}
return newScore;
}
/**
* @description 使用将score和time合成一个字段更新分数
* @param myId
* @param score
* @param timestamp
*/
private async updateRankScoreEncode(myId: string, score: number, timestamp: number, isInc = false) {
let newScore = score;
let key = this.keyName.getName();
if(isInc) {
let oldScore = await this.getMyScore(myId);
newScore = oldScore + score;
}
let scoreStr = this.encodeScore(newScore, timestamp);
await redisClient().zaddAsync(key, scoreStr, myId);
return score
}
/**
* @description 使用分两个zset更新redis中某field的值
* @param myId
* @param score
* @param timestamp
* @param isInc
* @class Rank
*/
private async updateRankScoreAtom(myId: string, score: number, timestamp: number, isInc = false) {
let key = this.keyName.getName();
let timeKey = this.keyName.getTimeName();
let pow = Math.pow(10, this.timelen + 1);
let newScore = 0;
// 分数zset
if(isInc) {
newScore = await redisClient().zincrbyAsync(key, score, myId);
} else {
newScore = await redisClient().zaddAsync(key, score, myId);
}
// 时间zset
await redisClient().zaddAsync(timeKey, pow - 1 - Math.floor(timestamp/1000), 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, time: number, role?: RoleType, needReduceCe = false) {
// 如果没有信息,更新玩家信息
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, roleId);
if(!hasCurUser) {
if(!role) {
role = await RoleModel.findByRoleId(roleId, ROLE_SELECT.RANK, true);
needReduceCe = true;
}
let param = new RoleRankInfo(role, needReduceCe);
param.setInfo(0, roleId, score, time);
return await this.generFields(param);
} else {
const info = await redisClient().hgetAsync(this.infoKey, roleId);
const userInfo = JSON.parse(info);
let param = new RoleRankInfo(userInfo);
param.setInfo(0, roleId, score, time);
return await this.generFields(param);
}
}
public async generMyRankWithGuild(guildCode: string, score: number, time: number, guild?: GuildType) {
// 如果没有信息,更新玩家信息
let hasCurUser = await redisClient().hexistsAsync(this.infoKey, guildCode);
if(!hasCurUser) {
if(!guild) {
guild = await GuildModel.findByCode(guildCode, this.keyName.serverId, GUILD_SELECT.RANK);
}
let param = new GuildRankInfo(guild);
param.setInfo(0, guildCode, score, time);
return await this.generFields(param);
} else {
const info = await redisClient().hgetAsync(this.infoKey, guildCode);
const guildInfo = JSON.parse(info);
let param = new GuildRankInfo(guildInfo);
param.setInfo(0, guildCode, score, time);
return await this.generFields(param);
}
}
public async getRankListWithMyRank(myId: string) {
let ranks = await this.getRankByRange();
let newRanks = [], newMyRank;
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 }
}
public async getRankByRange(from: number|string = '+inf', to: number|string = '-inf') {
let ranks = new Array<RoleRankInfo|GuildRankInfo>();
let key = this.keyName.getName();
if(this.isUnion) {
key = await this.generateUnionRank();
}
const rankFromDb = await redisClient().zrevrangebyscoreAsync(key, from, to, "WITHSCORES", "LIMIT", 0, this.limit);
for(let ii = 0; ii < rankFromDb.length; ii+=2) {
const myId = rankFromDb[ii];
const { score, time } = this.decodeScore(rankFromDb[ii + 1]);
const info = await redisClient().hgetAsync(this.infoKey, myId);
const userInfo = JSON.parse(info);
if(this.infoKey == REDIS_KEY.USER_INFO) {
let param = new RoleRankInfo(userInfo);
param.setInfo(Math.floor(ii/2)+1, myId, score, time);
ranks.push(param);
} else if(this.infoKey == REDIS_KEY.GUILD_INFO) {
let param = new GuildRankInfo(userInfo);
param.setInfo(Math.floor(ii/2)+1, myId, score, time);
ranks.push(param);
}
}
return ranks
}
// 获取我的排名
public async getMyRank(myId: string) {
let key = this.keyName.getName();
if(this.isUnion) {
key = await this.generateUnionRank();
}
let myRank = await redisClient().zrevrankAsync(key, myId);
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: string) {
let key = this.keyName.getName();
let score = await redisClient().zscoreAsync(key, myId);
if(!score) score = 0;
if(!this.isUnion) {
let result = this.decodeScore(score.toString());
score = result.score;
}
return parseInt(score.toString());
}
// 从排行榜中移除
public async removeFromRank(myId: string) {
let key = this.keyName.getName();
await redisClient().zremAsync(key, myId);
return true;
}
private async generateUnionRank() {
let unionKey = this.keyName.getUnionName(); // 联合的key
let existsKey = await redisClient().existsAsync(unionKey);
if(!existsKey) {
let originKey = this.keyName.getName();
let timeKey = this.keyName.getTimeName();
let pow = Math.pow(10, this.timelen + 1);
await redisClient().zunionstoreAsync(unionKey, 2, originKey, timeKey, 'WEIGHTS', pow, 1);
await redisClient().expireAsync(unionKey, this.unionRankLife); // 10秒更新一次
}
return unionKey;
}
// 有序排行综合时间和得分排序
private encodeScore(score: number, timestamp: number) {
let timelen = this.timelen;
let pow = Math.pow(10, timelen + 1);
return score * pow + pow - 1 - Math.floor(timestamp/1000)
}
private decodeScore(num: string) {
let timelen = this.timelen;
let pow = Math.pow(10, timelen + 1);
let _num = parseInt(num);
return {
time: pow - _num % pow,
score: Math.floor(_num/pow)
};
}
}