Files
ZYZ/game-server/app/services/redisService.ts
T

654 lines
22 KiB
TypeScript

import { GOOD_QUALITY, REDIS_RANK_TO_INFO, COM_BTL_QUALITY } from './../consts';
import { RoleModel, RoleType } from "../db/Role";
import * as Redis from 'redis';
import {REDIS_KEY} from '../consts'
import { GameModel } from "../db/Game";
import { promisifyAll } from 'bluebird';
import { pinus } from 'pinus';
import { PvpDefenseModel } from '../db/PvpDefense';
import { SystemConfigModel } from '../db/SystemConfig';
import { GuildRankParam, GuildLeader, RankParam } from '../domain/rank';
import { GuildModel } from '../db/Guild';
import { comBtlRanges } from '../pubUtils/gamedata';
import { getNextHourPoint } from '../pubUtils/timeUtil';
/**
* 在服务重新启动时,将信息存入redis
*/
export async function initAllRank() {
const client: Redis.RedisClient = redisClient();
const serverList = await GameModel.getAllServerList();
await client.delAsync(REDIS_KEY.ONLINE_USERS);
await client.delAsync(REDIS_KEY.USER_INFO);
await client.delAsync(REDIS_KEY.GUILD_INFO);
await client.delAsync(REDIS_KEY.PVP_RANK);
for(let {id} of serverList) {
await client.delAsync(getKeyName(REDIS_KEY.TOWER_RANK, id));
await client.delAsync(getKeyName(REDIS_KEY.GUILD_ACTIVE_RANK, id));
await initRank(id);
}
}
/**
* 分服内初始redis排行榜
*
* @param serverId 服务器
*/
export async function initRank(serverId: number) {
// console.log('*****', 'initRank')
const client: Redis.RedisClient = redisClient();
await client.expireAsync(getKeyName(REDIS_KEY.TOWER_RANK, serverId), 30 * 24 * 60 * 60);
await client.expireAsync(REDIS_KEY.PVP_RANK, 30 * 24 * 60 * 60);
await client.expireAsync(REDIS_KEY.USER_INFO, 30 * 24 * 60 * 60);
await client.expireAsync(REDIS_KEY.GUILD_INFO, 30 * 24 * 60 * 60);
await client.expireAsync(getKeyName(REDIS_KEY.GUILD_ACTIVE_RANK, serverId), 30 * 24 * 60 * 60);
await setRankRedisFromDb(REDIS_KEY.TOWER_RANK, serverId);
await setRankRedisFromDb(REDIS_KEY.GUILD_ACTIVE_RANK, serverId);
}
/**
* 初始化某一个排行榜,如定时器内
* @param key redis内的key
*/
export async function initSingleRank(key: string) {
const serverList = await GameModel.getAllServerList();
for(let {id} of serverList) {
await initSingleRankWithServer(key, id);
}
}
/**
* 初始化某一服内的某一种排行榜
* @param key redis key
*/
export async function initSingleRankWithServer(key: string, serverId: number) {
await redisClient().delAsync(getKeyName(key, serverId));
await redisClient().expireAsync(key, 30 * 24 * 60 * 60);
await setRankRedisFromDb(key, serverId);
}
/**
* 从数据库内获取排行榜存入redis
* @param type 排行榜类型
* @param serverId 分服
*/
async function setRankRedisFromDb(type: string, serverId: number) {
if(type == REDIS_KEY.TOWER_RANK) {
let ranks = await RoleModel.getRank('tower', serverId, ['roleId', 'roleName', 'towerLv', 'lv', 'vLv', 'head', 'frame', 'spine','title', 'updatedAt']);
for(let {towerLv, roleId, roleName, lv, vLv, towerUpTime, head, frame, spine, title} of ranks) {
// console.log(roleId);
await redisClient().zaddAsync(getKeyName(REDIS_KEY.TOWER_RANK, serverId), encodeScoreWithTime(towerLv, towerUpTime?towerUpTime.getTime():0), roleId);
let rankPram = new RankParam(roleName, lv, vLv, head, frame, spine, title);
await redisUserInfoAdd(REDIS_KEY.USER_INFO, roleId, rankPram);
}
} else if (type == REDIS_KEY.GUILD_ACTIVE_RANK) {
let ranks = await GuildModel.getRank(serverId);
for(let { code, icon, name, lv, leader, activeWeekly = 0, activeUpdateTime = 0 } of ranks) {
let _leader = <RoleType>leader;
let { roleName, title, head, frame, spine, lv: leaderLv } = _leader;
await redisClient().zaddAsync(getKeyName(REDIS_KEY.GUILD_ACTIVE_RANK, serverId), encodeScoreWithTime(activeWeekly, activeUpdateTime * 1000 ), code);
let rankParam = new GuildRankParam(icon, name, lv, { roleName, title, head, frame, spine, lv: leaderLv });
await redisUserInfoAdd(REDIS_KEY.GUILD_INFO, code, rankParam);
}
}
}
// 排行榜是否存在
export async function existsRank(key: string, serverId: number) {
const result = await redisClient().existsAsync(getKeyName(key, serverId));
return result;
}
/**
* 获得redis key的名字
* @param key REDIS_KEY中配置的key
* @param serverId 服务器id
* @param plus 后面再加
*/
export function getKeyName(key: string, serverId?: number, plus: string = '') {
let newKey = '';
if(serverId) {
newKey = `${key}:${serverId}`;
} else {
newKey = key;
}
if(plus) {
newKey += `:${plus}`;
}
return newKey;
}
// 更新玩家信息
export async function redisUserInfoUpdate(key: string, roleId: string, arr: Array<{field: string, value:(string|number|GuildLeader)}>) {
let params = await redisClient().hgetAsync(key, roleId);
if(params) {
let obj = JSON.parse(params);
for(let {field, value} of arr) {
obj[field] = value;
}
return await redisClient().hsetAsync(key, roleId, JSON.stringify(obj));
}
}
// 添加玩家信息缓存
export async function redisUserInfoAdd(key: string, roleId: string, params: RankParam|GuildRankParam) {
let value = JSON.stringify(params);
return await redisClient().hsetAsync(key, roleId, value);
}
/**
* 更新排行榜
* @param key 配置在REDIS_KEY中的key
* @param serverId 区服id 如pvp这样跨服的,serverId传0
* @param myId 玩家roleId或军团code
* @param score 得分
* @param timestamp 时间 13位时间戳
* @param params 玩家数据
* @param isAtom 是否是原子性的更新
* @param limit
*/
export async function setRank(key: string, serverId: number, myId: string, score: number, timestamp: number, params: RankParam|GuildRankParam, isAtom = false, limit = 100) {
// 更新分数
let newScore = score;
if(isAtom) {
newScore = await updateRankAtom(key, serverId, myId, score, timestamp);
} else {
const _score = encodeScoreWithTime(score, timestamp);
await redisClient().zaddAsync(getKeyName(key, serverId), _score, myId);
}
// 移除100名以外
await redisClient().zremrangebyrankAsync(getKeyName(key, serverId), limit, 10000);
let infoKey = REDIS_RANK_TO_INFO.get(key)||REDIS_KEY.USER_INFO;
// 如果没有信息,更新玩家信息
const hasCurUser = await redisClient().hexistsAsync(infoKey, myId);
if(!hasCurUser) {
await redisUserInfoAdd(infoKey, myId, params);
}
return newScore;
}
// 获取排行榜
export async function getRank(key: string, serverId: number, roleId: string, limit = 100) {
let ranks = [], myRank = null;
const rankFromDb = await redisClient().zrevrangebyscoreAsync(getKeyName(key, serverId), '+inf', '-inf', "WITHSCORES", "LIMIT", 0, limit);
let infoKey = REDIS_RANK_TO_INFO.get(key)||REDIS_KEY.USER_INFO;
for(let ii = 0; ii < rankFromDb.length; ii+=2) {
const _roleId = rankFromDb[ii];
const _score = decodeScoreWithTime(rankFromDb[ii + 1]);
const info = await redisClient().hgetAsync(infoKey, _roleId);
const _userInfo = JSON.parse(info);
const tmp = {..._userInfo, num: _score, rank: Math.floor(ii/2)+1};
if(infoKey == REDIS_KEY.USER_INFO) {
tmp["roleId"] = _roleId;
} else if(infoKey == REDIS_KEY.GUILD_INFO) {
tmp["code"] = _roleId;
}
ranks.push(tmp);
if(roleId == _roleId) myRank = tmp;
}
return {ranks, myRank}
}
// 获取我的排名
export async function getMyRank(key: string, serverId: number, roleId: string) {
let myRank = await redisClient().zrevrankAsync(getKeyName(key, serverId), roleId);
return myRank + 1;
}
// 获取排名第几名的信息
export async function getFieldByRank(key: string, serverId: number, rank: number) {
let myRank = await redisClient().zrevrangeAsync(getKeyName(key, serverId), rank - 1, rank - 1);
return myRank;
}
// 有序排行综合时间和得分排序
function encodeScoreWithTime(score: number, timestamp: number): number {
// value = score * Math.power(10, 14) + max_time - timestamp
let timelen = 10;
let pow = Math.pow(10, timelen + 1);
return score * pow + pow - 1 - Math.floor(timestamp/1000)
}
function decodeScoreWithTime(num: string): number {
let timelen = 10;
let pow = Math.pow(10, timelen + 1);
let _num = parseInt(num);
return Math.floor(_num/pow);
}
// 从排行榜中移除
export async function removeFromRank(key: string, serverId: number, myId: string) {
await redisClient().zremAsync(getKeyName(key, serverId), myId);
return true;
}
/**************** 寻宝相关 start */
/**
* @description 拼接匹配分组的 key
* @param {number} quality 品质
* @param {number} lvRange 等级范围
*/
function getComTeamKey(quality: number, lvRange: number) {
return `${REDIS_KEY.COM_TEAM_SEARCH_PRE}:${quality}_${lvRange}`;
}
/**
* @description 拼接匹配分组中 member 的值
* @param {string} roleId
* @param {string} sid connector serverId
* @returns
*/
function getComTeamValue(roleId: string, sid: string) {
return `${roleId}:${sid}`;
}
/**
* @description 把寻宝的玩家信息存入 redis
* @export
* @param {string} roleId
* @param {string} sid
* @param {Array<number>} qualityArr
* @param {number} lvRange
* @returns
*/
export async function setTeamSearchReq(roleId: string, sid: string, qualityArr: Array<number>, lvRange: number) {
let cmds = [];
qualityArr.forEach(quality => {
if (quality) {
cmds.push(['sadd', getComTeamKey(quality, lvRange), getComTeamValue(roleId, sid)]);
}
});
const multiClient = redisClient().multi(cmds) as Redis.Multi;
const newMulti = promisifyAll(multiClient) as Redis.Multi;
const res = await newMulti.execAsync();
console.log('setTeamSearchReq: ', res);
return res;
}
/**
* @description 从匹配队列中删除某个用户
* @export
* @param {string} roleId
* @param {string} sid
* @param {Array<number>} qualityArr
* @param {number} lvRange
*/
export async function rmRoleFromQueue(roleId: string, sid: string, qualityArr: Array<number>, lvRange: number) {
let cmds = [];
for (let q of qualityArr) {
if (lvRange) {
cmds.push(['srem', getComTeamKey(q, lvRange), getComTeamValue(roleId, sid)]);
} else {
for (let range of comBtlRanges()) {
cmds.push(['srem', getComTeamKey(q, range), getComTeamValue(roleId, sid)]);
}
}
};
const multiClient = redisClient().multi(cmds) as Redis.Multi;
const newMulti = promisifyAll(multiClient) as Redis.Multi;
await newMulti.execAsync();
}
/**
* @description 在寻宝匹配队列中随机两个玩家
* @export
* @param {number} quality 队伍品质
* @param {number} lvRange 等级范围
* @returns
*/
export async function getTeamSearchByQuality(quality: number, lvRange: number) {
// TODO: 操作不具有原子性
const userInfos = await redisClient().srandmemberAsync(getComTeamKey(quality, lvRange), 2);
console.log('getTeamSearchByQuality: ' + userInfos);
if (!userInfos || !userInfos.length) return null;
let res = [];
for (let userInfo of userInfos) {
const decodeData = `${userInfo}`.split(':');
if (decodeData.length !== 2) return null;
res.push({roleId: decodeData[0], sid: decodeData[1]});
}
console.log('getTeamSearchByQuality res: ', res);
return res;
}
/**
* @description 检查玩家是否在某个队列中等待寻宝匹配
* @export
* @param {string} roleId
* @param {string} sid
* @param {Array<number>} qualityArr
* @param {number} lvRange
* @returns
*/
export async function checkRoleInQueue(roleId: string, sid: string, qualityArr: Array<number>, lvRange: number) {
for (let quality of qualityArr) {
let res = await redisClient().sismemberAsync(getComTeamKey(quality, lvRange), `${roleId}:${sid}`);
if (res) {
return true;
}
};
return false;
}
/**
* @description 清除所有的寻宝匹配队列
* @export
*/
export async function clearComBtlQueue() {
for (let q of COM_BTL_QUALITY) {
for (let lvRange of comBtlRanges()) {
await redisClient().delAsync(getComTeamKey(q, lvRange));
}
}
}
export function setRedis(key: string, data: string) {
redisClient().setAsync(key, data);
}
export async function getRedis(key: string) {
const str = await redisClient().getAsync(key);
return str;
}
export async function delRedis(key: string) {
await redisClient().delAsync(key);
}
export function redisSidKey(roleId: string) {
return `login_roleId_${roleId}`;
}
export async function redisChannelServer(roomId: string) {
const sid = await redisClient().hgetAsync(REDIS_KEY.CHANNEL_SERVERS, roomId);
return sid;
}
export async function addRedisChannel(roomId: string, sid: string) {
const result = await redisClient().hsetAsync(REDIS_KEY.CHANNEL_SERVERS, roomId, sid);
return result;
}
export async function clearChannelServers() {
const result = await redisClient().delAsync(REDIS_KEY.CHANNEL_SERVERS);
return result;
}
/**
* 玩家上线
* @param roleId role表id
* @param userCode user表唯一字符串标识
* @param sid connector服的那个sid
*/
export async function roleLogin(roleId: string, userCode: string, sid: string, pkgName: string) {
let param = { userCode, sid, pkgName };
return await redisClient().hsetAsync(REDIS_KEY.ONLINE_USERS, roleId, JSON.stringify(param));
}
/**
* 玩家下线
* @param roleId role表id
*/
export async function roleLeave(roleId: string) {
let role = await getRoleOnlineInfo(roleId);
await redisClient().hdelAsync(REDIS_KEY.ONLINE_USERS, roleId);
return role;
}
/**
* 判断玩家是否在线
* @param roleId role表id
*/
export async function isRoleOnline(roleId: string) {
let result = await redisClient().hexistsAsync(REDIS_KEY.ONLINE_USERS, roleId);
return !!result;
}
/**
* 获得在线玩家userCode和sid
* @param roleId
*/
export async function getRoleOnlineInfo(roleId: string) {
let str = await redisClient().hgetAsync(REDIS_KEY.ONLINE_USERS, roleId);
if(str) {
try {
let result = JSON.parse(str);
return {
isOnline: true,
userCode: result.userCode,
sid: result.sid,
pkgName: result.pkgName
}
} catch(e) {
return { isOnline: false }
}
} else {
return { isOnline: false }
}
}
/**
* 获得所有在线的玩家
*/
export async function getAllOnlineRoles() {
const client: Redis.RedisClient = pinus.app.get('redis');
let allRoles = await redisClient().hgetallAsync(REDIS_KEY.ONLINE_USERS);
let result = new Array<{roleId: string, userCode: string, sid: string, pkgName: string}>();
for(let roleId in allRoles) {
try{
let param = JSON.parse(allRoles[roleId]);
if(param) {
result.push({ roleId, userCode: param.userCode, sid: param.sid, pkgName: param.pkgName });
}
} catch(e) {
continue;
}
}
return result;
}
export async function resetPvpRanks() {
await redisClient().delAsync(REDIS_KEY.PVP_RANK);
let { seasonNum } = await SystemConfigModel.findSystemConfig();
console.log('execute season resetPvpRanks seasonNum = ' + seasonNum);
let pvpRank = await PvpDefenseModel.getRank(seasonNum);//获得全服前1000名的排名,加入到redis中
for(let {roleId, role: _role, score, updatedAt } of pvpRank) {
let role = <RoleType>_role;
if (!role) {
continue;
}
let { roleName, head, frame, spine, title, lv, vLv } = role;
await redisClient().zaddAsync(getKeyName(REDIS_KEY.PVP_RANK), encodeScoreWithTime(score, updatedAt?updatedAt.getTime():0), roleId);
const hasCurUser = await redisClient().hexistsAsync(REDIS_KEY.USER_INFO, roleId);
if(!hasCurUser) {
let rankPram = new RankParam(roleName, lv, vLv, head, frame, spine, title);
await redisUserInfoAdd(REDIS_KEY.USER_INFO, roleId, rankPram);
}
}
}
// 排行榜是否存在
export async function smembersAsync(key: string) {
const result = await redisClient().smembersAsync(key);
return result;
}
export async function saddAsync(key: string, values: Array<string>) {
const result = await redisClient().saddAsync(key, values);
return result;
}
export async function sismemberAsync(key: string, value: string) {
const result = await redisClient().sismemberAsync(key, value);
return result;
}
export async function delAsync(key:string) {
await redisClient().delAsync(key);
}
export async function sremAsync(key:string, member:string) {
await redisClient().sremAsync(key, member);
}
/**************** 寻宝相关 end */
/**************** 数据库表存入缓存 */
export async function readDataBase() {
await setServerList();
}
async function setServerList() {
const serverList = await GameModel.getAllServerList();
await redisClient().delAsync(REDIS_KEY.DB_GAME);
for(let { id, serverType, name } of serverList) {
// console.log(roleId);
await redisClient().hsetAsync(REDIS_KEY.DB_GAME, `${serverType}_${id}`, name);
}
}
export async function getAllServers() {
let servers = await redisClient().hgetallAsync(REDIS_KEY.DB_GAME);
let serverlist = new Array<number>();
for(let serverStr in servers) {
let arr = serverStr.split('_');
let serverId = parseInt(arr[1]);
if(!isNaN(serverId) && !serverlist.includes(serverId)) {
serverlist.push(serverId);
}
}
return serverlist;
}
export async function getServerName(serverType: string, serverId: number) {
let name = await redisClient().hgetAsync(REDIS_KEY.DB_GAME, `${serverType}_${serverId}`);
return name
}
function redisClient() {
const client: Redis.RedisClient = pinus.app.get('redis');
return client;
}
/**************** 数据库表end */
/**************** 军团活动排行 */
/**
* 更新排行榜(将得分和时间拆分开)
* @param key 配置在REDIS_KEY中的key
* @param serverId 区服id
* @param field 玩家id/军团id
* @param score 得分
* @param time 事件
*/
async function updateRankAtom(key: string, serverId: number, field: string, score: number, timestamp: number) {
let originKey = getKeyName(key, serverId);
let timeKey = getKeyName(key, serverId, 'time');
await redisClient().expireatAsync(originKey, getNextHourPoint(5));
await redisClient().expireatAsync(timeKey, getNextHourPoint(5));
let timelen = 10;
let pow = Math.pow(10, timelen + 1);
let newScore = await redisClient().zincrbyAsync(originKey, score, field);
await redisClient().zaddAsync(timeKey, pow - 1 - Math.floor(timestamp/1000), field);
return newScore;
}
async function generateUnionRank(key: string, serverId: number) {
let unionKey = getKeyName(key, serverId, 'union'); // 联合的key
let existsKey = await redisClient().existsAsync(unionKey);
if(!existsKey) {
await redisClient().expireatAsync(unionKey, 10); // 10秒更新一次
let originKey = getKeyName(key, serverId);
let timeKey = getKeyName(key, serverId, 'time');
let timelen = 10;
let pow = Math.pow(10, timelen + 1);
await redisClient().zunionstoreAsync(unionKey, 2, originKey, timeKey, 'WEIGHTS', pow, 1);
}
return unionKey;
}
/**
* 使用updateRankAtom更新的排行榜,使用这个方法获取列表
* @param key 配置在REDIS_KEY中的key
* @param serverId 区服id
* @param roleId 自己的id
*/
export async function getUnionRank(key: string, serverId: number, roleId: string, limit = 100) {
let ranks = [], myRank = null;
let unionKey = await generateUnionRank(key, serverId);
const rankFromDb = await redisClient().zrevrangebyscoreAsync(unionKey, '+inf', '-inf', "WITHSCORES", "LIMIT", 0, limit);
let infoKey = REDIS_RANK_TO_INFO.get(key)||REDIS_KEY.USER_INFO;
for(let ii = 0; ii < rankFromDb.length; ii+=2) {
const _roleId = rankFromDb[ii];
const _score = decodeScoreWithTime(rankFromDb[ii + 1]);
const info = await redisClient().hgetAsync(infoKey, _roleId);
const _userInfo = JSON.parse(info);
const tmp = {..._userInfo, num: _score, rank: Math.floor(ii/2)+1};
if(infoKey == REDIS_KEY.USER_INFO) {
tmp["roleId"] = _roleId;
} else if(infoKey == REDIS_KEY.GUILD_INFO) {
tmp["code"] = _roleId;
}
ranks.push(tmp);
if(roleId == _roleId) myRank = tmp;
}
return {ranks, myRank}
}
// 获取我的排名
export async function getMyUnionRank(key: string, serverId: number, roleId: string) {
let unionKey = await generateUnionRank(key, serverId);
let myRank = await redisClient().zrevrankAsync(unionKey, roleId);
return myRank + 1;
}
/**
* 获取拼接得分的排行榜的某一个人的得分
* @param key REDIS_KEY中配置的
* @param serverId 分服
* @param field 查询的人
*/
export async function getRankScore(key: string, serverId: number, field: string, needDecode = false) {
let score = await redisClient().zscoreAsync(getKeyName(key, serverId), field);
if(!score) score = 0;
if(needDecode) {
score = decodeScoreWithTime(score.toString());
}
return score;
}
/**
* 按军团名拼接key
* @param key
* @param guildCode 军团编号
*/
export function getGuildKeyName(key: string, guildCode: string) {
return `${key}:${guildCode}`;
}
/**************** 军团活动排行end */