排行榜:旧数据完成
This commit is contained in:
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user