添加redis相关接口
This commit is contained in:
parent
a9d799b05e
commit
b69953fd51
35
docs/api.md
35
docs/api.md
@ -650,6 +650,41 @@
|
||||
}]
|
||||
```
|
||||
|
||||
### 4. 获取用户的简要信息
|
||||
1. Method: GET
|
||||
2. URI: /svr/:accountid/uinfo
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| accountid | 帐号id |
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
|
||||
|
||||
```js
|
||||
{
|
||||
nickname: '阿三', // 英雄id
|
||||
avatar: true, // 头像
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 随机获取一个机器人信息
|
||||
1. Method: GET
|
||||
2. URI: /svr/randomrobot
|
||||
|
||||
|
||||
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
|
||||
```js
|
||||
{
|
||||
nickname: '阿三', // 英雄id
|
||||
avatar: true, // 头像
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
34
package-lock.json
generated
34
package-lock.json
generated
@ -79,6 +79,15 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
|
||||
"integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A=="
|
||||
},
|
||||
"@types/redis": {
|
||||
"version": "2.8.28",
|
||||
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.28.tgz",
|
||||
"integrity": "sha512-8l2gr2OQ969ypa7hFOeKqtFoY70XkHxISV0pAwmQ2nm6CSPb1brmTmqJCGGrekCo+pAZyWlNXr+Kvo6L/1wijA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
@ -305,6 +314,11 @@
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"double-ended-queue": {
|
||||
"version": "2.1.0-0",
|
||||
"resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz",
|
||||
"integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw="
|
||||
},
|
||||
"ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@ -970,6 +984,26 @@
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"redis": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz",
|
||||
"integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==",
|
||||
"requires": {
|
||||
"double-ended-queue": "^2.1.0-0",
|
||||
"redis-commands": "^1.2.0",
|
||||
"redis-parser": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"redis-commands": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz",
|
||||
"integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ=="
|
||||
},
|
||||
"redis-parser": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz",
|
||||
"integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs="
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
|
@ -28,6 +28,7 @@
|
||||
"mime-types": "^2.1.28",
|
||||
"mongoose": "5.10.3",
|
||||
"mongoose-findorcreate": "^3.0.0",
|
||||
"redis": "^2.8.0",
|
||||
"tracer": "^1.1.4",
|
||||
"urlencode": "^1.1.0"
|
||||
},
|
||||
@ -35,6 +36,7 @@
|
||||
"@types/debug": "4.1.5",
|
||||
"@types/mongoose": "5.10.3",
|
||||
"@types/node": "^14.14.20",
|
||||
"@types/redis": "^2.8.28",
|
||||
"ts-node": "^9.1.1",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"typescript": "^4.1.3"
|
||||
|
@ -11,6 +11,9 @@ import {mongoose} from "@typegoose/typegoose";
|
||||
import logger from './logger/logger';
|
||||
import {Config} from "./cfg/Config";
|
||||
import {initData} from "./common/GConfig";
|
||||
import { RedisClient } from './redis/RedisClient'
|
||||
import { initRobotCache } from './service/jcfw'
|
||||
import { error } from './common/Debug'
|
||||
|
||||
const zReqParserPlugin = require('./plugins/zReqParser');
|
||||
const apiAuthPlugin = require('./plugins/apiauth');
|
||||
@ -84,6 +87,8 @@ export class ApiServer {
|
||||
} catch (err) {
|
||||
logger.log(`DB Connection Error: ${err.message}`);
|
||||
}
|
||||
let opts = {url: config.redis}
|
||||
new RedisClient(opts)
|
||||
}
|
||||
private setErrHandler() {
|
||||
this.server.setNotFoundHandler(function(request: any, reply: { send: (arg0: { errcode: number; errmsg: string; }) => void; }){
|
||||
@ -124,10 +129,18 @@ export class ApiServer {
|
||||
return payload
|
||||
})
|
||||
}
|
||||
private async initCache() {
|
||||
try {
|
||||
await initRobotCache(100)
|
||||
} catch (err) {
|
||||
error('error init robot info', err)
|
||||
}
|
||||
}
|
||||
public async start() {
|
||||
let self = this;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
await self.connectDB();
|
||||
await self.initCache();
|
||||
self.initControllers();
|
||||
self.registerRouter();
|
||||
self.setErrHandler();
|
||||
|
@ -72,4 +72,13 @@ export class BaseConst {
|
||||
public static readonly ITEMCARD = "itemcard";
|
||||
public static readonly MATCH = "match";
|
||||
public static readonly ITEMFUNC = "itemfunc";
|
||||
/**
|
||||
* 缓存的机器人信息在redis中的key
|
||||
*/
|
||||
public static readonly ROBOT_INFO = 'robot_info'
|
||||
/**
|
||||
* 缓存的机器人信息昵称和头像的分割字符
|
||||
*/
|
||||
public static readonly ROBOT_INFO_SEP = '|||'
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { BaseConst } from '../constants/BaseConst'
|
||||
import { Hero } from '../models/subdoc/Hero'
|
||||
import { BagItem, ItemType } from '../models/BagItem'
|
||||
import { addHeroDefaultCardGroup } from '../dao/CardGroupDao'
|
||||
import { RedisClient } from '../redis/RedisClient'
|
||||
|
||||
export default class AccountController extends BaseController {
|
||||
@role('anon')
|
||||
@ -75,6 +76,25 @@ export default class AccountController extends BaseController {
|
||||
result.match_score = account.getMatchScore()
|
||||
return result
|
||||
}
|
||||
@router('get /svr/:accountid/uinfo')
|
||||
async simpleInfo(req: any) {
|
||||
let account = req.user
|
||||
return {
|
||||
nickname: account.nickname,
|
||||
avatar: account.avatar
|
||||
}
|
||||
}
|
||||
|
||||
@router('get /svr/randomrobot')
|
||||
async randomRobot(req: any) {
|
||||
// @ts-ignore
|
||||
let str: string = await new RedisClient().srandmember(BaseConst.ROBOT_INFO)
|
||||
let arr = str.split(BaseConst.ROBOT_INFO_SEP)
|
||||
return {
|
||||
nickname: arr[0],
|
||||
avatar: arr[1]
|
||||
}
|
||||
}
|
||||
|
||||
@router('post /api/:accountid/season_data')
|
||||
async seasonData(req: any) {
|
||||
|
212
src/redis/RedisClient.ts
Normal file
212
src/redis/RedisClient.ts
Normal file
@ -0,0 +1,212 @@
|
||||
import redis from 'redis';
|
||||
import { promisify } from 'util';
|
||||
import { singleton } from '../decorators/singleton.decorator'
|
||||
|
||||
type Callback = (...args: any[]) => void;
|
||||
|
||||
@singleton
|
||||
export class RedisClient {
|
||||
public pub: redis.RedisClient;
|
||||
public sub: redis.RedisClient;
|
||||
|
||||
protected subscribeAsync: any;
|
||||
protected unsubscribeAsync: any;
|
||||
protected publishAsync: any;
|
||||
|
||||
protected subscriptions: { [channel: string]: Callback[] } = {};
|
||||
|
||||
protected smembersAsync: any;
|
||||
protected sismemberAsync: any;
|
||||
protected hgetAsync: any;
|
||||
protected hlenAsync: any;
|
||||
protected pubsubAsync: any;
|
||||
protected incrAsync: any;
|
||||
protected decrAsync: any;
|
||||
|
||||
constructor(opts?: redis.ClientOpts) {
|
||||
this.sub = redis.createClient(opts);
|
||||
this.pub = redis.createClient(opts);
|
||||
|
||||
// no listener limit
|
||||
this.sub.setMaxListeners(0);
|
||||
|
||||
// create promisified pub/sub methods.
|
||||
this.subscribeAsync = promisify(this.sub.subscribe).bind(this.sub);
|
||||
this.unsubscribeAsync = promisify(this.sub.unsubscribe).bind(this.sub);
|
||||
|
||||
this.publishAsync = promisify(this.pub.publish).bind(this.pub);
|
||||
|
||||
// create promisified redis methods.
|
||||
this.smembersAsync = promisify(this.pub.smembers).bind(this.pub);
|
||||
this.sismemberAsync = promisify(this.pub.sismember).bind(this.pub);
|
||||
this.hlenAsync = promisify(this.pub.hlen).bind(this.pub);
|
||||
this.hgetAsync = promisify(this.pub.hget).bind(this.pub);
|
||||
this.pubsubAsync = promisify(this.pub.pubsub).bind(this.pub);
|
||||
this.decrAsync = promisify(this.pub.decr).bind(this.pub);
|
||||
this.incrAsync = promisify(this.pub.incr).bind(this.pub);
|
||||
}
|
||||
|
||||
public async subscribe(topic: string, callback: Callback) {
|
||||
if (!this.subscriptions[topic]) {
|
||||
this.subscriptions[topic] = [];
|
||||
}
|
||||
|
||||
this.subscriptions[topic].push(callback);
|
||||
|
||||
if (this.sub.listeners('message').length === 0) {
|
||||
this.sub.addListener('message', this.handleSubscription);
|
||||
}
|
||||
|
||||
await this.subscribeAsync(topic);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async unsubscribe(topic: string, callback?: Callback) {
|
||||
if (callback) {
|
||||
const index = this.subscriptions[topic].indexOf(callback);
|
||||
this.subscriptions[topic].splice(index, 1);
|
||||
|
||||
} else {
|
||||
this.subscriptions[topic] = [];
|
||||
}
|
||||
|
||||
if (this.subscriptions[topic].length === 0) {
|
||||
await this.unsubscribeAsync(topic);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public async publish(topic: string, data: any) {
|
||||
if (data === undefined) {
|
||||
data = false;
|
||||
}
|
||||
|
||||
await this.publishAsync(topic, JSON.stringify(data));
|
||||
}
|
||||
|
||||
public async exists(roomId: string): Promise<boolean> {
|
||||
return (await this.pubsubAsync('channels', roomId)).length > 0;
|
||||
}
|
||||
|
||||
public async setex(key: string, value: string, seconds: number) {
|
||||
return new Promise((resolve) =>
|
||||
this.pub.setex(key, seconds, value, resolve));
|
||||
}
|
||||
|
||||
public async get(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.get(key, (err, data) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async del(roomId: string) {
|
||||
return new Promise((resolve) => {
|
||||
this.pub.del(roomId, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public async sadd(key: string, value: any) {
|
||||
return new Promise((resolve) => {
|
||||
this.pub.sadd(key, value, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public async smembers(key: string): Promise<string[]> {
|
||||
return await this.smembersAsync(key);
|
||||
}
|
||||
|
||||
public async sismember(key: string, field: string): Promise<number> {
|
||||
return await this.sismemberAsync(key, field);
|
||||
}
|
||||
|
||||
public async srem(key: string, value: any) {
|
||||
return new Promise((resolve) => {
|
||||
this.pub.srem(key, value, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public async scard(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.scard(key, (err, data) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
public async srandmember(key: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.srandmember(key, (err, data) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async sinter(...keys: string[]) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
this.pub.sinter(...keys, (err, data) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async hset(key: string, field: string, value: string) {
|
||||
return new Promise((resolve) => {
|
||||
this.pub.hset(key, field, value, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public async hincrby(key: string, field: string, value: number) {
|
||||
return new Promise((resolve) => {
|
||||
this.pub.hincrby(key, field, value, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
public async hget(key: string, field: string) {
|
||||
return await this.hgetAsync(key, field);
|
||||
}
|
||||
|
||||
public async hgetall(key: string) {
|
||||
return new Promise<{ [key: string]: string }>((resolve, reject) => {
|
||||
this.pub.hgetall(key, (err, values) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(values);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async hdel(key: string, field: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pub.hdel(key, field, (err, ok) => {
|
||||
if (err) { return reject(err); }
|
||||
resolve(ok);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async hlen(key: string): Promise<number> {
|
||||
return await this.hlenAsync(key);
|
||||
}
|
||||
|
||||
public async incr(key: string): Promise<number> {
|
||||
return await this.incrAsync(key);
|
||||
}
|
||||
|
||||
public async decr(key: string): Promise<number> {
|
||||
return await this.decrAsync(key);
|
||||
}
|
||||
|
||||
protected handleSubscription = (channel: string, message: string) => {
|
||||
if (this.subscriptions[channel]) {
|
||||
for (let i = 0, l = this.subscriptions[channel].length; i < l; i++) {
|
||||
this.subscriptions[channel][i](JSON.parse(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +1,60 @@
|
||||
import { generateKeyValStr } from '../utils/string.util'
|
||||
import axios from 'axios'
|
||||
import { createSign } from '../utils/security.util'
|
||||
import { RedisClient } from '../redis/RedisClient'
|
||||
import { BaseConst } from '../constants/BaseConst'
|
||||
|
||||
const CONFIG_URL = 'https://center.kingsome.cn/api/cfg_list'
|
||||
|
||||
/**
|
||||
* 获取jcfw中该游戏的配置
|
||||
* @param {string} gameid
|
||||
* @param {string} channel
|
||||
*/
|
||||
export function getGameConfig(gameid: string, channel: string) {
|
||||
let url = `${ CONFIG_URL }?game_id=${gameid}&channel_id=${channel}`
|
||||
let url = `${ CONFIG_URL }?game_id=${ gameid }&channel_id=${ channel }`
|
||||
return axios.get(url).then((res: any) => {
|
||||
if (res.data.errorcode && res.data.result) {
|
||||
throw new Error(`error get game cfg, code: ${res.errorcode}, msg: ${res.errmsg}`)
|
||||
if (res.data.errorcode || !res.data.result) {
|
||||
throw new Error(`error get game cfg, code: ${ res.errorcode }, msg: ${ res.errmsg }`)
|
||||
}
|
||||
return JSON.parse(res.data.result)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机数量的机器人数据
|
||||
* @param {string | undefined} accountId
|
||||
* @param {number} num
|
||||
* @return {Promise<AxiosResponse<any>>}
|
||||
*/
|
||||
export function randomUser(accountId: string | undefined, num: number) {
|
||||
const excludeAccountids = accountId ? `[${ accountId }]` : '[]'
|
||||
const timestamp = new Date().getTime()
|
||||
const url = 'https://service.kingsome.cn/webapp/index.php?c=Voodoo&a=getRobotList'
|
||||
const paramStr = `exclude_accountids=${ excludeAccountids }&exclude_names=[]&num=${ num }`
|
||||
const signStr = createSign('70e32abc60367adccaa9eb7b56ed821b', paramStr, timestamp)
|
||||
const link = `${ url }&${ paramStr }&sign=${ signStr }×tamp=${ timestamp }`
|
||||
return axios.get(link).then((res: any) => {
|
||||
if (res.data.errorcode || !res.data.robot_list) {
|
||||
throw new Error(`error get robo list, code: ${ res.errorcode }, msg: ${ res.errmsg }`)
|
||||
}
|
||||
return res.data.robot_list
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新redis中机器人数据
|
||||
* @param {number} count
|
||||
* @param {boolean} force
|
||||
*/
|
||||
export async function initRobotCache(count: number, force?: boolean) {
|
||||
const redisClient = new RedisClient()
|
||||
const countExists = await redisClient.scard(BaseConst.ROBOT_INFO)
|
||||
if (countExists > count && !force) {
|
||||
return
|
||||
}
|
||||
let robots = await randomUser('', 100)
|
||||
if (robots && Array.isArray(robots)) {
|
||||
for(const robot of robots) {
|
||||
await redisClient.sadd(BaseConst.ROBOT_INFO, `${robot.nickname}${BaseConst.ROBOT_INFO_SEP}${robot.avatar_url}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
10
src/utils/security.util.ts
Normal file
10
src/utils/security.util.ts
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
import crypto from 'crypto'
|
||||
|
||||
// 生成签名字段
|
||||
// paramStr 为key1=val1&key2=val2, key1, key2按字母升序
|
||||
|
||||
export function createSign(secretKey: string, paramStr: string, timestamp: number) {
|
||||
paramStr = `${paramStr}:${timestamp}${secretKey}`;
|
||||
return crypto.createHash('md5').update(paramStr, 'utf8').digest('hex');
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user