增加一些排行榜相关方法

This commit is contained in:
zhl 2021-04-29 14:00:46 +08:00
parent 0252c39e18
commit 2f646863ae
6 changed files with 442 additions and 6 deletions

View File

@ -17,8 +17,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"@typegoose/auto-increment": "^0.4.1",
"@typegoose/typegoose": "^7.4.6",
"alipay-sdk": "^3.1.4",
"axios": "^0.21.1",
"bson": "^4.0.4",
@ -40,6 +38,7 @@
"mongoose-findorcreate": "^3.0.0",
"qrcode": "^1.4.4",
"querystring": "^0.2.1",
"redis": "^3.1.2",
"reflect-metadata": "^0.1.13",
"svg-captcha": "^1.4.0",
"tracer": "^1.0.3",
@ -52,6 +51,9 @@
"@types/dotenv": "^8.2.0",
"@types/mongoose": "5.10.3",
"@types/node": "^14.14.20",
"@typegoose/auto-increment": "^0.4.1",
"@typegoose/typegoose": "^7.4.6",
"@types/redis": "^2.8.28",
"ts-node": "^9.1.1",
"tsconfig-paths": "^3.9.0",
"tslint": "^6.1.1",

View File

@ -1,4 +1,5 @@
export class BaseConst{
public static readonly COMPOUND = 'compound'
public static readonly MISSION = 'mission'
public static readonly RANK_SCORE = 'rank_score'
}

268
src/redis/RedisClient.ts Normal file
View File

