HJ_Server/src/public/rank/rank.ts
xcy 381aeb1792 fix:
丛林猎手优化
2024-01-01 09:04:08 +08:00

414 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(param?: any) {
// 将param属性赋值给this
param && Object.assign(this, param);
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}
// 首先清理redis中sort数据 在从数据库中初始化
await G.ioredis.del(this.getRedisKeySort);
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()
if (this.type.indexOf("slzd") != -1) {
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 } });
})
}
} else if (this.type.indexOf('clslCross') != -1) {
// 丛林猎手 跨服排行榜 不需要更新
} else {
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);
})
}
// 按照redis uids 排序顺序排序
return res.sort((a, b) => uids.indexOf(a.idKey) - uids.indexOf(b.idKey)).map(ele => ele.data);
}
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);
this.db.deleteMany({ type: this.type });
// });
}
}