增加宝箱激活码相关接口
This commit is contained in:
parent
db8a16b954
commit
17961ce320
71
docs/uaw.md
71
docs/uaw.md
@ -52,6 +52,9 @@
|
||||
2. 增加接口: 合作伙伴NFT列表
|
||||
3. 增加接口: 领取NFT holder奖励
|
||||
|
||||
#### 20240416
|
||||
1. 增加接口: 兑换宝箱激活码(30)
|
||||
|
||||
### 1. 钱包预登录
|
||||
|
||||
#### Request
|
||||
@ -873,3 +876,71 @@ body:
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 30.\* 兑换宝箱激活码
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/voucher/claim`
|
||||
- 方法:POST
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"id": "兑换码"
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
chests: [
|
||||
{ // 结构同 18.宝箱列表
|
||||
id: 1, // 箱子id
|
||||
stat: 0, // 0: 锁定, 1: 正常
|
||||
shareCode: '箱子的分享码',
|
||||
level: 1, // 箱子品级
|
||||
maxBonus: 10, // 最大可助力数量
|
||||
scoreInit: 5, // 初始可获得积分
|
||||
scoreBonus: 10, // 助力增加的分数
|
||||
bonusCount: 2, // 已助力次数
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 31.\* 生成测试用宝箱激活码
|
||||
|
||||
> 只在测试环境有效
|
||||
>
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/voucher/generate`
|
||||
- 方法:POST
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"num": 10, //要生成的激活码数量
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
['1234567890ab'] // 激活码列表
|
||||
```
|
@ -66,9 +66,9 @@
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe0my7",
|
||||
"task": "TwitterRetweet",
|
||||
"title": "Repost on X",
|
||||
"title": "Retweet on X",
|
||||
"type": 1,
|
||||
"desc": "Show your friends Counter Fire.",
|
||||
"desc": "Retweet specific tweets",
|
||||
"category": "Social Tasks",
|
||||
"score": 50,
|
||||
"autoclaim": false,
|
||||
@ -81,9 +81,9 @@
|
||||
}, {
|
||||
"id": "e2fuah0j30vwcpe0my9",
|
||||
"task": "TwitterLike",
|
||||
"title": "Like on X",
|
||||
"title": "Like the tweets on X",
|
||||
"type": 1,
|
||||
"desc": "Show your friends Counter Fire.",
|
||||
"desc": "Like specific tweets",
|
||||
"category": "Social Tasks",
|
||||
"score": 50,
|
||||
"autoclaim": false,
|
||||
|
@ -30,6 +30,7 @@
|
||||
"fastify-xml-body-parser": "^2.2.0",
|
||||
"mongodb-extended-json": "^1.11.1",
|
||||
"mongoose": "8.2.3",
|
||||
"nanoid": "^5.0.7",
|
||||
"node-schedule": "^2.0.0",
|
||||
"siwe": "^2.1.4",
|
||||
"tracer": "^1.1.6",
|
||||
|
@ -63,3 +63,5 @@ export const SCORE_ENHANCE_CHEST_GIFT = 'enhance_chest_gift'
|
||||
export const RECAPTCHA_MIN_SCORE = 0.5
|
||||
// 排行榜分数缩放系数
|
||||
export const RANK_SCORE_SCALE = 100
|
||||
|
||||
export const BASE52_ALPHABET = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||
|
@ -8,6 +8,10 @@ export const isValidShareCode = (str: string) => {
|
||||
return /^[3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI]{10}$/.test(str)
|
||||
}
|
||||
|
||||
export const isValidVoucherCode = (str: string) => {
|
||||
return /^[3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI]{12}$/.test(str)
|
||||
}
|
||||
|
||||
export const formatNumShow = (num: number) => {
|
||||
if (num >= 10) {
|
||||
return Math.round(num) + ''
|
||||
|
@ -78,6 +78,7 @@ export default class TasksController extends BaseController {
|
||||
@router('post /api/tasks/begin_task')
|
||||
async beginTask(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
logger.db('begin_task', req)
|
||||
let user = req.user
|
||||
let activity = req.activity
|
||||
let { task } = req.params
|
||||
@ -136,6 +137,7 @@ export default class TasksController extends BaseController {
|
||||
@router('post /api/tasks/check_task')
|
||||
async checkTask(req) {
|
||||
const user = req.user
|
||||
logger.db('chect_task', req)
|
||||
const activity = req.activity
|
||||
const { task } = req.params
|
||||
const [taskId, dateTag] = task.split(':')
|
||||
|
@ -1,26 +1,83 @@
|
||||
import { isValidShareCode } from 'common/Utils'
|
||||
import { isValidShareCode, isValidVoucherCode } from 'common/Utils'
|
||||
import logger from 'logger/logger'
|
||||
import { ChestStatusEnum } from 'models/ActivityChest'
|
||||
import { NFTHolderRecord } from 'models/NFTHodlerRecord'
|
||||
import { queryNftBalance } from 'services/chain.svr'
|
||||
import { generateChestLevel, generateNewChest } from 'services/game.svr'
|
||||
import { checkDiscordRole } from 'services/oauth.svr'
|
||||
import { SourceEnum, VoucherRecord, VoucherStatusEnum } from 'models/VoucherRecord'
|
||||
import { generateNewChest } from 'services/game.svr'
|
||||
import { SyncLocker, BaseController, router, role, ROLE_ANON, ZError } from 'zutils'
|
||||
|
||||
import { customAlphabet } from 'nanoid'
|
||||
import { BASE52_ALPHABET } from 'common/Constants'
|
||||
/**
|
||||
* 礼品券相关接口
|
||||
*/
|
||||
class VoucherController extends BaseController {
|
||||
@router('post /api/voucher/generate')
|
||||
async generateTestVoucher(req) {
|
||||
logger.db('generate_test_voucher', req)
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new ZError(10, 'only support in development')
|
||||
}
|
||||
const user = req.user
|
||||
let { num } = req.params
|
||||
if (!num || num <= 1) {
|
||||
num = 1
|
||||
}
|
||||
let results = []
|
||||
const nanoid = customAlphabet(BASE52_ALPHABET, 8)
|
||||
for (let i = 0; i < num; i++) {
|
||||
let code = 'test' + nanoid()
|
||||
let voucher = new VoucherRecord({
|
||||
batch: 'test',
|
||||
code,
|
||||
scoreInit: 0,
|
||||
chestLevel: 1,
|
||||
chestNum: 1,
|
||||
deliverTime: Date.now(),
|
||||
activity: user.activity,
|
||||
status: VoucherStatusEnum.NORMAL,
|
||||
source: SourceEnum.TEST,
|
||||
creator: user.id,
|
||||
})
|
||||
await voucher.save()
|
||||
results.push(voucher.id)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
@router('post /api/voucher/claim')
|
||||
async claimVoucherReward(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
logger.db('claim_voucher', req)
|
||||
const user = req.user
|
||||
const { id } = req.params
|
||||
if (!id || !isValidShareCode(id)) {
|
||||
throw new ZError(10, 'invild voucher id')
|
||||
if (!id || !isValidVoucherCode(id)) {
|
||||
throw new ZError(10, 'invild voucher code')
|
||||
}
|
||||
const record = await VoucherRecord.findOne({ code: id, status: VoucherStatusEnum.NORMAL })
|
||||
if (!record) {
|
||||
throw new ZError(11, 'voucher not found')
|
||||
}
|
||||
record.status = VoucherStatusEnum.USED
|
||||
record.user = user.id
|
||||
if (record.bonusScores.length > 0) {
|
||||
record.score = record.scoreInit + record.bonusScores[Math.floor(Math.random() * record.bonusScores.length)]
|
||||
} else {
|
||||
record.score = record.scoreInit
|
||||
}
|
||||
let chestList: any = []
|
||||
if (record.chestNum > 0) {
|
||||
for (let i = 0; i < record.chestNum; i++) {
|
||||
const chest = generateNewChest(user.id, user.activity, record.chestLevel, ChestStatusEnum.NORMAL)
|
||||
await chest.save()
|
||||
record.chests.push(chest.id)
|
||||
chestList.push(chest.toJson())
|
||||
}
|
||||
}
|
||||
record.userTime = Date.now()
|
||||
await record.save()
|
||||
|
||||
return {}
|
||||
return {
|
||||
score: record.score,
|
||||
chests: chestList,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { getModelForClass, index, modelOptions, mongoose, pre, prop } from '@typ
|
||||
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
|
||||
import { BaseModule } from './Base'
|
||||
import { convert } from 'zutils/utils/number.util'
|
||||
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||
import { BASE52_ALPHABET } from 'common/Constants'
|
||||
|
||||
export enum ChestStatusEnum {
|
||||
LOCKED = 0,
|
||||
@ -32,7 +32,7 @@ export enum ChestStatusEnum {
|
||||
}
|
||||
let shortId = timeStr + this.id.slice(-6)
|
||||
console.log(shortId)
|
||||
this.shareCode = convert({ numStr: shortId, base: 16, to: 52, alphabet })
|
||||
this.shareCode = convert({ numStr: shortId, base: 16, to: 52, alphabet: BASE52_ALPHABET })
|
||||
console.log(this.id, this.shareCode)
|
||||
}
|
||||
})
|
||||
|
@ -12,8 +12,7 @@ import { dbconn } from 'decorators/dbconn'
|
||||
import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses'
|
||||
import { BaseModule } from './Base'
|
||||
import { convert } from 'zutils/utils/number.util'
|
||||
|
||||
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||
import { BASE52_ALPHABET } from 'common/Constants'
|
||||
|
||||
export enum TaskStatusEnum {
|
||||
NOT_START = 0,
|
||||
@ -65,7 +64,7 @@ export interface ActivityUserClass extends Base, TimeStamps {}
|
||||
timeStr = randomStr + timeStr.slice(1)
|
||||
}
|
||||
let shortId = timeStr + this.id.slice(-6)
|
||||
this.inviteCode = convert({ numStr: shortId, base: 16, to: 52, alphabet })
|
||||
this.inviteCode = convert({ numStr: shortId, base: 16, to: 52, alphabet: BASE52_ALPHABET })
|
||||
}
|
||||
})
|
||||
export class ActivityUserClass extends BaseModule {
|
||||
|
83
src/models/VoucherRecord.ts
Normal file
83
src/models/VoucherRecord.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { getModelForClass, index, modelOptions, mongoose, pre, prop } from '@typegoose/typegoose'
|
||||
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
|
||||
import { BaseModule } from './Base'
|
||||
import { convert } from 'zutils/utils/number.util'
|
||||
const alphabet = '3fBCM8j17XNA9xYun4wmLWep2oHFlhPcgyEJskqOz6GK0UtV5ZRaDSvrTbidQI'
|
||||
|
||||
export enum VoucherStatusEnum {
|
||||
LOCKED = 0,
|
||||
INITED = 1,
|
||||
NORMAL = 2,
|
||||
USED = 9,
|
||||
}
|
||||
|
||||
export enum SourceEnum {
|
||||
TEST = 'test',
|
||||
ADMIN = 'admin',
|
||||
}
|
||||
/**
|
||||
* 宝箱券
|
||||
*/
|
||||
@dbconn()
|
||||
@index({ user: 1, activity: 1 }, { unique: false })
|
||||
@index({ code: 1, activity: 1 }, { unique: true })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'activity_box', timestamps: true },
|
||||
options: { allowMixed: Severity.ALLOW },
|
||||
})
|
||||
export class VoucherRecordClass extends BaseModule {
|
||||
@prop()
|
||||
public batch: number
|
||||
@prop()
|
||||
public code: string
|
||||
// 0 锁定, 1 未发放 2 已发放,待使用 9 已兑换
|
||||
@prop({ enum: VoucherStatusEnum, default: VoucherStatusEnum.INITED })
|
||||
public status: VoucherStatusEnum
|
||||
@prop()
|
||||
public user: string
|
||||
@prop()
|
||||
public activity: string
|
||||
// 基础积分
|
||||
@prop({ default: 0 })
|
||||
public scoreInit: number
|
||||
// 浮动积分
|
||||
@prop({ type: () => [Number], default: [] })
|
||||
public bonusScores: number[]
|
||||
// 可获得的宝箱等级
|
||||
@prop({ default: 1 })
|
||||
public chestLevel: number
|
||||
// 可获得的宝箱数量
|
||||
@prop({ default: 1 })
|
||||
public chestNum: number
|
||||
// 最终得到的积分
|
||||
@prop({ default: 0 })
|
||||
public score: number
|
||||
// 最终得到的宝箱列表
|
||||
@prop({ type: () => [String], default: [] })
|
||||
public chests: string[]
|
||||
@prop()
|
||||
public comment: string
|
||||
@prop()
|
||||
public creator: string
|
||||
@prop({ enum: SourceEnum, default: SourceEnum.ADMIN })
|
||||
public source: SourceEnum
|
||||
// 发放时间
|
||||
@prop()
|
||||
public deliverTime: number
|
||||
// 使用时间
|
||||
@prop()
|
||||
public userTime: number
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
// @ts-ignore
|
||||
id: this.id,
|
||||
stat: this.status,
|
||||
code: this.code,
|
||||
score: this.score,
|
||||
chests: this.chests,
|
||||
}
|
||||
}
|
||||
}
|
||||
export const VoucherRecord = getModelForClass(VoucherRecordClass, { existingConnection: VoucherRecordClass['db'] })
|
@ -3317,6 +3317,11 @@ nano-json-stream-parser@^0.1.2:
|
||||
resolved "https://registry.npmjs.org/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz"
|
||||
integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==
|
||||
|
||||
nanoid@^5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.7.tgz#6452e8c5a816861fd9d2b898399f7e5fd6944cc6"
|
||||
integrity sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==
|
||||
|
||||
natural-compare@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
|
Loading…
x
Reference in New Issue
Block a user