增加用户状态的接口

This commit is contained in:
CounterFire2023 2024-01-05 12:54:57 +08:00
parent eb9ce07ff5
commit c08d7a465a
10 changed files with 146 additions and 36 deletions

View File

@ -108,7 +108,7 @@ SiweMessage的nonce说明(具体参考例子):
"pretasks": ["task id 1"], //前置任务 "pretasks": ["task id 1"], //前置任务
"score": 0, // 完成任务可获得的积分 "score": 0, // 完成任务可获得的积分
"category": "", // 任务分类 "category": "", // 任务分类
"data": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数 "cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数
"autoclaim": false // 任务完成后是否自动获取奖励 "autoclaim": false // 任务完成后是否自动获取奖励
} }
], ],
@ -204,7 +204,7 @@ body:
} }
``` ```
###7.\* 获取任务奖励 ### 7.\* 获取任务奖励
#### Request #### Request
@ -277,9 +277,37 @@ body:
```json ```json
[ [
{ {
"rank": 1, // 排名 "rank": 1, // 排名, 从1开始
"address": "钱包地址", "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": "自己的邀请码"
}
```

View File

@ -12,7 +12,7 @@
"score": 100, "score": 100,
"category": "", "category": "",
"autoclaim": true, "autoclaim": true,
"data": {}, "cfg": {"icon": "twitter"},
"params": {} "params": {}
}, { }, {
"id": "e2fclylj30vwcpe0szl", "id": "e2fclylj30vwcpe0szl",
@ -23,8 +23,8 @@
"category": "Social Tasks", "category": "Social Tasks",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": ["TwitterConnect"], "pretasks": ["e2yhq2lj30vwcpedv7p"],
"data": {}, "cfg": {"account": "@_CounterFire", "icon": "twitter"},
"params": {"time": 6, "failRate": 60} "params": {"time": 6, "failRate": 60}
}, { }, {
"id": "e2feyflj30vwcpe0sjy", "id": "e2feyflj30vwcpe0sjy",
@ -35,8 +35,8 @@
"category": "Social Tasks", "category": "Social Tasks",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": ["TwitterConnect"], "pretasks": ["e2yhq2lj30vwcpedv7p"],
"data": {}, "cfg": {"icon": "twitter"},
"params": {"time": 6, "failRate": 60} "params": {"time": 6, "failRate": 60}
}, { }, {
"id": "e2fuah0j30vwcpe0my7", "id": "e2fuah0j30vwcpe0my7",
@ -47,32 +47,32 @@
"category": "Social Tasks", "category": "Social Tasks",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": ["TwitterConnect"], "pretasks": ["e2yhq2lj30vwcpedv7p"],
"data": {}, "cfg": {"icon": "twitter"},
"params": {"time": 6, "failRate": 60} "params": {"time": 6, "failRate": 60}
}, { }, {
"id": "e2far3lj30vwcpe0mh7", "id": "e2far3lj30vwcpe0mh7",
"task": "DiscordConnect", "task": "DiscordConnect",
"title": "Connect Discord", "title": "Connect Discord",
"type": 2, "type": 1,
"desc": "", "desc": "",
"category": "", "category": "",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": [], "pretasks": [],
"data": {}, "cfg": {"icon": "discord"},
"params": {} "params": {}
}, { }, {
"id": "e2far3lj30vwcpe0mf8", "id": "e2far3lj30vwcpe0mf8",
"task": "DiscordJoin", "task": "DiscordJoin",
"title": "Join Discord", "title": "Join Discord",
"type": 2, "type": 2,
"desc": "", "desc": "Join Counter Fires official Discord server",
"category": "", "category": "Social Tasks",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": [], "pretasks": [],
"data": {}, "cfg": {"icon": "discord"},
"params": {"time": 6, "failRate": 60} "params": {"time": 6, "failRate": 60}
}, { }, {
"id": "e2fak2lj30vwcpe0awc", "id": "e2fak2lj30vwcpe0awc",
@ -83,8 +83,8 @@
"category": "Social Tasks", "category": "Social Tasks",
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": ["DiscordJoin"], "pretasks": ["e2far3lj30vwcpe0mf8"],
"data": {}, "cfg": {"icon": "discord"},
"params": {"time": 6, "failRate": 60} "params": {"time": 6, "failRate": 60}
}, { }, {
"id": "e2f7fplj30vwcpe0l98", "id": "e2f7fplj30vwcpe0l98",
@ -96,7 +96,7 @@
"score": 100, "score": 100,
"autoclaim": false, "autoclaim": false,
"pretasks": [], "pretasks": [],
"data": {}, "cfg": {"account": "okx", "icon": "okx"},
"params": {} "params": {}
}, { }, {
"id": "e2f7t4lj30vwcpe0ldr", "id": "e2f7t4lj30vwcpe0ldr",
@ -105,7 +105,7 @@
"show": false, "show": false,
"autoclaim": false, "autoclaim": false,
"pretasks": [], "pretasks": [],
"data": {}, "cfg": {},
"params": {"score": [100, 20]} "params": {"score": [100, 20]}
}], }],
"startTime": 1702628292366, "startTime": 1702628292366,

View File

@ -4,6 +4,8 @@ import { role, router } from "decorators/router";
import { ActivityInfo } from "models/ActivityInfo"; import { ActivityInfo } from "models/ActivityInfo";
import { ActivityUser } from "models/ActivityUser"; import { ActivityUser } from "models/ActivityUser";
import { RedisClient } from "redis/RedisClient"; import { RedisClient } from "redis/RedisClient";
import { rankKey } from "services/rank.svr";
import { yesterday } from "utils/date.util";
const MAX_LIMIT = 50 const MAX_LIMIT = 50
export default class ActivityController extends BaseController { export default class ActivityController extends BaseController {
@ -46,25 +48,33 @@ export default class ActivityController extends BaseController {
@role(ROLE_ANON) @role(ROLE_ANON)
@router('get /api/activity/leaderboard/:activity/:page') @router('get /api/activity/leaderboard/:activity/:page')
async inviteCode(req) { async inviteCode(req) {
let user = req.user;
let { page, activity, limit } = req.params let { page, activity, limit } = req.params
page = parseInt(page || '0') page = parseInt(page || '0')
limit = parseInt(limit || MAX_LIMIT) limit = parseInt(limit || MAX_LIMIT)
if (page < 0) { page = page < 0 ? 0 : page
page = 0
}
const start = page * limit const start = page * limit
const end = start + limit - 1 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 = [] let results: any = []
const yesterdayKey = rankKey(activity, yesterday());
for (let i = 0; i < records.length; i+=2) { for (let i = 0; i < records.length; i+=2) {
let id = records[i] const id = records[i]
let score = parseInt(records[i + 1]) 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({ results.push({
rank: start + i / 2 + 1, rank: start + i / 2 + 1,
address: user?.address || 'unknow', address: user?.address || 'unknow',
score invite,
score,
yesterday: yesterdayScore ? parseInt(yesterdayScore+'') : 0
}) })
} }
return results; return results;

View File

@ -5,7 +5,10 @@ import logger from 'logger/logger'
import { ActivityUser } from 'models/ActivityUser' import { ActivityUser } from 'models/ActivityUser'
import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord' import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord'
import { LoginRecordQueue } from 'queue/loginrecord.queue' import { LoginRecordQueue } from 'queue/loginrecord.queue'
import { RedisClient } from 'redis/RedisClient'
import { rankKey } from 'services/rank.svr'
import {SiweMessage} from 'siwe' import {SiweMessage} from 'siwe'
import { yesterday } from 'utils/date.util'
import { checkParamsNeeded } from 'utils/net.util' import { checkParamsNeeded } from 'utils/net.util'
import { aesDecrypt, base58ToHex } from 'utils/security.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 }) const token = await res.jwtSign({ id: accountData.id, address: accountData.address, activity: accountData.activity })
return { token } 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 * regist user by token from wallet-svr
* TODO:: * TODO::

View File

@ -33,7 +33,7 @@ export class TaskCfg {
@prop({default: true}) @prop({default: true})
show: boolean show: boolean
@prop({ type: mongoose.Schema.Types.Mixed }) @prop({ type: mongoose.Schema.Types.Mixed })
data: any cfg: any
@prop({ type: mongoose.Schema.Types.Mixed }) @prop({ type: mongoose.Schema.Types.Mixed })
params: any params: any
} }
@ -80,7 +80,7 @@ class ActivityInfoClass extends BaseModule {
score: task.score, score: task.score,
category: task.category, category: task.category,
autoclaim: task.autoclaim, autoclaim: task.autoclaim,
data: task.data, cfg: task.cfg,
}) })
} }
} }

View File

@ -79,8 +79,17 @@ class ActivityUserClass extends BaseModule {
@prop() @prop()
public twitterId?: string public twitterId?: string
@prop()
public twitterName?: string
@prop() @prop()
public discordId?: string public discordId?: string
@prop()
public discordName?: string
@prop({default: 1})
public boost: number
@prop() @prop()
public lastLogin?: Date public lastLogin?: Date

View File

@ -1,5 +1,6 @@
import { ScoreRecord } from "models/ScoreRecord"; import { ScoreRecord } from "models/ScoreRecord";
import { RedisClient } from "redis/RedisClient"; import { RedisClient } from "redis/RedisClient";
import { formatDate } from "utils/date.util";
/** /**
* *
@ -31,13 +32,34 @@ export const updateRankScore = async ({
data: scoreParams data: scoreParams
}) })
await record.save(); await record.save();
const key = `${activity}:score` const key = rankKey(activity);
let scoreSaved = await new RedisClient().zscore(key, user) + ''; 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) { if (scoreSaved) {
scoreSaved = scoreSaved.substring(0, scoreSaved.indexOf('.')) scoreSaved = scoreSaved.substring(0, scoreSaved.indexOf('.'))
} }
let scoreOld = parseInt(scoreSaved || '0'); let scoreOld = parseInt(scoreSaved || '0');
score = score + scoreOld; score = score + scoreOld;
const scoreToSave = score + 1 - (Date.now() / 1000 / 10000000000) const scoreToSave = score + 1 - (Date.now() / 1000 / 10000000000)
await new RedisClient().zadd(key, scoreToSave, user); 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}`
} }

View File

@ -24,7 +24,8 @@ export default class DiscordConnect extends ITask {
task.status = TaskStatusEnum.SUCCESS task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now() task.timeFinish = Date.now()
task.data = res.data.data 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 { try {
await this.params.user.save() await this.params.user.save()
} catch(err) { } catch(err) {

View File

@ -22,7 +22,8 @@ export default class TwitterConnect extends ITask {
task.status = TaskStatusEnum.SUCCESS task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now() task.timeFinish = Date.now()
task.data = res.data.data 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 { try {
await this.params.user.save() await this.params.user.save()
} catch(err) { } catch(err) {

View File

@ -5,3 +5,10 @@ export const formatDate = (date: Date): string => {
const day = (date.getDate() + '').padStart(2, '0'); const day = (date.getDate() + '').padStart(2, '0');
return `${year}${month}${day}`; return `${year}${month}${day}`;
}; };
// get formated datestring of yesterday
export const yesterday = () => {
const date = new Date();
date.setDate(date.getDate() - 1);
return date;
};