添加排行榜相关接口
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
|
* @param arr
|
||||||
*/
|
*/
|
||||||
difference?<T>(arr: T[]): T[];
|
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, {
|
Object.defineProperties(Array.prototype, {
|
||||||
@ -858,6 +865,18 @@ Object.defineProperties(Array.prototype, {
|
|||||||
return [...set1];
|
return [...set1];
|
||||||
},
|
},
|
||||||
writable: true
|
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 { RedisClient } from '../redis/RedisClient'
|
||||||
import {
|
import {
|
||||||
checkGameing,
|
checkGameing,
|
||||||
getRankScore,
|
getAccountRank,
|
||||||
setGameing,
|
setGameing,
|
||||||
usersByScore
|
usersByScore
|
||||||
} from '../service/rank'
|
} from '../service/rank'
|
||||||
@ -37,7 +37,7 @@ export default class AccountController extends BaseController {
|
|||||||
result.season_score = account.season_score
|
result.season_score = account.season_score
|
||||||
result.season_data = account.season_data
|
result.season_data = account.season_data
|
||||||
result.match_score = account.getMatchScore()
|
result.match_score = account.getMatchScore()
|
||||||
let rank = await getRankScore(accountid)
|
let rank = await getAccountRank(accountid)
|
||||||
if (typeof rank === 'string') {
|
if (typeof rank === 'string') {
|
||||||
result.rank = parseInt(rank)
|
result.rank = parseInt(rank)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,77 @@
|
|||||||
import BaseController from '../common/base.controller'
|
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 {
|
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 {
|
import {
|
||||||
FastifyInstance,
|
FastifyInstance,
|
||||||
FastifyPluginAsync, FastifyReply,
|
FastifyPluginAsync,
|
||||||
FastifyRequest,
|
FastifyReply,
|
||||||
} from 'fastify';
|
FastifyRequest
|
||||||
import fastifyPlugin from 'fastify-plugin';
|
} from 'fastify'
|
||||||
import {User} from '../models/User';
|
import fastifyPlugin from 'fastify-plugin'
|
||||||
import {ZError} from "../common/ZError";
|
import { User } from '../models/User'
|
||||||
|
import { ZError } from '../common/ZError'
|
||||||
|
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
interface FastifyInstance {
|
interface FastifyInstance {
|
||||||
apiAuth: (request: FastifyRequest, reply: FastifyReply) => {};
|
apiAuth: (request: FastifyRequest, reply: FastifyReply) => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
session?: {[key: string]: any};
|
session?: { [key: string]: any };
|
||||||
roles?: any,
|
roles?: any,
|
||||||
user?: any
|
user?: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiAuthPlugin: FastifyPluginAsync = async function(
|
const apiAuthPlugin: FastifyPluginAsync = async function (
|
||||||
fastify: FastifyInstance,
|
fastify: FastifyInstance,
|
||||||
options?: any
|
options?: any
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// 只有路由配置的role为anon才不需要过滤
|
// 只有路由配置的role为anon才不需要过滤
|
||||||
fastify.decorate("apiAuth", async function(request: FastifyRequest, reply: FastifyReply) {
|
fastify.decorate('apiAuth', async function (request: FastifyRequest, reply: FastifyReply) {
|
||||||
if (request.url.startsWith('/svr')) {
|
if (request.url.startsWith('/svr')) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let { accountid } = request.params;
|
let { accountid } = request.params
|
||||||
if (accountid) {
|
if (accountid) {
|
||||||
request.user = await User.findById(accountid);
|
request.user = await User.findById(accountid)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!request.roles || request.roles.indexOf('anon') == -1) {
|
if (!request.roles || request.roles.indexOf('anon') == -1) {
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let { accountid, sessionid } = request.params;
|
let { accountid, sessionid } = request.params
|
||||||
//TODO: 增加sessionid的校验
|
//TODO: 增加sessionid的校验
|
||||||
// if (!accountid || !sessionid) {
|
// if (!accountid || !sessionid) {
|
||||||
// return reply.send({code: 11, msg: 'need accountid and sessionid'});
|
// return reply.send({code: 11, msg: 'need accountid and sessionid'});
|
||||||
// }
|
// }
|
||||||
if (!accountid) {
|
if (!accountid) {
|
||||||
return reply.send({code: 2, msg: 'need accountid and sessionid'});
|
throw new ZError(2, 'need accountid and sessionid')
|
||||||
}
|
}
|
||||||
// const data = this.jwt.verify(request.token);
|
// const data = this.jwt.verify(request.token);
|
||||||
// if (!data || !data.id) {
|
// if (!data || !data.id) {
|
||||||
// return reply.send({code: 10, msg: 'need login'});
|
// return reply.send({code: 10, msg: 'need login'});
|
||||||
// }
|
// }
|
||||||
let account = await User.findById(accountid);
|
let account = await User.findById(accountid)
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return reply.send({code: 5, msg: 'account not found'});
|
throw new ZError(5, 'account not found')
|
||||||
}
|
}
|
||||||
if (account.locked) {
|
if (account.locked) {
|
||||||
return reply.send({code: 4, msg: 'account locked'});
|
throw new ZError(4, 'account locked')
|
||||||
}
|
}
|
||||||
request.user = account;
|
request.user = account
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reply.send({code: 401, msg: 'need auth'})
|
throw err
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
export = fastifyPlugin(apiAuthPlugin, {
|
export = fastifyPlugin(apiAuthPlugin, {
|
||||||
fastify: '>=3.0.0',
|
fastify: '>=3.0.0',
|
||||||
name: 'apiauth',
|
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) {
|
public async zrevrank(key: string, member: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.pub.zrevrank(key, member, (err, data) => {
|
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) {
|
public async hset(key: string, field: string, value: string) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.pub.hset(key, field, value, resolve);
|
this.pub.hset(key, field, value, resolve);
|
||||||
|
@ -3,26 +3,106 @@ import { BaseConst } from '../constants/BaseConst'
|
|||||||
|
|
||||||
const MAX_TIME = 5000000000000
|
const MAX_TIME = 5000000000000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新排行榜数据
|
||||||
|
* @param {string} accountid
|
||||||
|
* @param {number} score
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
export async function updateRank(accountid: string, score: number) {
|
export async function updateRank(accountid: string, score: number) {
|
||||||
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
|
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
|
||||||
await new RedisClient().zadd(BaseConst.RANK_SCORE, scoreL, accountid)
|
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) {
|
export async function usersByScore(min: number, max: number) {
|
||||||
return await new RedisClient().zrangebyscore(BaseConst.RANK_SCORE, min, max)
|
return await new RedisClient().zrangebyscore(BaseConst.RANK_SCORE, min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置在游戏中的状态
|
||||||
|
* @param {string} accountid
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
export async function setGameing(accountid: string) {
|
export async function setGameing(accountid: string) {
|
||||||
await new RedisClient().setex('gameing_' + accountid, (Date.now() / 1000 | 0) + '', 30)
|
await new RedisClient().setex('gameing_' + accountid, (Date.now() / 1000 | 0) + '', 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否在游戏中
|
||||||
|
* @param {string} accountid
|
||||||
|
* @return {Promise<unknown>}
|
||||||
|
*/
|
||||||
export async function checkGameing(accountid: string) {
|
export async function checkGameing(accountid: string) {
|
||||||
return await new RedisClient().get('gameing_' + accountid)
|
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)
|
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