409 lines
14 KiB
TypeScript
409 lines
14 KiB
TypeScript
import { rankType, ResOpen } from '../../shared/protocols/rank/PtlOpen';
|
||
import { rankInfo } from '../../shared/protocols/type';
|
||
import { Queue } from '../../shared/public/queue';
|
||
import { RankClslCross } from './rank_clsl';
|
||
//import { RankHbzbJfsCross, RankHbzbJfsLocal, RankHbzbZbsCross } from './rank_hbzb_jfs';
|
||
import { RankPower } from './rank_power';
|
||
import { RankQjzzd } from './rank_qjzzd';
|
||
import { RankSlzd1, RankSlzd2, RankSlzd3, RankSlzd4, RankSlzd5, RankSlzd6 } from './rank_slzd';
|
||
import { RankTanXian } from './rank_tanxian';
|
||
import { RankTujian } from './rank_tujian';
|
||
import { RankWzryCross } from './rank_wzry';
|
||
import { RankXszm } from './rank_xszm';
|
||
import { RankZccg } from './rank_zccg';
|
||
import * as util from 'util'
|
||
import { RankKfjs_1 } from "./rank_kfjs";
|
||
import { clusterRunOnce } from '../../clusterUtils';
|
||
import { number } from 'mathjs';
|
||
|
||
|
||
|
||
export abstract class Rank {
|
||
static list: Partial<{
|
||
qjzzd: RankQjzzd;
|
||
zhanli: RankPower;
|
||
tanxian: RankTanXian;
|
||
slzd1: RankSlzd1;
|
||
slzd2: RankSlzd2;
|
||
slzd3: RankSlzd3;
|
||
slzd4: RankSlzd4;
|
||
slzd5: RankSlzd5;
|
||
slzd6: RankSlzd6;
|
||
xszm: RankXszm;
|
||
clslCross: RankClslCross;
|
||
zccg: RankZccg;
|
||
tujian: RankTujian;
|
||
wzryCross: RankWzryCross;
|
||
}> & k_v<Rank> = {};
|
||
|
||
queue = new Queue();
|
||
findKey = 'uid';
|
||
countMaxNum = 50;
|
||
utimeTTL = 60;
|
||
|
||
abstract getType(): rankType;
|
||
|
||
// 获取rank数据, 注:可能重写,具体根据type类型判断
|
||
async getRankList(uid: string, { min, max }): Promise<{ rankList: rankInfo[]; myRank: rankInfo; }> {
|
||
let conn = G?.server?.uid_connections[uid];
|
||
let data = await this.getRankData(uid);
|
||
let rankList = await this.getRankListRange(min, max);
|
||
let rank = await this.getRankSortByOne(uid);
|
||
let score = await this.getRankScore(uid)
|
||
return {
|
||
rankList: rankList,
|
||
myRank: {
|
||
rank,
|
||
player: data?.player || conn?.gud || {},
|
||
valArr: [score]
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 根据RankInfo数据,获取用于排名的积分值得
|
||
* 注:可能重写,具体根据type类型判断
|
||
* @param info
|
||
* @returns 积分值
|
||
*/
|
||
getValArr(info: rankInfo): number | string {
|
||
return info?.valArr[0] || 0
|
||
}
|
||
|
||
// 注:可能重写,具体根据type类型判断
|
||
compare(other: rankInfo, cur: rankInfo): boolean {
|
||
return cur.valArr[0] > other.valArr[0];
|
||
}
|
||
|
||
// rankList排序,注:可能重写,具体根据type类型判断
|
||
compareSort(a: rankInfo, b: rankInfo): number {
|
||
return b.valArr[0] - a.valArr[0];
|
||
}
|
||
|
||
/**
|
||
* 页面转换,根据page和offset转换出min和max
|
||
* @param page 页面
|
||
* @param offset 每页数量
|
||
* @returns
|
||
*/
|
||
static pageToMin(page: number, offset: number) {
|
||
let res = {
|
||
min: page * offset,
|
||
max: page * offset + offset
|
||
}
|
||
return res
|
||
}
|
||
|
||
/**
|
||
* 返回排行榜DB连接对象
|
||
*/
|
||
get db() {
|
||
return G.mongodb.collection('rankList');
|
||
}
|
||
|
||
/**
|
||
* 返回排行榜类型
|
||
*/
|
||
get type() {
|
||
return this.getType();
|
||
}
|
||
|
||
constructor() {
|
||
Rank.list[this.getType() as string] = this;
|
||
this.cotr();
|
||
}
|
||
|
||
/**
|
||
* 根据type,获取redis Data的key
|
||
*/
|
||
get getRedisKey() {
|
||
return util.format('rank:%s:data', this.getType())
|
||
}
|
||
|
||
/**
|
||
* 根据type,获取redis Sort的key
|
||
*/
|
||
get getRedisKeySort() {
|
||
return util.format('rank:%s:sort', this.getType())
|
||
}
|
||
|
||
// 初始化
|
||
async cotr() {
|
||
clusterRunOnce(async () => {
|
||
// redis已存在则不初始化
|
||
//if(await this.getRankLen() > 0) return
|
||
|
||
//将db里的数据,写入到 rank:xxx:sort里
|
||
//写入的单条数据为: {uid:score}
|
||
this.db.find({ type: this.type }, {
|
||
projection: {
|
||
"idKey": 1,
|
||
"type": 1,
|
||
"data.valArr": 1,
|
||
"utime": 1
|
||
}
|
||
}).toArray().then(listArr => {
|
||
// 写入初始化数据
|
||
listArr = listArr || [];
|
||
listArr.forEach(item => {
|
||
if (item.idKey && item.data) this.setRankData(item.idKey, item.data)
|
||
})
|
||
});
|
||
})
|
||
|
||
this.getRankListRange();
|
||
}
|
||
|
||
/**
|
||
* 更新玩家的积分
|
||
* @param uid uid,在rank里通常保存于idKey这个字段
|
||
* @param data 积分数据,主要是需要里面的data.valArr字段
|
||
* @returns
|
||
*/
|
||
async setRankData(uid: string, data: rankInfo) {
|
||
let keySort = this.getRedisKeySort
|
||
|
||
// let key = this.getRedisKey
|
||
// G.redis.hSet(key, idKey, data)
|
||
|
||
/**积分 */
|
||
let score = await this.getValArr(data)
|
||
//G.redis.zAdd(keySort, <sortEd>{value: idKey, score: valArr})
|
||
await G.ioredis.zadd(keySort, score, uid)
|
||
return
|
||
}
|
||
|
||
/**
|
||
* 获取单个用户的数据
|
||
* @param uid uid
|
||
* @returns
|
||
*/
|
||
async getRankData(uid: string) {
|
||
let data: rankInfo;
|
||
let res = await this.db.findOne({ "idKey": uid, "type": this.getType() })
|
||
|
||
if (res?.data && res.data.utime + 60 > G.time) {
|
||
data = (G.mongodb.conversionIdObj(res)).data;
|
||
} else if (res?.data) {
|
||
let player = await G.mongodb.collection("user").findOne({ uid: uid }, { projection: { _id: 0 } });
|
||
|
||
data = Object.assign(res.data, { player: player });
|
||
this.db.updateOne({ idKey: uid, type: this.getType() }, { $set: { "data.player": data.player, "data.utime": G.time } });
|
||
}
|
||
return data;
|
||
|
||
// let key = this.getRedisKey
|
||
// let data = await G.redis.hGet(key, idKey)
|
||
// if(!data || data.utime < (G.time - this.utimeTTL)) {
|
||
// let type = this.getType()
|
||
// let res = await this.db.findOne({isKey: idKey, type}) //<--isKey??意味着整个rank从来不会更新,res永远是空
|
||
// if(res) {
|
||
// data = G.mongodb.conversionIdObj(res)
|
||
// this.setRankData(idKey, data.data)
|
||
// }
|
||
// }
|
||
// if(data) return data
|
||
// return undefined || {}
|
||
}
|
||
|
||
/**
|
||
* 获取单个用户的排序分数
|
||
* @param uid
|
||
* @returns
|
||
*/
|
||
async getRankScore(uid: string): Promise<number> {
|
||
let score = await G.redis.zScore(this.getRedisKeySort, uid)
|
||
return score || 0
|
||
}
|
||
|
||
/**
|
||
* 从redis中获取单个用户的排名 *降序
|
||
* @param uid
|
||
* @returns
|
||
*/
|
||
async getRankSortByOne(uid: string): Promise<number> {
|
||
let rank = await G.redis.zRevRank(this.getRedisKeySort, uid)
|
||
return rank === 0 || rank > 0 ? rank : -1;
|
||
}
|
||
|
||
// 从redis中获取单个指定排名的用户数据 *降序
|
||
async getRankSortDataByOne(rank: number): Promise<rankInfo> {
|
||
let rankList: rankInfo[] = await this.getRankListRange(rank, rank + 1)
|
||
let rankInfo = rankList && rankList.length > 0 ? rankList[0] : {} as rankInfo
|
||
return rankInfo
|
||
}
|
||
|
||
// 获取排名长度
|
||
// async getRankLen() {
|
||
// return await G.redis.hLen(this.getRedisKey)
|
||
// }
|
||
|
||
/**
|
||
* 获取指定排名范围的数据 *降序
|
||
* @param min
|
||
* @param max
|
||
* @returns
|
||
*/
|
||
async getRankListRange(min: number = 0, max: number = 50): Promise<rankInfo[]> {
|
||
let uids = await this.getRankListIdKeyRange(min, max)
|
||
if (uids && uids.length > 0) {
|
||
let res = await this.db.find({ idKey: { $in: uids }, type: this.getType() }).toArray()
|
||
|
||
switch (this.getType()) {
|
||
case "slzd1":
|
||
case "slzd2":
|
||
case "slzd3":
|
||
case "slzd4":
|
||
case "slzd5":
|
||
case "slzd6":
|
||
let ghid = [];
|
||
res = res.map(item => {
|
||
if (!item.data?.player?.ghid || item.data.utime + 60 < G.time) {
|
||
ghid.push(G.mongodb.conversionId(item.idKey));
|
||
}
|
||
return item;
|
||
})
|
||
if(ghid.length > 0){
|
||
let ghinfo = await G.mongodb.collection("gonghui").find(
|
||
{ _id: { $in: ghid } }, { projection: { name: 1 } }
|
||
).toArray();
|
||
ghinfo.forEach(item => {
|
||
let index = res.findIndex(x => x.idKey == item._id.toHexString());
|
||
res[index].data.player = {
|
||
ghName: item.name,
|
||
ghId: item._id.toHexString(),
|
||
};
|
||
this.db.updateOne({ idKey: item._id.toHexString(), type: this.getType() }, { $set: { "data.player": res[index].data.player } });
|
||
})
|
||
}
|
||
break;
|
||
default: // 排行数据更新逻辑 默认更新playerInfo
|
||
let updateUids = [];
|
||
res = res.map(item => {
|
||
// 没有player 或者 player 过期
|
||
if (!item.data?.player || item.data.utime + 60 < G.time) {
|
||
updateUids.push(item.idKey);
|
||
}
|
||
return item;
|
||
});
|
||
let newUserArr = await G.mongodb.collection('user').find(
|
||
{ uid: { $in: updateUids } }, { projection: { _id: 0 } }
|
||
).toArray();
|
||
|
||
newUserArr.forEach(item => {
|
||
// 每次遍历查找?
|
||
let index = res.findIndex(x => x.idKey == item.uid);
|
||
|
||
res[index].data.player = item;
|
||
this.db.updateOne({ idKey: item.uid, type: this.getType() }, { $set: { "data.player": item } });
|
||
|
||
// 跟新redis score
|
||
// this.setRankData(item.uid, res[index].data as any);
|
||
})
|
||
}
|
||
|
||
return res.map(ele => ele.data).sort(this.compareSort) as any;
|
||
}
|
||
return []
|
||
}
|
||
|
||
/**
|
||
* 从redis中获取指定排名范围的uid集合 *降序
|
||
* @param min
|
||
* @param max
|
||
* @returns uid集合数组
|
||
*/
|
||
async getRankListIdKeyRange(min: number = 0, max: number = 50): Promise<string[]> {
|
||
let uids = await G.redis.zRevRange(this.getRedisKeySort, min, max - 1)
|
||
return uids || []
|
||
}
|
||
|
||
/**
|
||
* 获取指定类型的全部rank列表,返回为积分排序后的数组
|
||
* @returns
|
||
*/
|
||
async getRankListAll(): Promise<rankInfo[]> {
|
||
// let res = await G.redis.hGetAll(this.getRedisKey)
|
||
let res = (await this.db.find({ type: this.getType() }).toArray()).map(ele => ele.data);
|
||
if (res) {
|
||
// 如果是用户数据,则比对utime,更新数据
|
||
// res = await this.checkData(res)
|
||
return res.sort(this.compareSort)
|
||
}
|
||
return []
|
||
}
|
||
|
||
/**
|
||
* 按排名获取全部的idKey
|
||
* @returns
|
||
*/
|
||
async getRankListIdKeyAll(): Promise<string[]> {
|
||
let res = this.getRankListIdKeyRange(0, -1)
|
||
return res
|
||
}
|
||
|
||
// 验证数据的过期时间,更新数据
|
||
// async checkData(rankList: rankInfo[]): Promise<rankInfo[]> {
|
||
// let updateUid = []
|
||
// rankList.forEach((value,key) => {
|
||
// // 仅针对用户表,公会表暂不考虑
|
||
// if(rankList[key].player?.uid && (rankList[key].utime || 0) < (G.time - this.utimeTTL)) {
|
||
// // 更新数据
|
||
// updateUid.push(rankList[key].player.uid)
|
||
// }
|
||
// })
|
||
// if(updateUid.length > 0) {
|
||
// let newUserArr = await G.mongodb.collection('user').find({uid:{$in: updateUid}}).toArray()
|
||
// // let newUserArr = await G.redis.gets('user', ...updateUid.map(uid => [uid] as [string]))
|
||
// // let newUserArr = await G.mongodb.collection('user').find({uid:{$in: updateUid}}).toArray()
|
||
// newUserArr.forEach(item => {
|
||
// let index = rankList.findIndex( x => x.player.uid == item.uid);
|
||
// rankList[index].player = item;
|
||
// this.setRankData(item.uid, rankList[index]);
|
||
// })
|
||
// }
|
||
// return rankList
|
||
// }
|
||
|
||
|
||
/**
|
||
* 删除指定idk的排名数据
|
||
* @param idKey
|
||
* @returns
|
||
*/
|
||
async delRankData(idKey: string) {
|
||
// G.redis.hDel(this.getRedisKey, idKey)
|
||
G.redis.zRem(this.getRedisKeySort, idKey);
|
||
this.db.updateOne({ idKey: idKey, type: this.getType() }, {
|
||
$set: { idKey: `del_${idKey}` }
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 原逻辑前50(countMaxNum)名才更新数据(上榜),多余的数据会删除。
|
||
* @param info
|
||
* @returns
|
||
*/
|
||
async addNew(info: rankInfo) {
|
||
//this.queue.enqueue(async () => {
|
||
await this.setRankData(info.player[this.findKey], info)
|
||
await this.db.updateOne({ type: this.type, idKey: info.player[this.findKey] }, { $set: { data: info } }, { upsert: true });
|
||
// 删除第50名以后的数据,(排名从0开始计算)
|
||
// let idKeys:string[] = await this.getRankListIdKeyRange(50, -1)
|
||
// idKeys.forEach(idKey => {
|
||
// this.db.deleteOne({ type: this.type, idKey: idKey });
|
||
// this.delRankData(idKey)
|
||
// })
|
||
//});
|
||
}
|
||
|
||
|
||
// 清空相关rank数据
|
||
async clear() {
|
||
this.queue.enqueue(async () => {
|
||
// G.redis.rawDel(this.getRedisKey)
|
||
G.redis.rawDel(this.getRedisKeySort);
|
||
await this.db.deleteMany({ type: this.type });
|
||
});
|
||
}
|
||
} |