import { createClient, RedisClientType } from 'redis'; import { RedisCollections1, RedisCollections2, RedisCollectionsArr1, RedisCollectionsArr2, RedisCollectionsHkey1String, RedisCollectionsHkeyObj1, RedisCollectionsSortedObj1, sortEd } from './module/redis'; import { clusterRunOnce } from './clusterUtils'; const localKeys = [ 'hero', 'equip', 'shiwu', 'shop', 'email', 'gbtx', 'dxlt', 'fightLog', 'jjc', 'tanxian', 'chatPrivate', 'peijian' ]; const localVals: k_v = { fightLog: { jjc: {}, slzd: {}, kbzz: {}, ganhai: {}, } }; const crossKeys = ['fightLog']; const crossVals: k_v = { fightLog: { hbzbJfs: {}, hbzbZbs: {}, clsl: {} } }; export let redisClient : RedisClientType; export async function initRedis() { const proto = await import('@redis/json/dist/commands/index'); //@ts-ignore // proto.transformRedisJsonArgument = function (json: any) { // return JSON.stringify(json); // }; // proto.transformRedisJsonNullReply = function (json: string) { // if (json === null) return null; // const a = decode(encode(json, 'iso-8859-1'), 'utf-8'); // const x = JSON.parse(a); // return JSON.parse(decodeURIComponent(escape(json))); // }; console.log('connect redis ......'); redisClient = createClient({ url: G.argv.serverType == 'cross' ? G.config.crossRedisUrl : G.config.redisUrl }); await redisClient.connect(); if (G.config.dbIndex) await redisClient.select(G.config.dbIndex); G.redis = new _redis(redisClient as any); //启动前清理redis残余数据 clusterRunOnce(async () => { await clearRedis(); await setRedis(); }) if (G.config.cleanRedis) await redisClient.flushDb(); console.log('connect redis succ'); } async function clearRedis() { //现在redis的机制,只增不减,以下key,是会在getList等逻辑里重建的,因此 //每次重启时,将这些key清空,避免占用redis内存 await redisClient.del([ G.redis.fromatKey('email'), G.redis.fromatKey('jjc'), G.redis.fromatKey('user'), G.redis.fromatKey('item'), G.redis.fromatKey('hero'), G.redis.fromatKey('equip'), G.redis.fromatKey('shiwu'), //G.redis.fromatKey('gbtx'), //G.redis.fromatKey('dxlt'), ]) } export async function setRedis() { let keys = G.argv.serverType == 'cross' ? crossKeys : localKeys; let vals = G.argv.serverType == 'cross' ? crossVals : localVals; for (let key of keys) { if (await G.redis.type(key as any) == null) { await G.redis.set(key as any, {}); } if (vals[key]) { for (let val of Object.entries(vals[key])) { if (await G.redis.type(key as any, val[0]) == null) { await G.redis.set(key as any, val[0], val[1]); } } } } } export class _redis implements redisJsonFun { logMsg = ''; constructor(private redis: RedisClientType) { } log(str: string, pos: 'end' | 'start') { // if (pos == 'end') { // console.log('redis cmd:', this. , str); // this.logMsg = ''; // } else if (pos == 'start') { // this.logMsg = str; // } } fromatKey(key: string, state: 'zAdd' | 'zRange' | 'zRangeByScore' | 'zScore' | 'zRevRange' | 'zRem' | 'zRevRank' | 'zRank' | 'zCard' | 'hGet' | 'hmGet' | 'hGetAll' | 'hLen' | 'hSet' | 'hDel' | 'del' | 'numIncrBy' | 'type' | 'get' | 'set' | 'arrAppend' | 'arrPop' | 'arrLen' | 'arrInsert' | null) { let sid = G.config.serverId || 0; let fmtKey = `${G.config.projectName}_${G.argv.serverType == 'cross' ? `corss${sid}` : sid}_${key}`; state && this.log(state + ' ' + fmtKey, 'start'); return fmtKey; } /** * redis路径只能以字母、美元符号或下划线开头 */ formatPath(args: string[], log = true) { args = args.map(str => G.formatRedisKey(str)); let path = '.' + args.join('.'); log && this.log(path, 'end'); return path; } async hGet() { let _args = Array.from(arguments); let result = await this.redis.hGet(this.fromatKey(_args[0], 'hGet'), _args[1]); try{ result = JSON.parse(result) } catch (err) {} return result } async hmGet() { let _args = Array.from(arguments); let result = await this.redis.hmGet(this.fromatKey(_args[0], 'hmGet'), _args[1]); try{ for(let i in result) { result[i] = JSON.parse(result[i]) } } catch(err) {} return result } async rawGet(){ let _args = Array.from(arguments); return await this.redis.get(this.fromatKey(_args[0], 'get')) as any; } async rawDel(){ let _args = Array.from(arguments); return await this.redis.del(this.fromatKey(_args[0], 'del')) as any; } async rawSet(){ let _args = Array.from(arguments); return await this.redis.set(this.fromatKey(_args[0], 'set'), _args[1] as any, _args?.[2]||null ); } async hGetAll() { let _args = Array.from(arguments); let result = await this.redis.hGetAll(this.fromatKey(_args[0], 'hGetAll')); if(result) { for(let i in result) { try{ result[i] = JSON.parse(result[i]) } catch (err) {} } } return result } async hLen() { let _args = Array.from(arguments); let result = await this.redis.hLen(this.fromatKey(_args[0], 'hLen')); return result } async hSet() { let _args = Array.from(arguments); if(_args[2] instanceof Array || _args instanceof Object) _args[2] = JSON.stringify(_args[2]) return this.redis.hSet(this.fromatKey(_args[0], 'hSet'), _args[1], _args[2]); } async hDel() { let _args = Array.from(arguments); return this.redis.hDel(this.fromatKey(_args[0], 'hDel'), _args); } async zAdd() { let _args = Array.from(arguments); return this.redis.zAdd(this.fromatKey(_args[0], 'zAdd'), _args[1]); } async zCard () { let _args = Array.from(arguments); return this.redis.zCard(this.fromatKey(_args[0], 'zAdd')); } /** * 获取指定索引范围排名 * redis参数 * ${type} * ${min} * ${max} */ async zRange() { let _args = Array.from(arguments); return await this.redis.zRange(this.fromatKey(_args[0], 'zRange'), _args[1], _args[2]) } async zRevRange() { let _args = Array.from(arguments); //zRange在默认设定里,相同的score时,会将后插入的排在前面,而排行榜里,是期望先插入的排在前面 //所以这里需要特殊处理 let res = await this.redis.zRange(this.fromatKey(_args[0], 'zRange'), _args[1], _args[2], {REV: true}) // let value = await this.redis.zRangeWithScores(this.fromatKey(_args[0], 'zRange'), _args[1], _args[2], {REV: true}); // //自己排序,只有分数>时才交换排名,实现相同分数时不交换 // value.sort((a,b)=>{ // return a.score > b.score ? -1 : 1 // }); // let res = []; // value.forEach(v=>{res.push(v.value)}) return res } /** * 获取指定key排名 * redis参数 * ${type} * ${key} */ async zRank() { let _args = Array.from(arguments); return await this.redis.zRank(this.fromatKey(_args[0], 'zRank'), _args[1]) } async zScore() { let _args = Array.from(arguments); return await this.redis.zScore(this.fromatKey(_args[0], 'zScore'), _args[1]) } /**获取指定key排名,倒叙 */ async zRevRank() { let _args = Array.from(arguments); return await this.redis.zRevRank(this.fromatKey(_args[0], 'zRevRank'), _args[1]); } /**删除指定key的排名数据 */ async zRem() { let _args = Array.from(arguments); return await this.redis.zRem(this.fromatKey(_args[0], 'zRem'), _args[1]); } async get() { if (await this.type(...arguments) == null) return null; let _args = Array.from(arguments); return await this.redis.json.get(this.fromatKey(_args[0], 'get'), { path: this.formatPath(_args.slice(1)) }) as any; } async gets() { let key = arguments[0]; let _args = Array.from(arguments); let dataObj = {}; if (_args.length <= 1) return []; if (key == 'user' && _args[1].length == 1) { return (await G.mongodb.collection('user').find({ uid: { $in: _args.slice(1).map(s => s[0]) } }).toArray()) as any; } for (let arg of _args.slice(1)) { let arr = arg; let path = this.formatPath(arr, false); dataObj[path] = await this.type(key, ...arr) == null ? null : true; } let paths = Object.keys(dataObj).filter(k => dataObj[k] == true); if (paths.length > 0) { let res = await this.redis.json.get(this.fromatKey(key, null), { path: paths }); if (paths.length == 1) { dataObj[paths[0]] = res; } else { Object.assign(dataObj, res); } } return Object.values(dataObj); } async set() { let _args = Array.from(arguments); return await this.redis.json.set(this.fromatKey(_args[0], 'set'), this.formatPath(_args.slice(1, _args.length - 1)), _args[_args.length - 1]); } async del() { let _args = Array.from(arguments); return await this.redis.json.del(this.fromatKey(_args[0], 'del'), this.formatPath(_args.slice(1, _args.length))); } async type(...args: any[]) { let _args = Array.from(arguments); return await this.redis.json.type(this.fromatKey(_args[0], 'type'), this.formatPath(_args.slice(1))) as any; } async numIncrBy() { let _args = Array.from(arguments); return await this.redis.json.numIncrBy(this.fromatKey(_args[0], 'numIncrBy'), this.formatPath(_args.slice(1, _args.length - 1)), _args[_args.length - 1]) as number; } async arrLen() { let _args = Array.from(arguments); return await this.redis.json.arrLen(this.fromatKey(_args[0], 'arrLen'), this.formatPath(_args.slice(1))) as any; } async arrPop() { let _args = Array.from(arguments); return await this.redis.json.arrPop(this.fromatKey(_args[0], 'arrPop'), this.formatPath(_args.slice(1, _args.length - 1)), _args[_args.length - 1]) as any; } async arrAppend() { let _args = Array.from(arguments); return await this.redis.json.arrAppend(this.fromatKey(_args[0], 'arrAppend'), this.formatPath(_args.slice(1, _args.length - 1)), ...[].concat(_args[_args.length - 1])) as any; } async arrInsert() { let _args = Array.from(arguments); let _numIndex = _args.length - 2; return await this.redis.json.arrInsert(this.fromatKey(_args[0], 'arrInsert'), this.formatPath(_args.slice(1, _numIndex)), _args[_numIndex], ...[].concat(_args[_args.length - 1])) as any; } // 检查 redis 是否连接 async chkRedis() { return this.redis.isOpen } } interface Document { [key: string]: any; } type Join = T extends [] ? '' : T extends [string | number] ? `${T[0]}` : T extends [string | number, ...infer R] ? `${T[0]}${D}${Join}` : string; type NestedPaths = Depth['length'] extends 8 ? [] : Type extends string | number | boolean | Date | RegExp | Buffer | Uint8Array | ((...args: any[]) => any) | { _bsontype: string; } ? [] : Type extends ReadonlyArray ? [] | [number, ...NestedPaths] : Type extends Map ? [string] : Type extends object ? { [Key in Extract]: Type[Key] extends Type ? [Key] : Type extends Type[Key] ? [Key] : Type[Key] extends ReadonlyArray ? Type extends ArrayType ? [Key] : ArrayType extends Type ? [Key] : [ Key, ...NestedPaths ] : // child is not structured the same as the parent [ Key, ...NestedPaths ] | [Key]; }[Extract] : []; type KeysOfAType = { [key in keyof TSchema]: NonNullable extends Type ? key : never; }[keyof TSchema]; type ArrayElement = Type extends ReadonlyArray ? Item : never; type PropertyType = string extends Property ? unknown : Property extends keyof Type ? Type[Property] : Property extends `${number}` ? Type extends ReadonlyArray ? ArrayType : unknown : Property extends `${infer Key}.${infer Rest}` ? Key extends `${number}` ? Type extends ReadonlyArray ? PropertyType : unknown : Key extends keyof Type ? Type[Key] extends Map ? MapType : PropertyType : unknown : unknown; type NestedPathsOfType = KeysOfAType<{ [Property in Join, '.'>]: PropertyType; }, Type>; type MatchKeysAndValues = Readonly<{ [Property in Join, '.'>]?: PropertyType; } /**& { [Property in `${NestedPathsOfType}.$${`[${string}]` | ''}`]?: ArrayElement>; } & { [Property in `${NestedPathsOfType[]>}.$${`[${string}]` | ''}.${string}`]?: any; } & Document*/>; type obj1 = RedisCollections1; type obj2 = RedisCollections2; type arr1 = RedisCollectionsArr1; type arr2 = RedisCollectionsArr2; type hStr1 = RedisCollectionsHkey1String; type hObj1 = RedisCollectionsHkeyObj1; type sObj1 = RedisCollectionsSortedObj1; type keys = keyof T; type hkeys = keyof T; type skeys = keyof T; type redisPathValueType = 'integer' | 'string' | 'object' | 'boolean' | 'array' | null; type NumericKeys = { [K in keyof T]: T[K] extends number ? K : never; }[keyof T]; export interface redisJsonFun { /** * 向路径处的数组值追加一个或多个值。RedisJSON.arrAppend */ arrAppend>(key: key, path: string, val: arr1[key][] | arr1[key]): Promise; arrAppend>(key: key, path: string, path1: string, val: arr2[key][] | arr2[key]): Promise; /** * 向路径处的数组值插入一个或多个值。 RedisJSON.arrInsert */ arrInsert>(key: key, path: string, index: number, val: arr1[key][] | arr1[key]): Promise; arrInsert>(key: key, path: string, path1: string, index: number, val: arr2[key][] | arr2[key]): Promise; /** * 获取路径处数组值的长度 RedisJSON.arrLen */ arrLen>(key: key, path: string): Promise; arrLen>(key: key, path: string, path1: string): Promise; /** * 从数组中移除并返回索引处的元素。弹出空数组会返回 Null。 RedisJSON.arrPop * @param index 数组下标 如果为-1,这表示最后一个元素。 */ arrPop>(key: key, path: string, index: number): Promise; arrPop>(key: key, path: string, path1: string, index: number): Promise; /** * 删除路径上的值 RedisJSON.del */ del>(key: key): Promise; del>(key: key, path: string): Promise; del, path extends keys>(key: key, path: string, path1: path): Promise; del>(key: key): Promise; del>(key: key, path: string): Promise; del>(key: key, path: string, path1: string): Promise; del, path extends keys>(key: key, path: string, path1: string, path2: path): Promise; del>(key: key): Promise; del>(key: key, path: string): Promise; del, path extends keys>(key: key, path: `${string}[${number}]`, path1: path): Promise; del>(key: key): Promise; del>(key: key, path: string): Promise; del>(key: key, path: string, path1: string): Promise; del, path extends keys>(key: key, path: string, path1: `${string}[${number}]`, path3: path): Promise; del(key): Promise; /** * 获取路径上的值 RedisJSON.get * @returns 没有找到返回null * @example * ```ts * //一层嵌套对象 * * await G.redis.get('user', 'uid') //获取某个玩家的信息 * await G.redis.get('user') //获取所有玩家 * * //二层嵌套对象 * * await G.redis.get('hero', 'uid', '_id') //获取玩家的某个英雄 * await G.redis.get('hero', 'uid') //获取玩家的所有英雄 * await G.redis.get('hero') //获取所有玩家的所有英雄 * * ``` */ get>(key: key): Promise>; get>(key: key): Promise>>; get>(key: key): Promise>; get>(key: key): Promise>>; get>(key: key, uid: string): Promise; get>(key: key, uid: string): Promise>; get>(key: key, k: `${string}[${number}]`): Promise; get>(key: key, k: string): Promise; get>(key: key, k: string): Promise>; get, path extends keys>(key: key, k: `${string}[${number}]`, path: path): Promise; get>(key: key, k: string, k1: `${string}[${number}]`): Promise; get>(key: key, k: string, k1: string): Promise; get, path extends keys>(key: key, uid: string, path: path): Promise; get>(key: key, uid: string, _id: string): Promise; get, path extends keys>(key: key, k: string, k1: `${string}[${number}]`, k2: path): Promise; get, path extends keys>(key: key, uid: string, _id: string, path: path): Promise; /** * 获取多个路径上的值 基于RedisJSON.get的封装 * @returns [val | null, val | null, ...] 需要注意的是会跟据传入参数的长度返回对应长度的数组 其中找不到的值为null * @example * ```ts * //一层嵌套对象 * * await G.redis.gets('user', ['bindUid1'], ['bindUid2']) //获取 [bindUid1的玩家数据, bindUid2的玩家数据]; * await G.redis.gets('user', ['bindUid1', 'lv'], ['bindUid2', 'vip']) //获取 [bindUid1玩家的lv, bindUid2玩家的vip] * * //二层嵌套对象 * * await G.redis.get('hero', ['uid1'], ['uid2']) //获取 [uid玩家的所有英雄, uid2玩家的所有英雄] * await G.redis.get('hero', ['uid1', '_id1'], ['uid2', '_id1']) //获取 [uid玩家_id为'_id1'的英雄, uid2玩家_id为'_id1'的英雄] * await G.redis.get('hero', ['uid1', '_id1', 'lv'], ['uid2', '_id1', 'heroId']) //获取 [uid玩家_id为'_id1'的英雄的lv, uid2玩家_id为'_id1'的英雄的heroId] * * ``` */ gets, path extends keys>(key: key, ...paths: [string, `${string}[${number}]`, path][]): Promise; gets, path extends keys>(key: key, ...paths: [string, string, path][]): Promise; gets, path extends keys>(key: key, ...paths: [`${string}[${number}]`, path][]): Promise; gets>(key: key, ...paths: [string, `${string}[${number}]`][]): Promise; gets, path extends keys>(key: key, ...paths: [string, path][]): Promise; gets>(key: key, ...paths: [string, string][]): Promise; gets>(key: key, ...paths: [string, string][]): Promise; gets>(key: key, ...paths: [string][]): Promise; gets>(key: key, ...paths: [string][]): Promise[]>; gets>(key: key, ...paths: [`${string}[${number}]`][]): Promise; gets>(key: key, ...paths: [string][]): Promise; /** * 将路径上的数字值增加给定数字 RedisJSON.numIncrBy */ numIncrBy, path extends NumericKeys>(key: key, path: string, path1: `${string}[${number}]`, path2: path, by: number): Promise; numIncrBy, path extends NumericKeys>(key: key, path: string, path1: string, path2: path, by: number): Promise; numIncrBy, path extends NumericKeys>(key: key, path: `${string}[${number}]`, path1: path, by: number): Promise; numIncrBy, path extends NumericKeys>(key: key, path: string, path1: path, by: number): Promise; /** * 设置路径上的值 RedisJSON.set */ set>(key: key, json: k_v | {}): Promise<'OK'>; set>(key: key, json: k_v> | {}): Promise<'OK'>; set>(key: key, json: k_v | {}): Promise<'OK'>; set>(key: key, json: k_v> | {}): Promise<'OK'>; set, path extends keys>(key: key, path1: string, path2: `${string}[${number}]`, path3: path, json: arr2[key][path]): Promise<'OK'>; set>(key: key, path1: `${string}[${number}]`, json: arr1[key]): Promise<'OK'>; set>(key: key, path1: string, json: arr1[key][] | []): Promise<'OK'>; set>(key: key, path1: string, json: k_v | {}): Promise<'OK'>; set>(key: key, path1: string, json: obj1[key]): Promise<'OK'>; set>(key: key, path1: string, json: k_v | {}): Promise<'OK'>; set, path extends keys>(key: key, path1: `${string}[${number}]`, path2: path, json: arr1[key][path]): Promise<'OK'>; set, path extends keys>(key: key, path1: string, path2: path, json: obj1[key][path]): Promise<'OK'>; set>(key: key, path1: string, path2: string, json: obj2[key]): Promise<'OK'>; set>(key: key, path1: string, path2: string, json: arr2[key][] | []): Promise<'OK'>; set, path extends keys>(key: key, path1: string, path2: string, path3: path, json: obj2[key][path]): Promise<'OK'>; set, path extends keys, path1 extends keys>(key: key, path1: string, path2: string, path3: path, path4: path1, json: obj2[key][path][path1]): Promise<'OK'>; //(property) set: >(...args: [key: RedisCommandArgument, value: number | RedisCommandArgument, options?: SetOptions] | [options: ...]) => Promise<...> rawSet(key:string | Buffer,value:string | Buffer,option?:any):Promise; rawGet(key: string): Promise; rawDel(key: string): Promise; /** * 设置hash格式的数据 * @param key * @param hash * @param value */ hSet>(key: key, hash:string, value: number | string):Promise; hSet>(key: key, hash:string, value: hObj1[key]):Promise; // 变量传key无法通过检测时,不做强制key类型验证。 hSet(key: key, hash:string, value: any):Promise; /** * 获取hash格式的数据 * @param key * @param hash */ hGet>(key: key, hash:string): Promise; hGet>(key: key, hash:string): Promise; // 变量传key无法通过检测时,不做强制key类型验证。 hGet(key: key, hash:string): Promise; hmGet>(key: key, hash:string[]): Promise; hmGet(key: key, hash:string[]): Promise; /**获取全部的hash数据 */ hGetAll>(key: key):Promise; hGetAll(key: key):Promise; /**获取hash的长度 */ hLen>(key: key):Promise; hLen(key: key):Promise; /**删除指定hash的某个field */ hDel>(key: key, field: string):Promise; hDel(key: key, field: string):Promise; /** * 写入排名数据 * @param key */ zAdd>(key: key, member: sObj1[key]): Promise; zAdd(key: key, member: sortEd|sortEd[]): Promise; /** * 取出范围索引的排名数据 * @param key */ zRange>(key: key, min: number, max: number): Promise; zRange(key: key, min: number, max: number): Promise; zRevRange>(key: key, min: number, max: number): Promise; zRevRange(key: key, min: number, max: number): Promise; /** * 取出指定member排名数据 */ zRank>(key: key, member: string): Promise; zRank(key: key, member: string): Promise; /** * 取出指定member的分数 * @param key * @param member */ zScore>(key: key, member: string): Promise; zScore(key: key, member: string): Promise; /** * 取出指定member排名数据-倒叙 */ zRevRank>(key: key, member: string): Promise; zRevRank(key: key, member: string): Promise; /** * 删除指定member排名数据 */ zRem>(key: key, member: string): Promise; zRem(key: key, member: string): Promise; /** */ /** * 排行榜总数 * @param key */ zCard>(key: key): Promise; zCard(key: key): Promise; /***- * 获取路径上值的类型 RedisJSON.type */ type>(key: key): Promise; type>(key: key, path: string): Promise; type, path extends keys>(key: key, path: string, path1: path): Promise; type>(key: key): Promise; type>(key: key, path: string): Promise; type>(key: key, path: string, path1: string): Promise; type, path extends keys>(key: key, path: string, path1: string, path2: path): Promise; type>(key: key): Promise; type>(key: key, path: string): Promise; type, path extends keys>(key: key, path: `${string}[${number}]`, path1: path): Promise; type>(key: key): Promise; type>(key: key, path: string): Promise; type>(key: key, path: string, path1: string): Promise; type, path extends keys>(key: key, path: string, path1: `${string}[${number}]`, path3: path): Promise; chkRedis: any fromatKey(key: string, state?: 'del' | 'numIncrBy' | 'type' | 'get' | 'set' | 'arrAppend' | 'arrPop' | 'arrLen' | 'arrInsert' | null); }