Files
ZYZ/game-server/app/services/redisService.ts
2020-12-15 16:02:59 +08:00

283 lines
9.9 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 { GOOD_QUALITY } from './../consts';
import { RoleModel } from "../db/Role";
import * as Redis from 'redis';
import {REDIS_KEY} from '../consts'
import { GameModel } from "../db/Game";
import { promisifyAll } from 'bluebird';
// 映射 redis 接口
declare module 'redis' {
export interface RedisClient extends NodeJS.EventEmitter {
hdelAsync(...args: any[]): Promise<any>;
spopAsync(...args: any[]): Promise<any>;
sremAsync(...args: any[]): Promise<any>;
sismemberAsync(...args: any[]): Promise<any>;
srandmemberAsync(...args: any[]): Promise<any>;
delAsync(...args: any[]): Promise<any>;
}
export interface Multi extends Commands<Multi> {
execAsync(...args: any[]): Promise<any>;
}
}
let redisArr = 'r-8vb4i2kgl91886fkxd.redis.zhangbei.rds.aliyuncs.com';
if(process.env.NODE_ENV == 'local') {
redisArr = '127.0.0.1';
}
// 创建 redis 连接
const oldRedisClient = Redis.createClient(6379, redisArr, {detect_buffers: true});
oldRedisClient.auth('zyz_2020', (err, reply) => {
if (err) {
console.log('redis err', err);
} else {
console.log('redis suc');
}
})
// 转 promise
const client = promisifyAll(oldRedisClient) as Redis.RedisClient;
client.set('hello', 'redis', Redis.print);
/**
* 在服务重新启动时将信息存入redis
*/
export async function initAllRank() {
const serverList = await GameModel.getAllServerList();
await redisDel(REDIS_KEY.USER_INFO);
for(let {id} of serverList) {
await redisDel(getKeyName(REDIS_KEY.TOWER_RANK, id));
await initRank(id);
}
}
export async function initRank(serverId: number) {
// console.log('*****', 'initRank')
await redisExpire(getKeyName(REDIS_KEY.TOWER_RANK, serverId), 30 * 24 * 60 * 60);
await redisExpire(REDIS_KEY.USER_INFO, 30 * 24 * 60 * 60);
let ranks = await RoleModel.getRank('tower', serverId, ['roleId', 'roleName', 'towerLv', 'lv', 'vLv']);
for(let {towerLv, roleId, roleName, lv, vLv, towerUpTime} of ranks) {
// console.log(roleId);
await redisZadd(getKeyName(REDIS_KEY.TOWER_RANK, serverId), encodeScoreWithTime(towerLv, towerUpTime?towerUpTime.getTime():0), roleId);
await redisUserInfoAdd(roleId, {roleName, lv, vLv, guildName:"", head: "zhaoyun"});
}
}
// 排行榜是否存在
export async function existsRank(key: string, serverId: number) {
const result = await redisExists(getKeyName(key, serverId));
return result;
}
export function getKeyName(key: string, serverId: number) {
return `${key}:${serverId}`;
}
// 更新玩家信息
export async function redisUserInfoUpdate(roleId: string, arr: Array<{field: string, value:(string|number)}>) {
let params = await redisHget(REDIS_KEY.USER_INFO, roleId);
if(params) {
let obj = JSON.parse(params);
for(let {field, value} of arr) {
obj[field] = value;
}
return await redisHset(REDIS_KEY.USER_INFO, roleId, JSON.stringify(obj));
}
}
// 添加玩家信息缓存
export async function redisUserInfoAdd(roleId: string, params: {roleName: string, lv: number, vLv: number, guildName: string, head: string}) {
let value = JSON.stringify(params);
return await redisHset(REDIS_KEY.USER_INFO, roleId, value);
}
// 更新排行榜
export async function setRank(key: string, serverId: number, roleId: string, score: number, timestamp: number, params: {roleName: string, lv: number, vLv: number, guildName: string, head: string}) {
// 更新分数
const _score = encodeScoreWithTime(score, timestamp);
await redisZadd(getKeyName(key, serverId), _score, roleId);
// 移除100名以外
await redisZremRangeByRank(getKeyName(key, serverId), 100, 10000);
// 如果没有信息,更新玩家信息
const hasCurUser = await redisHexists(REDIS_KEY.USER_INFO, roleId);
if(!hasCurUser) {
await redisUserInfoAdd(roleId, params);
}
return true;
}
// 获取排行榜
export async function getRank(key: string, serverId: number, roleId: string) {
let ranks = [], myRank = null;
const rankFromDb = await redisZrevrangeByScore(getKeyName(key, serverId), '+inf', '-inf', true, 100);
for(let ii = 0; ii < rankFromDb.length; ii+=2) {
const _roleId = rankFromDb[ii];
const _score = decodeScoreWithTime(rankFromDb[ii + 1]);
const userInfo = await redisHget(REDIS_KEY.USER_INFO, _roleId);
const _userInfo = JSON.parse(userInfo);
const tmp = {..._userInfo, roleId: _roleId, num: _score, rank: Math.floor(ii/2)+1}
ranks.push(tmp);
if(roleId == _roleId) myRank = tmp;
}
return {ranks, myRank}
}
// 有序排行综合时间和得分排序
function encodeScoreWithTime(score: number, timestamp: number): number {
// value = score * Math.power(10, 14) + max_time - timestamp
let timelen = 13;
let pow = Math.pow(10, timelen + 1);
return score * pow + pow - 1 - timestamp
}
function decodeScoreWithTime(num: string): number {
let timelen = 13;
let pow = Math.pow(10, timelen + 1);
let _num = parseInt(num);
return Math.floor(_num/pow);
}
/**************** 寻宝相关 start */
// 把寻宝的玩家信息存入 redis
export async function setTeamSearchReq(roleId: string, sid: string, qualityArr: Array<number>) {
let cmds = [];
qualityArr.forEach(quality => {
if (quality) {
cmds.push(['sadd', `${REDIS_KEY.COM_TEAM_SEARCH_PRE}${quality}`, `${roleId}:${sid}`]);
}
});
const multiClient = client.multi(cmds) as Redis.Multi;
const newMulti = promisifyAll(multiClient) as Redis.Multi;
const res = await newMulti.execAsync();
console.log('setTeamSearchReq: ', res);
return res;
}
// 取出 0 - 2 个某品质的匹配中玩家,并在其它品质中删除此玩家信息
export async function rmRoleFromQueue(roleId: string, sid: string, qualityArr: Array<number>) {
let cmds = [];
for (let q of qualityArr) {
cmds.push(['srem', `${REDIS_KEY.COM_TEAM_SEARCH_PRE}${q}`, `${roleId}:${sid}`]);
};
const multiClient = client.multi(cmds) as Redis.Multi;
const newMulti = promisifyAll(multiClient) as Redis.Multi;
await newMulti.execAsync();
}
export async function getTeamSearchByQuality(quality: number) {
// TODO: 操作不具有原子性
const userInfos = await client.srandmemberAsync(`${REDIS_KEY.COM_TEAM_SEARCH_PRE}${quality}`, 2);
console.log('getTeamSearchByQuality: ' + userInfos);
if (!userInfos || !userInfos.length) return null;
let cmds = [];
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]});
GOOD_QUALITY.forEach((q) => {
cmds.push(['srem', `${REDIS_KEY.COM_TEAM_SEARCH_PRE}${q}`, userInfo]);
})
const multiClient = client.multi(cmds) as Redis.Multi;
const newMulti = promisifyAll(multiClient) as Redis.Multi;
await newMulti.execAsync();
}
console.log('getTeamSearchByQuality res: ', res);
return res;
}
export async function checkRoleInQueue(roleId: string, sid: string, qualityArr: Array<number>) {
for (let quality of qualityArr) {
let res = await client.sismemberAsync(`${REDIS_KEY.COM_TEAM_SEARCH_PRE}${quality}`, `${roleId}:${sid}`);
if (res) {
return true;
}
};
return false;
}
export async function clearComBtlQueue() {
for (let q of GOOD_QUALITY) {
client.delAsync(`${REDIS_KEY.COM_TEAM_SEARCH_PRE}${q}`);
}
}
/**************** 寻宝相关 end */
////// redis方法
//// key
// key 是否存在。
export async function redisExists(key: string) {
return await createPromise('exists', [key]);
}
// 在 key 存在时删除 key。
export async function redisDel(key: string) {
return await createPromise('del', [key]);
}
// 设置过期时间
export async function redisExpire(key: string, seconds: number) {
return await createPromise('expire', [key, seconds]);
}
//// 有序集合
// 返回有序集中指定分数区间内的成员,分数从高到低排序
export async function redisZrevrangeByScore(key: string, max: (number|string), min: (number|string), withscores: boolean, limit?:number, offset=0): Promise<Array<string>> {
let param = [key, max, min];
if(withscores) param.push('WITHSCORES');
if(limit) param.push('LIMIT', offset, limit);
return await createPromise('zrevrangebyscore', param);
}
// 向有序集合添加一个或多个成员,或者更新已存在成员的分数
export async function redisZadd(key: string, field: number, value: string) {
return await createPromise('zadd', [key, field, value]);
}
// 获取有序集合的成员数
export async function redisZcard(key: string) {
return await createPromise('zcard', [key]);
}
// 移除有序集合中给定的排名区间的所有成员
export async function redisZremRangeByRank(key: string, start: number, stop: number) {
return await createPromise('zremrangebyrank', [key, start, stop]);
}
//// 哈希
// 将哈希表 key 中的字段 field 的值设为 value
export async function redisHset(key: string, field: string, value: string) {
return await createPromise('hset', [key, field, value]);
}
// 获取存储在哈希表中指定字段的值。
export async function redisHget(key: string, field: string) {
return await createPromise('hget', [key, field]);
}
// 获取存储在哈希表中指定字段的值。
export async function redisHexists(key: string, field: string) {
return await createPromise('hexists', [key, field]);
}
///// 封装promise
function createPromise(type: string, params: Array<string|number|any>):Promise<any> {
return new Promise((resolve, reject) => {
const cb = function(err: Error, result: any) {
if(err) {
reject(err);
} else {
resolve(result);
}
};
client[type].apply(client, [...params, cb]);
});
}