From c08d7a465add3babbcad649c65091fc2fb345777 Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:54:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=94=A8=E6=88=B7=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=9A=84=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api.md | 36 +++++++++++++++++++++++--- initdatas/activity_info.json | 32 +++++++++++------------ src/controllers/activity.controller.ts | 26 +++++++++++++------ src/controllers/sign.controller.ts | 32 +++++++++++++++++++++++ src/models/ActivityInfo.ts | 4 +-- src/models/ActivityUser.ts | 9 +++++++ src/services/rank.svr.ts | 30 ++++++++++++++++++--- src/tasks/DiscordConnect.ts | 3 ++- src/tasks/TwitterConnect.ts | 3 ++- src/utils/date.util.ts | 7 +++++ 10 files changed, 146 insertions(+), 36 deletions(-) diff --git a/docs/api.md b/docs/api.md index f6f8bfe..24650bd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -108,7 +108,7 @@ SiweMessage的nonce说明(具体参考例子): "pretasks": ["task id 1"], //前置任务 "score": 0, // 完成任务可获得的积分 "category": "", // 任务分类 - "data": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数 + "cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数 "autoclaim": false // 任务完成后是否自动获取奖励 } ], @@ -204,7 +204,7 @@ body: } ``` -###7.\* 获取任务奖励 +### 7.\* 获取任务奖励 #### Request @@ -277,9 +277,37 @@ body: ```json [ { - "rank": 1, // 排名 + "rank": 1, // 排名, 从1开始 "address": "钱包地址", - "score": 获得的积分 + "invite": "邀请人的地址", + "score": 获得的积分, + "yesterday": 上一日得分 } ] +``` + +### 10.\* 用户状态 + +#### Request + +- URL:`/api/user/state` +- 方法:`GET` +- 头部: + - Authorization: Bearer JWT_token + +#### Response + +```json +{ + "address": "钱包地址", + "boost": 1, // 正常值为1 + "twitterId": "", + "twitterName: "", + "discordId": "", + "discordName": "", + "scoreToday": 100, // 今日获得积分 + "scoreTotal": 200, // 总积分 + "invite": "邀请人address", + "code": "自己的邀请码" + } ``` \ No newline at end of file diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json index c554ac7..09b3e82 100644 --- a/initdatas/activity_info.json +++ b/initdatas/activity_info.json @@ -12,7 +12,7 @@ "score": 100, "category": "", "autoclaim": true, - "data": {}, + "cfg": {"icon": "twitter"}, "params": {} }, { "id": "e2fclylj30vwcpe0szl", @@ -23,8 +23,8 @@ "category": "Social Tasks", "score": 100, "autoclaim": false, - "pretasks": ["TwitterConnect"], - "data": {}, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": {"account": "@_CounterFire", "icon": "twitter"}, "params": {"time": 6, "failRate": 60} }, { "id": "e2feyflj30vwcpe0sjy", @@ -35,8 +35,8 @@ "category": "Social Tasks", "score": 100, "autoclaim": false, - "pretasks": ["TwitterConnect"], - "data": {}, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": {"icon": "twitter"}, "params": {"time": 6, "failRate": 60} }, { "id": "e2fuah0j30vwcpe0my7", @@ -47,32 +47,32 @@ "category": "Social Tasks", "score": 100, "autoclaim": false, - "pretasks": ["TwitterConnect"], - "data": {}, + "pretasks": ["e2yhq2lj30vwcpedv7p"], + "cfg": {"icon": "twitter"}, "params": {"time": 6, "failRate": 60} }, { "id": "e2far3lj30vwcpe0mh7", "task": "DiscordConnect", "title": "Connect Discord", - "type": 2, + "type": 1, "desc": "", "category": "", "score": 100, "autoclaim": false, "pretasks": [], - "data": {}, + "cfg": {"icon": "discord"}, "params": {} }, { "id": "e2far3lj30vwcpe0mf8", "task": "DiscordJoin", "title": "Join Discord", "type": 2, - "desc": "", - "category": "", + "desc": "Join Counter Fire’s official Discord server", + "category": "Social Tasks", "score": 100, "autoclaim": false, "pretasks": [], - "data": {}, + "cfg": {"icon": "discord"}, "params": {"time": 6, "failRate": 60} }, { "id": "e2fak2lj30vwcpe0awc", @@ -83,8 +83,8 @@ "category": "Social Tasks", "score": 100, "autoclaim": false, - "pretasks": ["DiscordJoin"], - "data": {}, + "pretasks": ["e2far3lj30vwcpe0mf8"], + "cfg": {"icon": "discord"}, "params": {"time": 6, "failRate": 60} }, { "id": "e2f7fplj30vwcpe0l98", @@ -96,7 +96,7 @@ "score": 100, "autoclaim": false, "pretasks": [], - "data": {}, + "cfg": {"account": "okx", "icon": "okx"}, "params": {} }, { "id": "e2f7t4lj30vwcpe0ldr", @@ -105,7 +105,7 @@ "show": false, "autoclaim": false, "pretasks": [], - "data": {}, + "cfg": {}, "params": {"score": [100, 20]} }], "startTime": 1702628292366, diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts index 11e1abe..a72f34f 100644 --- a/src/controllers/activity.controller.ts +++ b/src/controllers/activity.controller.ts @@ -4,6 +4,8 @@ import { role, router } from "decorators/router"; import { ActivityInfo } from "models/ActivityInfo"; import { ActivityUser } from "models/ActivityUser"; import { RedisClient } from "redis/RedisClient"; +import { rankKey } from "services/rank.svr"; +import { yesterday } from "utils/date.util"; const MAX_LIMIT = 50 export default class ActivityController extends BaseController { @@ -46,25 +48,33 @@ export default class ActivityController extends BaseController { @role(ROLE_ANON) @router('get /api/activity/leaderboard/:activity/:page') async inviteCode(req) { - let user = req.user; let { page, activity, limit } = req.params page = parseInt(page || '0') limit = parseInt(limit || MAX_LIMIT) - if (page < 0) { - page = 0 - } + page = page < 0 ? 0 : page const start = page * limit const end = start + limit - 1 - let records = await new RedisClient().zrevrange(`${activity}:score`, start, end) + const records = await new RedisClient().zrevrange(`${activity}:score`, start, end) let results: any = [] + const yesterdayKey = rankKey(activity, yesterday()); for (let i = 0; i < records.length; i+=2) { - let id = records[i] + const id = records[i] let score = parseInt(records[i + 1]) - let user = await ActivityUser.findById(id) + const user = await ActivityUser.findById(id) + let invite = '' + if (user.inviteUser) { + const inviteUser = await ActivityUser.findById(user.inviteUser) + if (inviteUser) { + invite = inviteUser.address + } + } + const yesterdayScore = await new RedisClient().zscore(yesterdayKey, id) results.push({ rank: start + i / 2 + 1, address: user?.address || 'unknow', - score + invite, + score, + yesterday: yesterdayScore ? parseInt(yesterdayScore+'') : 0 }) } return results; diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index ff09961..ace78c0 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -5,7 +5,10 @@ import logger from 'logger/logger' import { ActivityUser } from 'models/ActivityUser' import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord' import { LoginRecordQueue } from 'queue/loginrecord.queue' +import { RedisClient } from 'redis/RedisClient' +import { rankKey } from 'services/rank.svr' import {SiweMessage} from 'siwe' +import { yesterday } from 'utils/date.util' import { checkParamsNeeded } from 'utils/net.util' import { aesDecrypt, base58ToHex } from 'utils/security.util' @@ -74,6 +77,35 @@ class SignController extends BaseController { const token = await res.jwtSign({ id: accountData.id, address: accountData.address, activity: accountData.activity }) return { token } } + + @router('get /api/user/state') + async userInfo(req){ + const user = req.user; + const todayKey = rankKey(user.activity, new Date()); + const todayScore = await new RedisClient().zscore(todayKey, user.id) + const totalKey = rankKey(user.activity); + const totalScore = await new RedisClient().zscore(totalKey, user.id) + let invite = '' + if (user.inviteUser) { + const inviteUser = await ActivityUser.findById(user.inviteUser) + if (inviteUser) { + invite = inviteUser.address + } + } + let result = { + address: user.address, + boost: user.boost || 1, + twitterId: user.twitterId, + twitterName: user.twitterName, + discordId: user.discordId, + discordName: user.discordName, + scoreToday: todayScore ? parseInt(todayScore+'') : 0, + scoreTotal: totalScore ? parseInt(totalScore+'') : 0, + invite, + code: user.inviteCode, + } + return result; + } /** * regist user by token from wallet-svr * TODO:: diff --git a/src/models/ActivityInfo.ts b/src/models/ActivityInfo.ts index 558e41b..413f907 100644 --- a/src/models/ActivityInfo.ts +++ b/src/models/ActivityInfo.ts @@ -33,7 +33,7 @@ export class TaskCfg { @prop({default: true}) show: boolean @prop({ type: mongoose.Schema.Types.Mixed }) - data: any + cfg: any @prop({ type: mongoose.Schema.Types.Mixed }) params: any } @@ -80,7 +80,7 @@ class ActivityInfoClass extends BaseModule { score: task.score, category: task.category, autoclaim: task.autoclaim, - data: task.data, + cfg: task.cfg, }) } } diff --git a/src/models/ActivityUser.ts b/src/models/ActivityUser.ts index e89ffc3..b31ce53 100644 --- a/src/models/ActivityUser.ts +++ b/src/models/ActivityUser.ts @@ -79,8 +79,17 @@ class ActivityUserClass extends BaseModule { @prop() public twitterId?: string + + @prop() + public twitterName?: string + @prop() public discordId?: string + @prop() + public discordName?: string + + @prop({default: 1}) + public boost: number @prop() public lastLogin?: Date diff --git a/src/services/rank.svr.ts b/src/services/rank.svr.ts index 7eaa0c4..2494976 100644 --- a/src/services/rank.svr.ts +++ b/src/services/rank.svr.ts @@ -1,5 +1,6 @@ import { ScoreRecord } from "models/ScoreRecord"; import { RedisClient } from "redis/RedisClient"; +import { formatDate } from "utils/date.util"; /** * 更新排行榜 @@ -31,13 +32,34 @@ export const updateRankScore = async ({ data: scoreParams }) await record.save(); - const key = `${activity}:score` - let scoreSaved = await new RedisClient().zscore(key, user) + ''; + const key = rankKey(activity); + await updateRank(key, score, user); + // add daily score + const dailyKey = rankKey(activity, new Date()); + await updateRank(dailyKey, score, user); +} + +/** + * 更新排行榜 + * @param key + * @param score + * @param member + */ +const updateRank = async (key: string, score: number, member: string) => { + let scoreSaved = await new RedisClient().zscore(key, member) + ''; if (scoreSaved) { scoreSaved = scoreSaved.substring(0, scoreSaved.indexOf('.')) } let scoreOld = parseInt(scoreSaved || '0'); score = score + scoreOld; const scoreToSave = score + 1 - (Date.now() / 1000 / 10000000000) - await new RedisClient().zadd(key, scoreToSave, user); -} \ No newline at end of file + await new RedisClient().zadd(key, scoreToSave, member); +} + +export const rankKey = (activity: string, date?: Date) => { + if (!date) { + return `${activity}:score` + } + const dateTag = formatDate(date); + return `${activity}:score:${dateTag}` +} diff --git a/src/tasks/DiscordConnect.ts b/src/tasks/DiscordConnect.ts index 478faec..e659de1 100644 --- a/src/tasks/DiscordConnect.ts +++ b/src/tasks/DiscordConnect.ts @@ -24,7 +24,8 @@ export default class DiscordConnect extends ITask { task.status = TaskStatusEnum.SUCCESS task.timeFinish = Date.now() task.data = res.data.data - task.discordId = res.data.data.userid + this.params.user.discordId = res.data.data.userid + this.params.user.discordName = res.data.data.username try { await this.params.user.save() } catch(err) { diff --git a/src/tasks/TwitterConnect.ts b/src/tasks/TwitterConnect.ts index b42a500..091e0a0 100644 --- a/src/tasks/TwitterConnect.ts +++ b/src/tasks/TwitterConnect.ts @@ -22,7 +22,8 @@ export default class TwitterConnect extends ITask { task.status = TaskStatusEnum.SUCCESS task.timeFinish = Date.now() task.data = res.data.data - task.twitterId = res.data.data.userid + this.params.user.twitterId = res.data.data.userid + this.params.user.twitterName = res.data.data.username try { await this.params.user.save() } catch(err) { diff --git a/src/utils/date.util.ts b/src/utils/date.util.ts index 606fe52..76b98ee 100644 --- a/src/utils/date.util.ts +++ b/src/utils/date.util.ts @@ -4,4 +4,11 @@ export const formatDate = (date: Date): string => { const month = (date.getMonth() + 1 + '').padStart(2, '0'); const day = (date.getDate() + '').padStart(2, '0'); return `${year}${month}${day}`; +}; + +// get formated datestring of yesterday +export const yesterday = () => { + const date = new Date(); + date.setDate(date.getDate() - 1); + return date; }; \ No newline at end of file