task-svr/src/controllers/chest.controller.ts
2024-03-27 13:58:43 +08:00

196 lines
6.2 KiB
TypeScript

import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
import { ActivityChest, ChestStatusEnum } from 'models/ActivityChest'
import { ActivityUser } from 'models/ActivityUser'
import { rankKey, rankLevel, updateRankScore } from 'services/rank.svr'
import { formatDate } from 'zutils/utils/date.util'
import { ScoreRecord } from 'models/ScoreRecord'
import { ChestRecord } from 'models/chain/ChestRecord'
import { generateNewChest } from 'services/game.svr'
import { SCORE_OPEN_CHEST } from 'common/Constants'
import { formatAddress } from 'zutils/utils/chain.util'
/**
* 宝箱相关接口
*/
class BoxController extends BaseController {
/**
* 宝箱列表
*/
@router('get /api/chest/list')
async chestList(req) {
const user = req.user
const openRecord = await ChestRecord.find({ from: user.address.toLowerCase() })
const chestIds = openRecord.map(record => record.chestId)
const chestSet = new Set(chestIds)
const chests = await ActivityChest.find({
activity: user.activity,
user: user.id,
status: { $in: [0, 1, 2] },
}).sort({
level: -1,
})
const results = chests.map(chest => chest.toJson())
for (let result of results) {
if (chestSet.has(result.id)) {
result.stat = 2
}
}
return results
}
/**
* 宝箱助力列表
*/
@router('post /api/chest/enhance/list')
async enhanceList(req) {
const user = req.user
let { chestId, chestid } = req.params
chestid = chestId || chestid
if (!chestid) {
throw new ZError(11, 'chestId is required')
}
const chest = await ActivityChest.findById(chestid)
if (!chest) {
throw new ZError(12, 'chest not found')
}
if (chest.status === 0) {
throw new ZError(13, 'chest is locked')
}
if (chest.status === 9) {
throw new ZError(14, 'chest had been opened')
}
const result = []
const users = await ActivityUser.find({ _id: { $in: chest.bonusUsers } })
const userMap = new Map()
const totalKey = rankKey(user.activity)
users.forEach(u => userMap.set(u.id, u))
for (let i = 0; i < chest.bonusUsers.length; i++) {
const u = userMap.get(chest.bonusUsers[i])
const totalScore = await new ZRedisClient().zscore(totalKey, u.id)
const score = totalScore ? parseInt(totalScore + '') : 0
result.push({
nickname: u?.twitterName || u?.discordName || u?.address ? formatAddress(u.address) : 'unknown',
level: rankLevel(score),
score: score,
})
}
return result
}
/**
* 宝箱助力
*/
@router('post /api/chest/enhance')
async enhance(req) {
new SyncLocker().checkLock(req)
const { code } = req.params
const user = req.user
const uid = user.id
// TODO:: 待规则确定后, 检查用户是否符合助力条件
const chest = await ActivityChest.findOne({ shareCode: code, activity: user.activity })
if (chest.bonusUsers.includes(uid)) {
throw new ZError(10, 'user already enhanced')
}
if (chest.bonusUsers.length >= chest.maxBounsCount) {
throw new ZError(12, 'enhanced times exceed')
}
if (chest.status === ChestStatusEnum.OPENED) {
throw new ZError(13, 'chest already opened')
}
if (chest.status === ChestStatusEnum.LOCKED) {
throw new ZError(14, 'chest is locked')
}
// 生产环境不能助力自己
if (process.env.NODE_ENV === 'production' && chest.user === uid) {
throw new ZError(15, 'can not enhance self')
}
const score = Math.floor(Math.random() * (chest.bounsCfg[1] - chest.bounsCfg[0] + 1) + chest.bounsCfg[0])
chest.bonusUsers.push(uid)
chest.bonusScores.push(score)
chest.scoreBonus += score
await chest.save()
const chestsForUser = await ActivityChest.find({ user: uid, activity: user.activity })
// 如果用户没有宝箱, 则说明用户是新用户, 生成一个宝箱
if (chestsForUser.length === 0) {
const newChest = generateNewChest(uid, user.activity, 1, ChestStatusEnum.LOCKED)
await newChest.save()
return {
score: 0,
chests: [newChest.toJson()],
}
} else {
return {
score: 0,
}
}
}
/**
* 开启宝箱
* 流程如下:
* 1. 客户端组装json string: data:,{"p":"cf-20","op":"chest_open","id":"${chestId}"}
* 2. 将字符串转成hex
* 3. 将hex字符串作为data, 发起一笔至特定地址的0ETH交易
* 4. 服务端监听到交易后, 解析input, 获取chestId
* 5. 服务端调用此接口, 传入chestId, 完成开箱操作
*/
@router('post /api/chest/open')
async openChest(req) {
new SyncLocker().checkLock(req)
const user = req.user
const { chestId } = req.params
if (!chestId) {
throw new ZError(11, 'chestId is required')
}
const openRecord = await ChestRecord.findOne({ from: user.address.toLowerCase(), chestId })
if (!openRecord) {
throw new ZError(12, 'onchain open record not found')
}
const chest = await ActivityChest.findById(chestId)
if (!chest) {
throw new ZError(12, 'chest not found')
}
if (chest.user !== user.id) {
throw new ZError(13, 'chest not belong to user')
}
if (chest.status === ChestStatusEnum.OPENED) {
throw new ZError(14, 'chest already opened')
}
chest.status = ChestStatusEnum.OPENED
const score = chest.scoreInit + chest.scoreBonus
const dateTag = formatDate(new Date())
await updateRankScore({
user: user.id,
score: score,
activity: user.activity,
scoreType: SCORE_OPEN_CHEST,
scoreParams: {
date: dateTag,
chestId: chest.id,
level: chest.level,
},
})
await chest.save()
return { score }
}
/**
* 宝箱开启记录
*/
@router('get /api/chest/open/history')
async openChestHistory(req) {
const user = req.user
const records = await ScoreRecord.find({ user: user.id, activity: user.activity, type: SCORE_OPEN_CHEST }).sort({
createdAt: -1,
})
return records.map(record => {
return {
chest: record.data.chestId,
score: record.score,
level: record.data.level,
// @ts-ignore
time: record.createdAt.getTime(),
}
})
}
}