添加redis相关接口

This commit is contained in:
zhl 2021-01-28 16:32:59 +08:00
parent a9d799b05e
commit b69953fd51
9 changed files with 381 additions and 5 deletions

View File

@ -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
View File

@ -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",

View File

@ -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"

View File

@ -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();

View File

@ -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 = '|||'
}

View File

@ -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
View 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));
}
}
}
}

View File

@ -1,8 +1,9 @@
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
@ -11,9 +12,49 @@ const CONFIG_URL = 'https://center.kingsome.cn/api/cfg_list'
export function getGameConfig(gameid: string, channel: string) {
let url = `${ CONFIG_URL }?game_id=${ gameid }&channel_id=${ channel }`
return axios.get(url).then((res: any) => {
if (res.data.errorcode && res.data.result) {
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 }&timestamp=${ 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}`)
}
}
}

View 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');
}