@ -0,0 +1,268 @@
import redis from 'redis';
import { promisify } from 'util';
import { singleton } from '../decorators/singleton'
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 expire(key: string, seconds: number) {
return new Promise((resolve) =>
this.pub.expire(key, seconds, 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 zadd(key: string, value: any, member: string) {
return new Promise((resolve) => {
this.pub.zadd(key, value, member, resolve);
});
}
public async zrangebyscore(key: string, min: number, max: number) {
return new Promise((resolve, reject) => {
this.pub.zrangebyscore(key, min, max, 'withscores', (err, data) => {
if (err) { return reject(err); }
resolve(data);
});
});
}
public async zcard(key: string) {
return new Promise((resolve, reject) => {
this.pub.zcard(key, (err, data) => {
if (err) { return reject(err); }
resolve(data);
});
});
}
public async zrevrank(key: string, member: string) {
return new Promise((resolve, reject) => {
this.pub.zrevrank(key, member, (err, data) => {
if (err) { return reject(err); }
resolve(data);
});
});
}
public async zscore(key: string, member: string) {
return new Promise((resolve, reject) => {
this.pub.zscore(key, member, (err, data) => {
if (err) { return reject(err); }
resolve(data);
});
});
}
public async zrevrange(key: string, start: number, end: number) {
return new Promise((resolve, reject) => {
this.pub.zrevrange(key, start, end, 'withscores', (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));
}
}
}
}

131
src/services/Rank.ts Normal file
View File

@ -0,0 +1,131 @@
import { RedisClient } from '../redis/RedisClient'
import { BaseConst } from '../constants/BaseConst'
const MAX_TIME = 5000000000000
function generateRankKey(subKey?: string) {
if (subKey) {
return `${BaseConst.RANK_SCORE}_${subKey}`
} else {
return BaseConst.RANK_SCORE
}
}
/**
*
* @param subKey
* @param {string} accountId
* @param {number} score
* @return {Promise<void>}
*/
export async function updateRank(accountId: string, score: number, subKey?: string) {
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
await new RedisClient().zadd(generateRankKey(subKey), scoreL, accountId)
}
/**
*
* @param {number} min
* @param {number} max
* @param subKey
* @return {Promise<unknown>}
*/
export async function usersByScore(min: number, max: number, subKey?: string) {
return await new RedisClient().zrangebyscore(generateRankKey(subKey), min, max)
}
/**
*
* @param {string} accountId
* @param subKey
* @return {Promise<unknown>}
*/
export async function getAccountRank(accountId: string, subKey?: string) {
return await new RedisClient().zrevrank(generateRankKey(subKey), accountId)
}
/**
*
* @param {string} accountId
* @param subKey
* @return {Promise<unknown>}
*/
export async function getAccountScore(accountId: string, subKey?: string) {
return await new RedisClient().zscore(generateRankKey(subKey), accountId)
}
/**
* start到end的玩家列表
* @param {number} start
* @param {number} end
* @param subKey
* @return {Promise<unknown>}
*/
export async function getRankList(start: number, end: number, subKey?: string) {
return await new RedisClient().zrevrange(generateRankKey(subKey), start, end)
}
/**
*
* @param {string} accountId
* @param subKey
* @return {Promise<unknown>}
*/
export async function getRankNear(accountId: string, subKey?: string) {
let rank: any = await getAccountRank(accountId)
if (rank == null) {
return []
}
let total: any = await new RedisClient().zcard(generateRankKey(subKey))
let start = 0
let count = 2
let end = start + count
if (rank > 0 && rank < total - 1) {
start = rank - 1
end = start + count
} else if (rank == total - 1) {
start = rank - 2
end = total
}
start = start < 0? 0 : start
end = end > total - 1 ? total - 1 : end
let datas: any = await getRankList(start, end)
let results: any[] = []
for (let i = 0, l = datas.length; i < l; i += 2) {
results.push({
rank: start + (i / 2),
accountId: datas[i],
score: datas[i + 1] << 0
})
}
return results
}
/**
*
* @param {string} subKey
* @return {Promise<void>}
*/
export async function delRankList(subKey?: string) {
await new RedisClient().del(generateRankKey(subKey))
}
/**
*
* @param {string} accountId
* @param seconds
* @return {Promise<void>}
*/
export async function setGameing(accountId: string, seconds: number) {
await new RedisClient().setex('gameing_' + accountId, (Date.now() / 1000 | 0) + '', seconds)
}
export async function setGameEnd(accountId: string) {
await new RedisClient().expire('gameing_' + accountId, 0)
}
/**
*
* @param {string} accountId
* @return {Promise<unknown>}
*/
export async function checkGameing(accountId: string) {
return await new RedisClient().get('gameing_' + accountId)
}

View File

@ -18,7 +18,7 @@ export async function sendMsg(roomId, clientId, type, msg) {
method: 'smsg',
args: JSON.stringify(args)
}
return axios.get(url, {params: data})
return axios.post(url, {params: data})
.then(res => {
return res.data
})
@ -38,7 +38,7 @@ export async function broadcast(roomId, type, msg) {
type,
msg
}
return axios.get(url, {params: data})
return axios.post(url, {params: data})
.then(res => {
return res.data
})
@ -58,7 +58,7 @@ export async function kickClient(roomId, clientId) {
method: '_forceClientDisconnect',
args: JSON.stringify(args)
}
return axios.get(url, {params: data})
return axios.post(url, {params: data})
.then(res => {
return res.data
})

View File

@ -137,6 +137,13 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.61.tgz#29f124eddd41c4c74281bd0b455d689109fc2a2d"
integrity sha512-/aKAdg5c8n468cYLy2eQrcR5k6chlbNwZNGUj3TboyPa2hcO2QAJcfymlqPzMiRj8B6nYKXjzQz36minFE0RwQ==
"@types/redis@^2.8.28":
version "2.8.28"
resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.28.tgz#5862b2b64aa7f7cbc76dafd7e6f06992b52c55e3"
integrity sha512-8l2gr2OQ969ypa7hFOeKqtFoY70XkHxISV0pAwmQ2nm6CSPb1brmTmqJCGGrekCo+pAZyWlNXr+Kvo6L/1wijA==
dependencies:
"@types/node" "*"
abstract-logging@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839"
@ -621,7 +628,7 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
denque@^1.4.1:
denque@^1.4.1, denque@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==
@ -1943,6 +1950,33 @@ readable-stream@^3.4.0, readable-stream@^3.6.0:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
redis-commands@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
dependencies:
redis-errors "^1.0.0"
redis@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.2.tgz#766851117e80653d23e0ed536254677ab647638c"
integrity sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==
dependencies:
denque "^1.5.0"
redis-commands "^1.7.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
reflect-metadata@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"