增加排行榜相关接口
This commit is contained in:
parent
79ae8c195d
commit
12c78ab264
@ -8,6 +8,7 @@ import config from 'config/config'
|
||||
import { RedisClient } from './redis/RedisClient'
|
||||
import { GameInfoCache } from './cache/GameInfoCache'
|
||||
import SubscribeSchedule from './schedule/subscribe.schedule'
|
||||
import RankSchedule from './schedule/rank.schedule'
|
||||
|
||||
const zReqParserPlugin = require('plugins/zReqParser')
|
||||
|
||||
@ -129,13 +130,17 @@ export class ApiServer {
|
||||
* @private
|
||||
*/
|
||||
private setFormatSend() {
|
||||
this.server.addHook('preSerialization', async (request: FastifyRequest, reply: FastifyReply, payload) => {
|
||||
this.server.addHook('preSerialization', async (request: FastifyRequest, reply: FastifyReply, payload: any) => {
|
||||
reply.header('X-Powered-By', 'PHP/5.4.16')
|
||||
// @ts-ignore
|
||||
if (!payload.errcode) {
|
||||
payload = {
|
||||
errcode: 0,
|
||||
data: payload,
|
||||
if (payload.nt) {
|
||||
delete payload.nt
|
||||
} else {
|
||||
payload = {
|
||||
errcode: 0,
|
||||
data: payload,
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload
|
||||
@ -148,6 +153,7 @@ export class ApiServer {
|
||||
|
||||
private async initSchedules() {
|
||||
await new SubscribeSchedule().scheduleAll()
|
||||
await new RankSchedule().scheduleAll()
|
||||
}
|
||||
|
||||
public async start() {
|
||||
|
25
src/cache/GameCfgCache.ts
vendored
Normal file
25
src/cache/GameCfgCache.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
import { singleton } from '../decorators/singleton'
|
||||
|
||||
export interface CfgInfo {
|
||||
time: number
|
||||
datas: any[]
|
||||
}
|
||||
const MAX_TIME = 5 * 60 * 1000
|
||||
@singleton
|
||||
export class GameCfgCache {
|
||||
private _cache: Map<[string, string], any> = new Map()
|
||||
|
||||
public getCfg(gameId: string, channel: string) {
|
||||
const now = Date.now()
|
||||
let info = this._cache.get([gameId, channel])
|
||||
if (info && now - info.time >= MAX_TIME) {
|
||||
return null
|
||||
}
|
||||
return info?.datas
|
||||
}
|
||||
|
||||
public updateCfg(gameId: string, channel: string, datas: any) {
|
||||
let info = { time: Date.now(), datas }
|
||||
this._cache.set([gameId, channel], info)
|
||||
}
|
||||
}
|
1
src/constants/BaseConsts.ts
Normal file
1
src/constants/BaseConsts.ts
Normal file
@ -0,0 +1 @@
|
||||
export const RANK_SCORE = 'r'
|
100
src/controllers/rank.controller.ts
Normal file
100
src/controllers/rank.controller.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import BaseController from '../common/base.controller'
|
||||
import { role, router } from '../decorators/router'
|
||||
import { getAccountRank, getAccountScore, getRankCount, getRankList, rankKey, updateRank } from '../services/rank.svr'
|
||||
import { RankUser } from '../models/RankUser'
|
||||
import { fetchRankCfg, randomUsers } from '../services/jcfw.svr'
|
||||
import RankSchedule from '../schedule/rank.schedule'
|
||||
|
||||
class RankController extends BaseController {
|
||||
@role('anon')
|
||||
@router('post /api/svr/games/rank')
|
||||
async reqRankList(req: any) {
|
||||
let { gameId, channelId, limit, skip, accountId, all, rankType } = req.params
|
||||
const { min, max, type, valType } = await fetchRankCfg(gameId, channelId)
|
||||
skip = +skip || 0
|
||||
limit = +limit || 20
|
||||
all = all || type > 0
|
||||
rankType = rankType || rankKey(gameId, channelId, all)
|
||||
let datas: any = await getRankList(skip, limit, rankType)
|
||||
const rankList = await RankUser.parseRankList(datas)
|
||||
const userInfo = await RankUser.getByAccountID(accountId)
|
||||
let userRank = (await getAccountRank(accountId, rankType)) ?? 999
|
||||
let userScore = (await getAccountScore(accountId, rankType)) || 0
|
||||
let rankTotal = await getRankCount(rankType)
|
||||
return {
|
||||
nt: 1,
|
||||
errcode: 0,
|
||||
errmsg: '',
|
||||
records: rankList,
|
||||
total: rankTotal,
|
||||
userRank,
|
||||
userScore,
|
||||
userTitle: userInfo?.rankTitle,
|
||||
}
|
||||
}
|
||||
|
||||
@role('anon')
|
||||
@router('post /api/svr/games/rank/update')
|
||||
async reqUpdateRank(req: any) {
|
||||
let { gameId, channelId, accountId, score, needType, all, rankTitle, nickname, avatar, extInfo } = req.params
|
||||
const { min, max, type, valType } = await fetchRankCfg(gameId, channelId)
|
||||
all = all || type > 0
|
||||
needType = needType || 0
|
||||
await fetchRankCfg(gameId, channelId)
|
||||
const weekKey = rankKey(gameId, channelId, false)
|
||||
let week = await updateRank(accountId, score, weekKey, valType)
|
||||
const totalKey = rankKey(gameId, channelId, true)
|
||||
let total = await updateRank(accountId, score, totalKey, valType)
|
||||
const key = all ? totalKey : weekKey
|
||||
const totalCount = all ? total.total : week.total
|
||||
let userRank = (await getAccountRank(accountId, key)) as number
|
||||
let userScore = (await getAccountScore(accountId, key)) as number
|
||||
await RankUser.insertOrUpdate({ gameId, channelId, accountId }, { rankTitle, nickname, avatar, extInfo })
|
||||
if (!needType) {
|
||||
return { nt: 1, errcode: 0, errmsg: '', userRank, userScore }
|
||||
} else {
|
||||
let preRecord = null
|
||||
let nextRecord = null
|
||||
if (userRank > 0) {
|
||||
let preDatas: any = await getRankList(userRank - 1, userRank, key)
|
||||
let preList = await RankUser.parseRankList(preDatas)
|
||||
preRecord = preList[0]
|
||||
}
|
||||
if (userRank < totalCount - 1) {
|
||||
let nextDatas: any = await getRankList(userRank, userRank + 1, key)
|
||||
let nextList = await RankUser.parseRankList(nextDatas)
|
||||
nextRecord = nextList[0]
|
||||
}
|
||||
return {
|
||||
nt: 1,
|
||||
errcode: 0,
|
||||
errmsg: '',
|
||||
preRecord: preRecord,
|
||||
nextRecord: nextRecord,
|
||||
userRank,
|
||||
userScore,
|
||||
userTitle: rankTitle,
|
||||
over: all ? total.over : week.over,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@role('anon')
|
||||
@router('get /api/svr/games/rank/challenge/:score/:accountId/:count')
|
||||
@router('get /api/svr/games/rank/challenge/:score/:accountId')
|
||||
async challenge(req: any) {
|
||||
let { score, accountId, count } = req.params
|
||||
count = count || 10
|
||||
count = count > 100 ? 100 : count
|
||||
const users = await randomUsers(accountId, count)
|
||||
return { nt: 1, errcode: 0, errmsg: '', users: users }
|
||||
}
|
||||
|
||||
@role('anon')
|
||||
@router('post /api/svr/games/rank/init')
|
||||
async update(req: any) {
|
||||
let { gameId, channelId } = req.params
|
||||
await new RankSchedule().initRankData({ gameId, channelId })
|
||||
return {}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import { ObjectId } from 'bson'
|
||||
import { isTrue } from '../utils/string.util'
|
||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
||||
|
||||
const jsonExcludeKeys = ['updatedAt', '__v']
|
||||
const jsonExcludeKeys = ['updatedAt', '__v', 'isBot']
|
||||
const saveExcludeKeys = ['createdAt', 'updatedAt', '__v', '_id']
|
||||
|
||||
export abstract class BaseModule extends FindOrCreate {
|
||||
|
89
src/models/RankUser.ts
Normal file
89
src/models/RankUser.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { dbconn } from '../decorators/dbconn'
|
||||
import { getModelForClass, index, modelOptions, prop, ReturnModelType } from '@typegoose/typegoose'
|
||||
import { BaseModule } from './Base'
|
||||
import { customAlphabet } from 'nanoid'
|
||||
const nanoid = customAlphabet('2345678abcdefghjkmnpqrstwxy', 32)
|
||||
|
||||
const jsonExcludeKeys = ['updatedAt', '__v', '_id', 'createdAt', 'isBot', 'type']
|
||||
|
||||
/**
|
||||
* 用于存储排行榜的用户信息
|
||||
* 从redis中获取accountId和积分, 然后从这里获取其他信息
|
||||
*/
|
||||
@dbconn()
|
||||
@index({ accountId: 1 }, { unique: true })
|
||||
@index({ gameId: 1, channelId: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'rank_user', timestamps: true },
|
||||
})
|
||||
class RankUserClass extends BaseModule {
|
||||
@prop()
|
||||
accountId: string
|
||||
@prop()
|
||||
gameId: string
|
||||
@prop()
|
||||
channelId: string
|
||||
@prop({ default: false })
|
||||
isBot: boolean
|
||||
@prop()
|
||||
nickname: string
|
||||
@prop()
|
||||
avatar: string
|
||||
|
||||
@prop()
|
||||
rankTitle: string
|
||||
@prop()
|
||||
extInfo: string
|
||||
/**
|
||||
* @type {number} 0: 只在总榜有, 1: 在周榜和总榜都有
|
||||
*/
|
||||
@prop({ default: 1 })
|
||||
type: number
|
||||
|
||||
public static async getByAccountID(this: ReturnModelType<typeof RankUserClass>, accountId: string) {
|
||||
let records = await this.find({ accountId }).limit(1)
|
||||
return records.length > 0 ? records[0] : null
|
||||
}
|
||||
|
||||
public static async userMapByAccountIDS(this: ReturnModelType<typeof RankUserClass>, accountIds: string[]) {
|
||||
const records = await this.find({ accountId: { $in: accountIds } })
|
||||
let map: Map<string, any> = new Map()
|
||||
for (let record of records) {
|
||||
map.set(record.accountId, record.toJson())
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
public static async parseRankList(this: ReturnModelType<typeof RankUserClass>, datas: any[]) {
|
||||
let rankList: { accountId: string; score: any }[] = []
|
||||
let accountIDS: string[] = []
|
||||
for (let i = 0, l = datas.length; i < l; i += 2) {
|
||||
rankList.push({ accountId: datas[i] as string, score: datas[i + 1] << 0 })
|
||||
accountIDS.push(datas[i])
|
||||
}
|
||||
const userMap = await RankUser.userMapByAccountIDS(accountIDS)
|
||||
for (let d of rankList) {
|
||||
if (userMap.has(d.accountId)) {
|
||||
Object.assign(d, userMap.get(d.accountId))
|
||||
}
|
||||
}
|
||||
return rankList
|
||||
}
|
||||
|
||||
public static randomAccountId(gameId: string, channelId: string) {
|
||||
return `${channelId}_${gameId}_${nanoid()}`
|
||||
}
|
||||
|
||||
public toJson(): any {
|
||||
let result: any = {}
|
||||
// @ts-ignore
|
||||
for (let key in this._doc) {
|
||||
if (jsonExcludeKeys.indexOf(key) == -1) {
|
||||
result[key] = this[key]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export const RankUser = getModelForClass(RankUserClass, { existingConnection: RankUserClass.db })
|
92
src/schedule/rank.schedule.ts
Normal file
92
src/schedule/rank.schedule.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { singleton } from '../decorators/singleton'
|
||||
import * as schedule from 'node-schedule'
|
||||
import logger from '../logger/logger'
|
||||
import { RankUser } from '../models/RankUser'
|
||||
import { delRankList, rankKey, updateRank } from '../services/rank.svr'
|
||||
import { fetchRankCfg, randomUsers } from '../services/jcfw.svr'
|
||||
|
||||
// 每天晚上1点更新
|
||||
const WORLD_RANK_TASK = '1 0 1 * * *'
|
||||
// 总榜机器人总数
|
||||
const MAX_ROBOT_COUNT = 40
|
||||
@singleton
|
||||
export default class RankSchedule {
|
||||
/**
|
||||
* 取出原来的机器人列表
|
||||
* 移除n个, 同时补充n个机器人
|
||||
* 补充的n个机器人更新至总榜
|
||||
*/
|
||||
async initRankData({ gameId, channelId }) {
|
||||
let oldList = await RankUser.find({ gameId, channelId, isBot: true, type: 1 })
|
||||
if (oldList.length > 0) {
|
||||
const removeCount = Math.random2(5, 10) | 0
|
||||
let deleteCount = 0
|
||||
let tmpList = []
|
||||
for (let i = 0, l = oldList.length; i < l; i++) {
|
||||
let record = oldList[i]
|
||||
if (Math.random() > 0.5 && deleteCount <= removeCount) {
|
||||
record.type = 0
|
||||
await record.save()
|
||||
deleteCount++
|
||||
} else {
|
||||
tmpList.push(record)
|
||||
}
|
||||
}
|
||||
oldList = tmpList
|
||||
}
|
||||
let needCount = MAX_ROBOT_COUNT - oldList.length
|
||||
try {
|
||||
let users = await randomUsers('', needCount)
|
||||
for (const user of users) {
|
||||
let accountId = RankUser.randomAccountId(gameId, channelId)
|
||||
let record = await RankUser.insertOrUpdate(
|
||||
{ gameId, channelId, accountId },
|
||||
{ nickname: user.nickname, avatar: user.avatar_url, isBot: true, type: 1 },
|
||||
)
|
||||
oldList.push(record)
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('error get random user', err)
|
||||
}
|
||||
|
||||
let weekKey = rankKey(gameId, channelId, false)
|
||||
await delRankList(weekKey)
|
||||
let totalKey = rankKey(gameId, channelId, true)
|
||||
const { min, max, valType } = await fetchRankCfg(gameId, channelId)
|
||||
for (let record of oldList) {
|
||||
try {
|
||||
let score = Math.random2(min, max) | 0
|
||||
await updateRank(record.accountId, score, weekKey, valType)
|
||||
await updateRank(record.accountId, score, totalKey, valType)
|
||||
} catch (err) {
|
||||
logger.error('update exists rank error')
|
||||
}
|
||||
}
|
||||
}
|
||||
async parseAllRecord() {
|
||||
logger.info('=================begin update world rank.=================')
|
||||
let gameList = []
|
||||
try {
|
||||
gameList = await RankUser.aggregate([
|
||||
{ $match: { gameId: { $exists: true }, channelId: { $exists: true } } },
|
||||
{ $group: { _id: { gameId: '$gameId', channelId: '$channelId' } } },
|
||||
])
|
||||
} catch (err) {
|
||||
logger.error('err get game list for update world rank schedule', err)
|
||||
}
|
||||
for (const game of gameList) {
|
||||
try {
|
||||
await this.initRankData({ gameId: game._id.gameId, channelId: game._id.channelId })
|
||||
} catch (err) {
|
||||
logger.error(`err init game world rank, ${game}`, err)
|
||||
}
|
||||
}
|
||||
logger.info('=================end update world rank.=================')
|
||||
}
|
||||
scheduleAll() {
|
||||
logger.log('已添加更新世界排行榜的定时任务')
|
||||
const job = schedule.scheduleJob(WORLD_RANK_TASK, async () => {
|
||||
await this.parseAllRecord()
|
||||
})
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import { RedisClient } from '../redis/RedisClient'
|
||||
import exp from 'constants'
|
||||
import crypto from 'crypto'
|
||||
import axios from 'axios'
|
||||
import { GameCfgCache } from '../cache/GameCfgCache'
|
||||
|
||||
/**
|
||||
* 获取某游戏的所有客户端配置
|
||||
@ -100,3 +102,71 @@ export async function getSubscribeMsg(gameId: string, channel: string, type: str
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function createSign(secretKey: string, paramStr: string, timestamp: number) {
|
||||
paramStr = `${paramStr}:${timestamp}${secretKey}`
|
||||
return crypto.createHash('md5').update(paramStr, 'utf8').digest('hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机用户
|
||||
* @param {string} accountId
|
||||
* @param {number} num
|
||||
*/
|
||||
export async function randomUsers(accountId: string, num: number) {
|
||||
num = num || 10
|
||||
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}`
|
||||
const { data } = await axios.get(link)
|
||||
if (data.errcode === 0) {
|
||||
return data.robot_list
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
const RANK_MAX_KEY = 'world_rank_max'
|
||||
const RANK_MIN_KEY = 'world_rank_min'
|
||||
const RANK_TYPE_KEY = 'world_rank_type'
|
||||
const RANK_VALTYPE_KEY = 'world_rank_valtype'
|
||||
/**
|
||||
* 获取世界排行榜的配置
|
||||
* @param {string} gameId
|
||||
* @param {string} channel
|
||||
* @return min 生成排行榜的最小值
|
||||
* @return max 生成排行榜的最大值
|
||||
* @return type 排行榜类型, 0: 周榜, 1: 总榜
|
||||
* @return valType: 排行榜值存储类型, 0: 存最大值, 1: 存最新值
|
||||
*/
|
||||
export async function fetchRankCfg(gameId: string, channel: string) {
|
||||
let cfgs = new GameCfgCache().getCfg(gameId, channel)
|
||||
if (!cfgs) {
|
||||
cfgs = await getGameSvrCfg(gameId, channel)
|
||||
new GameCfgCache().updateCfg(gameId, channel, cfgs)
|
||||
}
|
||||
let type = 0
|
||||
let max = 1000
|
||||
let min = 500
|
||||
let valType = 0
|
||||
for (let i = 0, l = cfgs.length; i < l; i++) {
|
||||
const cfg = cfgs[i]
|
||||
switch (cfg.key) {
|
||||
case RANK_MAX_KEY:
|
||||
max = +cfg.value
|
||||
break
|
||||
case RANK_MIN_KEY:
|
||||
min = +cfg.value
|
||||
break
|
||||
case RANK_TYPE_KEY:
|
||||
type = +cfg.value
|
||||
break
|
||||
case RANK_VALTYPE_KEY:
|
||||
valType = +cfg.value
|
||||
break
|
||||
}
|
||||
}
|
||||
return { min, max, type, valType }
|
||||
}
|
||||
|
153
src/services/rank.svr.ts
Normal file
153
src/services/rank.svr.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { RedisClient } from '../redis/RedisClient'
|
||||
import { RANK_SCORE } from '../constants/BaseConsts'
|
||||
|
||||
const MAX_TIME = 5000000000000
|
||||
|
||||
function generateRankKey(subKey?: string) {
|
||||
if (subKey) {
|
||||
return `${RANK_SCORE}_${subKey}`
|
||||
} else {
|
||||
return RANK_SCORE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新排行榜数据
|
||||
* @param subKey
|
||||
* @param {string} accountId
|
||||
* @param {number} score
|
||||
* @param valType 值存储类型, 0: 存最大值, 1: 存最新值
|
||||
*/
|
||||
export async function updateRank(accountId: string, score: number, subKey: string, valType: number) {
|
||||
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
|
||||
const key = generateRankKey(subKey)
|
||||
let scoreResult = score
|
||||
if (valType === 0) {
|
||||
let scoreOld = await getAccountScore(accountId, subKey)
|
||||
if (score > scoreOld) {
|
||||
await new RedisClient().zadd(key, scoreL, accountId)
|
||||
} else {
|
||||
scoreResult = scoreOld
|
||||
}
|
||||
} else {
|
||||
await new RedisClient().zadd(key, scoreL, accountId)
|
||||
}
|
||||
const countTotal = (await new RedisClient().zcard(key)) as number
|
||||
const countOver = (await new RedisClient().zcount(key, 0, scoreL)) as number
|
||||
const over = countTotal ? countOver / countTotal : 0.99
|
||||
return { scoreReal: scoreResult, over, total: countTotal }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户的积分
|
||||
* @param {string} accountId
|
||||
* @param subKey
|
||||
*/
|
||||
export async function getAccountScore(accountId: string, subKey?: string) {
|
||||
let score = await new RedisClient().zscore(generateRankKey(subKey), accountId)
|
||||
if (score) {
|
||||
return +score << 0
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取超越只
|
||||
* @param {string} accountId
|
||||
* @param {number} score
|
||||
* @param {string} subKey
|
||||
* @return {Promise<number>}
|
||||
*/
|
||||
export async function currentIdx(accountId: string, score: number, subKey?: string) {
|
||||
const key = generateRankKey(subKey)
|
||||
let countTotal = (await new RedisClient().zcard(key)) as number
|
||||
let scoreL = parseFloat(`${score | 0}.${MAX_TIME - Date.now()}`)
|
||||
let countOver = (await new RedisClient().zcount(key, 0, scoreL)) as number
|
||||
return countOver / countTotal
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定分数段的玩家
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @param subKey
|
||||
*/
|
||||
export async function usersByScore(min: number, max: number, subKey?: string) {
|
||||
return await new RedisClient().zrangebyscore(generateRankKey(subKey), min, max)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定用户的排名
|
||||
* @param {string} accountId
|
||||
* @param subKey
|
||||
*/
|
||||
export async function getAccountRank(accountId: string, subKey?: string) {
|
||||
return await new RedisClient().zrevrank(generateRankKey(subKey), accountId)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取从start到end的玩家列表
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @param subKey
|
||||
*/
|
||||
export async function getRankList(start: number, end: number, subKey?: string) {
|
||||
return await new RedisClient().zrevrange(generateRankKey(subKey), start, end)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某排行榜的成员总数
|
||||
* @param {string} subKey
|
||||
*/
|
||||
export async function getRankCount(subKey?: string) {
|
||||
return await new RedisClient().zcard(generateRankKey(subKey))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帐号附近的几个记录
|
||||
* @param {string} accountId
|
||||
* @param subKey
|
||||
*/
|
||||
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))
|
||||
}
|
||||
|
||||
export function rankKey(gameId: string, channelId: string, all: boolean) {
|
||||
return all ? `${gameId}_${channelId}_total` : `${gameId}_${channelId}_week`
|
||||
}
|
@ -40,3 +40,29 @@ export function todayStart() {
|
||||
export function todayEnd() {
|
||||
return todayStart() + ONE_DAY_MILLISECOND - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周第一天和最后一天(周一开始)
|
||||
* @return {{startDay: string, endDay: string}}
|
||||
*/
|
||||
export function getThisWeekData() {
|
||||
return weekData(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前后n周的周一和周日的日期
|
||||
* @param {number} n 0为当前周, 1为下一周, -1为上周
|
||||
* @return {{startDay: string, endDay: string}}
|
||||
*/
|
||||
export function weekData(n: number) {
|
||||
const weekData = { startDay: '', endDay: '' }
|
||||
const date = new Date()
|
||||
// 上周一的日期
|
||||
date.setDate(date.getDate() + 7 * n - date.getDay() + 1)
|
||||
weekData.startDay = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
|
||||
|
||||
// 上周日的日期
|
||||
date.setDate(date.getDate() + 6)
|
||||
weekData.endDay = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
|
||||
return weekData
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user