添加排行榜相关接口
This commit is contained in:
parent
41ca21e2ca
commit
b7f2fd0d95
60
docs/api.md
60
docs/api.md
@ -570,6 +570,66 @@
|
||||
]
|
||||
|
||||
```
|
||||
### 22. 排行榜
|
||||
|
||||
|
||||
|
||||
1. Method: POST
|
||||
2. URI: /api/:accountid/rank
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| accountid | 帐号id |
|
||||
|
||||
> POST参数
|
||||
|
||||
|
||||
| 字段 |说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
|limit |获取的数据数量 |
|
||||
|skip |skip数量 |
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
```js
|
||||
|
||||
{"records": [
|
||||
{
|
||||
"rank": 0,
|
||||
"accountid": "6000_3200_QcYw2AAK6Vf95WNfRmHYly340J455zZR",
|
||||
"nickname": "Sunny",
|
||||
"avatar": "https://resource.kingsome.cn/matchvs_cdn/1.0.0.1/avatar/10_2.jpg",
|
||||
"score": 1000
|
||||
},
|
||||
],
|
||||
"userRank": 0, //当前玩家的排名
|
||||
"userScore": 1000 //当前玩家的积分
|
||||
}
|
||||
```
|
||||
|
||||
### 23. 排行榜-附近的玩家
|
||||
|
||||
|
||||
1. Method: POST
|
||||
2. URI: /api/:accountid/rank/nearMe
|
||||
|
||||
| 字段 | 说明 |
|
||||
| -------- | -------------------------------------- |
|
||||
| accountid | 帐号id |
|
||||
|
||||
3. Response: JSON
|
||||
|
||||
```js
|
||||
{
|
||||
"rank": 0,
|
||||
"accountid": "6000_3200_QcYw2AAK6Vf95WNfRmHYly340J455zZR",
|
||||
"nickname": "Sunny",
|
||||
"avatar": "https://resource.kingsome.cn/matchvs_cdn/1.0.0.1/avatar/10_2.jpg",
|
||||
"score": 1000
|
||||
},
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -580,6 +580,13 @@ interface Array<T> {
|
||||
* @param arr
|
||||
*/
|
||||
difference?<T>(arr: T[]): T[];
|
||||
|
||||
/**
|
||||
* 转换成Map
|
||||
* @param {string} key 用于生成map的key字段名
|
||||
* @return {Map<any, T>}
|
||||
*/
|
||||
toMap?<T>(key: string): Map<any, T>;
|
||||
}
|
||||
|
||||
Object.defineProperties(Array.prototype, {
|
||||
@ -858,6 +865,18 @@ Object.defineProperties(Array.prototype, {
|
||||
return [...set1];
|
||||
},
|
||||
writable: true
|
||||
},
|
||||
|
||||
toMap: {
|
||||
value: function<T> (this: T[], key: string) {
|
||||
let result: Map<any, T> = new Map()
|
||||
for(const o of this) {
|
||||
// @ts-ignore
|
||||
result.set(o[key], o)
|
||||
}
|
||||
return result
|
||||
},
|
||||
writable: true
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { BagItem, ItemType } from '../models/BagItem'
|
||||
import { RedisClient } from '../redis/RedisClient'
|
||||
import {
|
||||
checkGameing,
|
||||
getRankScore,
|
||||
getAccountRank,
|
||||
setGameing,
|
||||
usersByScore
|
||||
} from '../service/rank'
|
||||
@ -37,7 +37,7 @@ export default class AccountController extends BaseController {
|
||||
result.season_score = account.season_score
|
||||
result.season_data = account.season_data
|
||||
result.match_score = account.getMatchScore()
|
||||
let rank = await getRankScore(accountid)
|
||||
let rank = await getAccountRank(accountid)
|
||||
if (typeof rank === 'string') {
|
||||
result.rank = parseInt(rank)
|
||||
} else {
|
||||
|
@ -1,5 +1,77 @@
|
||||
import BaseController from '../common/base.controller'
|
||||
import { router } from '../decorators/router'
|
||||
import {
|
||||
getAccountRank,
|
||||
getAccountScore,
|
||||
getRankList,
|
||||
getRankNear
|
||||
} from '../service/rank'
|
||||
import { User } from '../models/User'
|
||||
|
||||
export default class RankController extends BaseController {
|
||||
|
||||
@router('post /api/games/rank')
|
||||
@router('post /api/:accountid/rank')
|
||||
async rank(req: any) {
|
||||
let { gameId, channelId, limit, skip, accountid } = req.params
|
||||
let start = skip || 0
|
||||
let end = start + limit
|
||||
let datas: any = await getRankList(start, end)
|
||||
let accountIds: string[] = []
|
||||
let scoreMap: Map<any, number> = new Map()
|
||||
for (let i = 0, l = datas.length; i < l; i += 2) {
|
||||
accountIds.push(datas[i])
|
||||
scoreMap.set(datas[i], datas[i + 1] << 0)
|
||||
}
|
||||
const accounts = await User.find({_id : {$in: accountIds}})
|
||||
const accountMap: Map<string, any> = accounts.toMap('_id')
|
||||
let results: any = []
|
||||
let i = 0
|
||||
for (let aid of accountIds) {
|
||||
const account = accountMap.get(aid)
|
||||
results.push({
|
||||
rank: start + i ++,
|
||||
accountid: aid,
|
||||
nickname: account.nickname,
|
||||
avatar: account.avatar,
|
||||
score: scoreMap.get(aid)
|
||||
})
|
||||
}
|
||||
let userRank = await getAccountRank(accountid)
|
||||
// @ts-ignore
|
||||
let userScore = (await getAccountScore(accountid)) << 0
|
||||
return {
|
||||
records: results,
|
||||
userRank,
|
||||
userScore
|
||||
}
|
||||
}
|
||||
|
||||
@router('post /api/:accountid/rank/nearMe')
|
||||
@router('post /games/rank/nearMe')
|
||||
async nearMe(req: any) {
|
||||
let { gameId, channelId, score, accountid } = req.params
|
||||
let datas = await getRankNear(accountid)
|
||||
let accountIds: string[] = []
|
||||
let scoreMap: Map<any, any> = new Map()
|
||||
for (let data of datas) {
|
||||
accountIds.push(data.accountid)
|
||||
scoreMap.set(data.accountid, data)
|
||||
}
|
||||
|
||||
const accounts = await User.find({_id : {$in: accountIds}})
|
||||
const accountMap: Map<string, any> = accounts.toMap('_id')
|
||||
let results: any = []
|
||||
for (let aid of accountIds) {
|
||||
const account = accountMap.get(aid)
|
||||
results.push({
|
||||
rank: scoreMap.get(aid).rank,
|
||||
accountid: aid,
|
||||
nickname: account.nickname,
|
||||
avatar: account.avatar,
|
||||
score: scoreMap.get(aid).score
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
@ -1,72 +1,73 @@
|
||||
import {
|
||||
FastifyInstance,
|
||||
FastifyPluginAsync, FastifyReply,
|
||||
FastifyRequest,
|
||||
} from 'fastify';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
import {User} from '../models/User';
|
||||
import {ZError} from "../common/ZError";
|
||||
FastifyInstance,
|
||||
FastifyPluginAsync,
|
||||
FastifyReply,
|
||||
FastifyRequest
|
||||
} from 'fastify'
|
||||
import fastifyPlugin from 'fastify-plugin'
|
||||
import { User } from '../models/User'
|
||||
import { ZError } from '../common/ZError'
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
apiAuth: (request: FastifyRequest, reply: FastifyReply) => {};
|
||||
}
|
||||
interface FastifyInstance {
|
||||
apiAuth: (request: FastifyRequest, reply: FastifyReply) => {};
|
||||
}
|
||||
|
||||
interface FastifyRequest {
|
||||
session?: {[key: string]: any};
|
||||
roles?: any,
|
||||
user?: any
|
||||
}
|
||||
interface FastifyRequest {
|
||||
session?: { [key: string]: any };
|
||||
roles?: any,
|
||||
user?: any
|
||||
}
|
||||
}
|
||||
|
||||
const apiAuthPlugin: FastifyPluginAsync = async function(
|
||||
fastify: FastifyInstance,
|
||||
options?: any
|
||||
const apiAuthPlugin: FastifyPluginAsync = async function (
|
||||
fastify: FastifyInstance,
|
||||
options?: any
|
||||
) {
|
||||
|
||||
// 只有路由配置的role为anon才不需要过滤
|
||||
fastify.decorate("apiAuth", async function(request: FastifyRequest, reply: FastifyReply) {
|
||||
if (request.url.startsWith('/svr')) {
|
||||
// @ts-ignore
|
||||
let { accountid } = request.params;
|
||||
if (accountid) {
|
||||
request.user = await User.findById(accountid);
|
||||
}
|
||||
} else {
|
||||
if (!request.roles || request.roles.indexOf('anon') == -1) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
let { accountid, sessionid } = request.params;
|
||||
//TODO: 增加sessionid的校验
|
||||
// if (!accountid || !sessionid) {
|
||||
// return reply.send({code: 11, msg: 'need accountid and sessionid'});
|
||||
// }
|
||||
if (!accountid) {
|
||||
return reply.send({code: 2, msg: 'need accountid and sessionid'});
|
||||
}
|
||||
// const data = this.jwt.verify(request.token);
|
||||
// if (!data || !data.id) {
|
||||
// return reply.send({code: 10, msg: 'need login'});
|
||||
// }
|
||||
let account = await User.findById(accountid);
|
||||
if (!account) {
|
||||
return reply.send({code: 5, msg: 'account not found'});
|
||||
}
|
||||
if (account.locked) {
|
||||
return reply.send({code: 4, msg: 'account locked'});
|
||||
}
|
||||
request.user = account;
|
||||
} catch (err) {
|
||||
return reply.send({code: 401, msg: 'need auth'})
|
||||
}
|
||||
}
|
||||
// 只有路由配置的role为anon才不需要过滤
|
||||
fastify.decorate('apiAuth', async function (request: FastifyRequest, reply: FastifyReply) {
|
||||
if (request.url.startsWith('/svr')) {
|
||||
// @ts-ignore
|
||||
let { accountid } = request.params
|
||||
if (accountid) {
|
||||
request.user = await User.findById(accountid)
|
||||
}
|
||||
} else {
|
||||
if (!request.roles || request.roles.indexOf('anon') == -1) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
let { accountid, sessionid } = request.params
|
||||
//TODO: 增加sessionid的校验
|
||||
// if (!accountid || !sessionid) {
|
||||
// return reply.send({code: 11, msg: 'need accountid and sessionid'});
|
||||
// }
|
||||
if (!accountid) {
|
||||
throw new ZError(2, 'need accountid and sessionid')
|
||||
}
|
||||
// const data = this.jwt.verify(request.token);
|
||||
// if (!data || !data.id) {
|
||||
// return reply.send({code: 10, msg: 'need login'});
|
||||
// }
|
||||
let account = await User.findById(accountid)
|
||||
if (!account) {
|
||||
throw new ZError(5, 'account not found')
|
||||
}
|
||||
if (account.locked) {
|
||||
throw new ZError(4, 'account locked')
|
||||
}
|
||||
request.user = account
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
};
|
||||
})
|
||||
}
|
||||
|
||||
export = fastifyPlugin(apiAuthPlugin, {
|
||||
fastify: '>=3.0.0',
|
||||
name: 'apiauth',
|
||||
fastify: '>=3.0.0',
|
||||
name: 'apiauth'
|
||||
});
|
||||
|
@ -170,6 +170,15 @@ export class RedisClient {
|
||||
});
|
||||
}
|
||||
|
||||
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) => {
|
||||
@ -179,6 +188,25 @@ export class RedisClient {
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -3,26 +3,106 @@ import { BaseConst } from '../constants/BaseConst'
|
||||
|
||||
const MAX_TIME = 5000000000000
|
||||
|
||||
/**
|
||||
* 更新排行榜数据
|
||||
* @param {string} accountid
|
||||
* @param {number} score
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
export async function updateRank(accountid: string, score: number) {
|
||||
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
|
||||
await new RedisClient().zadd(BaseConst.RANK_SCORE, scoreL, accountid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定分数段的玩家
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function usersByScore(min: number, max: number) {
|
||||
return await new RedisClient().zrangebyscore(BaseConst.RANK_SCORE, min, max)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置在游戏中的状态
|
||||
* @param {string} accountid
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
export async function setGameing(accountid: string) {
|
||||
await new RedisClient().setex('gameing_' + accountid, (Date.now() / 1000 | 0) + '', 30)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在游戏中
|
||||
* @param {string} accountid
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function checkGameing(accountid: string) {
|
||||
return await new RedisClient().get('gameing_' + accountid)
|
||||
}
|
||||
|
||||
export async function getRankScore(accountid: string) {
|
||||
/**
|
||||
* 获取指定用户的排名
|
||||
* @param {string} accountid
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function getAccountRank(accountid: string) {
|
||||
return await new RedisClient().zrevrank(BaseConst.RANK_SCORE, accountid)
|
||||
}
|
||||
/**
|
||||
* 获取指定用户的积分
|
||||
* @param {string} accountid
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function getAccountScore(accountid: string) {
|
||||
return await new RedisClient().zscore(BaseConst.RANK_SCORE, accountid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从start到end的玩家列表
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function getRankList(start: number, end: number) {
|
||||
return await new RedisClient().zrevrange(BaseConst.RANK_SCORE, start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帐号附近的几个记录
|
||||
* @param {string} accountid
|
||||
* @return {Promise<unknown>}
|
||||
*/
|
||||
export async function getRankNear(accountid: string) {
|
||||
let rank: any = await getAccountRank(accountid)
|
||||
if (rank == null) {
|
||||
return []
|
||||
}
|
||||
let total: any = await new RedisClient().zcard(BaseConst.RANK_SCORE)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user