修改探索地图为上链, 修改连续签到奖励为手动领取
This commit is contained in:
parent
50011f1eaf
commit
66f2d39124
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@ -15,7 +15,20 @@
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "pwa-node"
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"name": "Debug Admin",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run-script",
|
||||
"dev:admin"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "node"
|
||||
},
|
||||
]
|
||||
}
|
78
docs/uaw.md
78
docs/uaw.md
@ -15,6 +15,15 @@
|
||||
}
|
||||
```
|
||||
|
||||
### 0. 更新记录
|
||||
|
||||
#### 20240407
|
||||
1. 增加探索预处理接口(24), 用于探索信息上链前创建一条探索记录
|
||||
1. 修改 探索(13) request和response结构
|
||||
1. 探索状态(12) 增加返回已探索总次数totalUsed
|
||||
1. 探索状态(12), 移除signCfg, 增加seqStat, 用于标识连续签到奖励领取状态
|
||||
1. 增加领取连续签到奖励(25)的接口
|
||||
|
||||
### 1. 钱包预登录
|
||||
|
||||
#### Request
|
||||
@ -367,6 +376,7 @@ query param
|
||||
```js
|
||||
{
|
||||
ticket: 1, // 可用探索次数
|
||||
totalUsed: 1, // 已探索总次数
|
||||
todayStat: 0, // 当日签到状态, 0: 未签到, 1: 已签到,但未领取 9:已签到, 已领取
|
||||
todayTickets: 1, // 当日签到可领取次数
|
||||
daysTotal: 1, // 累计签到天数
|
||||
@ -376,9 +386,10 @@ query param
|
||||
tickets: 2, // 满足条件后可领取数量
|
||||
state: 0, // 领取状态: 0: 未领取, 1: 可领取, 9: 已领取
|
||||
}],
|
||||
signCfg: [{ // 连续签到配置
|
||||
days: 2, //天数
|
||||
tickets: 1, //额外增加的次数
|
||||
seqStat: [{ // 连续签到状态
|
||||
days: 3, // 天数
|
||||
tickets: 2, // 满足条件后可领取数量
|
||||
state: 0, // 领取状态: 0: 未领取, 1: 可领取, 9: 已领取
|
||||
}]
|
||||
}
|
||||
```
|
||||
@ -396,7 +407,7 @@ body:
|
||||
|
||||
```js
|
||||
{
|
||||
"step": 2 // 使用的次数
|
||||
"id": "" // 探索预处理接口返回的id
|
||||
}
|
||||
|
||||
```
|
||||
@ -635,4 +646,61 @@ body:
|
||||
{
|
||||
ticket: 1 // 获得的探索次数
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
|
||||
### 24.\* 探索预处理
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/game/pre_step`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"step": 2 // 使用的次数
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
id: '' // 本次探索上链需要的数据
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 25.\* 领取连续签到奖励
|
||||
|
||||
#### Request
|
||||
|
||||
- URL:`/api/user/checkin/claim_seq`
|
||||
- 方法:`POST`
|
||||
- 头部:
|
||||
- Authorization: Bearer JWT_token
|
||||
|
||||
body:
|
||||
|
||||
```js
|
||||
{
|
||||
"days": 3 // 领取的累计签到天数
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
ticket: 2, //获得探索次数
|
||||
}
|
||||
```
|
||||
|
||||
###
|
@ -7,6 +7,8 @@
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"dev:api": "ts-node -r tsconfig-paths/register src/api.ts",
|
||||
"prod:api": "node dist/api.js",
|
||||
"dev:admin": "ts-node -r tsconfig-paths/register src/admin.ts",
|
||||
"prod:admin": "node dist/admin.js",
|
||||
"lint": "eslint --ext .ts src/**",
|
||||
"format": "eslint --ext .ts src/** --fix",
|
||||
"initdata": "ts-node src/initdata.ts"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MANUAL_OPEN_GAME, RESET_STEP, SCORE_GAME_STEP } from 'common/Constants'
|
||||
import { ActivityGame } from 'models/ActivityGame'
|
||||
import { DAILY_SIGN, SIGN_TOTAL, TicketRecord, USE_TICKET } from 'models/TicketRecord'
|
||||
import { DAILY_SIGN, SIGN_SEQ, SIGN_TOTAL, TicketRecord, USE_TICKET } from 'models/TicketRecord'
|
||||
import { queryCheckInList } from 'services/chain.svr'
|
||||
import { checkInToday, seqSignCfg, seqSignScore, totalSignCfg, totalSignScore } from 'services/sign.svr'
|
||||
import { ZError, SyncLocker, BaseController, router } from 'zutils'
|
||||
@ -8,6 +8,9 @@ import { formatDate } from 'zutils/utils/date.util'
|
||||
import { generateChestLevel, generateNewChest, generateStepReward } from 'services/game.svr'
|
||||
import { ChestStatusEnum } from 'models/ActivityChest'
|
||||
import { updateRankScore } from 'services/rank.svr'
|
||||
import { ExploreRecord } from 'models/ExploreRecord'
|
||||
import { isObjectId } from 'zutils/utils/string.util'
|
||||
import { GeneralScription } from 'models/chain/GeneralScription'
|
||||
|
||||
/**
|
||||
* 探索游戏相关接口
|
||||
@ -116,6 +119,57 @@ class GameController extends BaseController {
|
||||
await ticketRecord.save()
|
||||
return { ticket: score }
|
||||
}
|
||||
/**
|
||||
* 领取连续签到奖励
|
||||
*/
|
||||
@router('post /api/user/checkin/claim_seq')
|
||||
async claimCheckSeqResult(req) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
let { days } = req.params
|
||||
if (!days || isNaN(days)) {
|
||||
throw new ZError(11, 'invalid days')
|
||||
}
|
||||
days = parseInt(days)
|
||||
if (days < 1) {
|
||||
throw new ZError(12, 'invalid days')
|
||||
}
|
||||
const dateTag = formatDate(new Date())
|
||||
const checkRecord = await checkInToday(user.address.toLowerCase(), dateTag)
|
||||
if (!checkRecord) {
|
||||
throw new ZError(12, 'not signed in')
|
||||
}
|
||||
if (days > checkRecord.total) {
|
||||
throw new ZError(13, 'invalid days')
|
||||
}
|
||||
const ticketRecords = await TicketRecord.find({ user: user.id, activity: user.activity, type: SIGN_TOTAL })
|
||||
const claimedSet = new Set()
|
||||
ticketRecords.forEach(record => {
|
||||
claimedSet.add(record.data.day)
|
||||
})
|
||||
if (claimedSet.has(days)) {
|
||||
throw new ZError(14, 'already claimed')
|
||||
}
|
||||
|
||||
const score = totalSignScore(days)
|
||||
if (score === 0) {
|
||||
throw new ZError(15, 'invalid days')
|
||||
}
|
||||
const ticketRecord = new TicketRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
type: SIGN_SEQ,
|
||||
data: { day: days },
|
||||
score,
|
||||
})
|
||||
const gameRecord = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
if (MANUAL_OPEN_GAME && gameRecord.status === 0) {
|
||||
throw new ZError(12, 'map not open')
|
||||
}
|
||||
await ActivityGame.updateOne({ user: user.id, activity: user.activity }, { $inc: { tickets: score } })
|
||||
await ticketRecord.save()
|
||||
return { ticket: score }
|
||||
}
|
||||
/**
|
||||
* 开启地图
|
||||
*/
|
||||
@ -157,10 +211,22 @@ class GameController extends BaseController {
|
||||
todayStat = 2
|
||||
}
|
||||
const scoreBonus = seqSignScore(checkRecord?.count || 0)
|
||||
const ticketRecords = await TicketRecord.find({ user: user.id, activity: user.activity, type: SIGN_TOTAL })
|
||||
const ticketRecords = await TicketRecord.find({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
type: { $in: [SIGN_TOTAL, USE_TICKET, SIGN_SEQ] },
|
||||
})
|
||||
const claimedSet = new Set()
|
||||
const seqSet = new Set()
|
||||
let totalUsed = 0
|
||||
ticketRecords.forEach(record => {
|
||||
claimedSet.add(record.data.day)
|
||||
if (record.type === USE_TICKET) {
|
||||
totalUsed += record.score
|
||||
} else if (record.type === SIGN_TOTAL) {
|
||||
claimedSet.add(record.data.day)
|
||||
} else if (record.type === SIGN_SEQ) {
|
||||
seqSet.add(record.data.day)
|
||||
}
|
||||
})
|
||||
|
||||
const totalStat = []
|
||||
@ -178,17 +244,66 @@ class GameController extends BaseController {
|
||||
state,
|
||||
})
|
||||
}
|
||||
// 连续签到, 根据最大连续签到天数来判断是否拥有领取的资格
|
||||
const seqStat = []
|
||||
for (let cfg of seqSignCfg) {
|
||||
let state = 0
|
||||
if (cfg.days <= checkRecord?.maxSeq || 0) {
|
||||
state = 1
|
||||
}
|
||||
if (seqSet.has(cfg.days)) {
|
||||
state = 9
|
||||
}
|
||||
seqStat.push({
|
||||
days: cfg.days,
|
||||
tickets: cfg.reward,
|
||||
state,
|
||||
})
|
||||
}
|
||||
totalStat.sort((a, b) => a.days - b.days)
|
||||
return {
|
||||
ticket: record.tickets,
|
||||
totalUsed: Math.abs(totalUsed),
|
||||
signCfg,
|
||||
todayStat,
|
||||
todayTickets: 1 + scoreBonus,
|
||||
daysTotal: checkRecord?.total || 0,
|
||||
daysSeq: checkRecord?.count || 0,
|
||||
seqStat,
|
||||
totalStat,
|
||||
}
|
||||
}
|
||||
@router('get /api/game/pre_step')
|
||||
async gameChest(req) {
|
||||
const user = req.user
|
||||
new SyncLocker().checkLock(req)
|
||||
let { step } = req.params
|
||||
step = step || '1'
|
||||
if (isNaN(step)) {
|
||||
throw new ZError(11, 'invalid step')
|
||||
}
|
||||
|
||||
// check if step is safe int
|
||||
step = parseInt(step)
|
||||
if (step < 1) {
|
||||
step = 1
|
||||
}
|
||||
const record = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
if (MANUAL_OPEN_GAME && record.status === 0) {
|
||||
throw new ZError(12, 'map not open')
|
||||
}
|
||||
if (record.tickets < step) {
|
||||
throw new ZError(13, 'insufficient tickets')
|
||||
}
|
||||
const exploreRecord = new ExploreRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
step,
|
||||
})
|
||||
await exploreRecord.save()
|
||||
|
||||
return { id: exploreRecord.id }
|
||||
}
|
||||
/**
|
||||
* 探索
|
||||
*/
|
||||
@ -196,26 +311,36 @@ class GameController extends BaseController {
|
||||
async gameStep(req, res) {
|
||||
new SyncLocker().checkLock(req)
|
||||
const user = req.user
|
||||
let { step } = req.params
|
||||
step = step || '1'
|
||||
if (isNaN(step)) {
|
||||
throw new ZError(11, 'invalid step')
|
||||
const { id } = req.params
|
||||
if (!id) {
|
||||
throw new ZError(11, 'invalid id')
|
||||
}
|
||||
// check if step is safe int
|
||||
step = parseInt(step)
|
||||
if (step < 1) {
|
||||
step = 1
|
||||
if (!isObjectId(id)) {
|
||||
throw new ZError(12, 'invalid id')
|
||||
}
|
||||
// const session = await mongoose.startSession()
|
||||
// session.startTransaction()
|
||||
// try {
|
||||
const chainRecord = await GeneralScription.findOne({ from: user.address.toLowerCase(), op: 'explore', data: id })
|
||||
if (!chainRecord) {
|
||||
throw new ZError(13, 'waiting for chain confirm')
|
||||
}
|
||||
const exploreRecord = await ExploreRecord.findById(id)
|
||||
if (!exploreRecord) {
|
||||
throw new ZError(14, 'invalid id')
|
||||
}
|
||||
if (exploreRecord.status !== 0) {
|
||||
throw new ZError(15, 'invalid status')
|
||||
}
|
||||
const step = exploreRecord.step
|
||||
const record = await ActivityGame.insertOrUpdate({ user: user.id, activity: user.activity }, {})
|
||||
if (MANUAL_OPEN_GAME && record.status === 0) {
|
||||
throw new ZError(12, 'map not open')
|
||||
throw new ZError(16, 'map not open')
|
||||
}
|
||||
if (record.tickets < step) {
|
||||
throw new ZError(13, 'insufficient tickets')
|
||||
exploreRecord.status = -1
|
||||
await exploreRecord.save()
|
||||
throw new ZError(17, 'insufficient tickets')
|
||||
}
|
||||
exploreRecord.status = 1
|
||||
await exploreRecord.save()
|
||||
const ticketRecord = new TicketRecord({
|
||||
user: user.id,
|
||||
activity: user.activity,
|
||||
@ -253,14 +378,7 @@ class GameController extends BaseController {
|
||||
await chest.save()
|
||||
}
|
||||
await ticketRecord.save()
|
||||
// await ticketRecord.save({ session })
|
||||
// await session.commitTransaction()
|
||||
// session.endSession()
|
||||
|
||||
return { score, chests: chests.map(chest => chest.toJson()) }
|
||||
// } catch (e) {
|
||||
// session.abortTransaction()
|
||||
// session.endSession()
|
||||
// throw e
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
37
src/models/ExploreRecord.ts
Normal file
37
src/models/ExploreRecord.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
@dbconn()
|
||||
@index({ user: 1, activity: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'explore_record', timestamps: true },
|
||||
})
|
||||
class ExploreRecordClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public user: string
|
||||
|
||||
@prop({ required: true })
|
||||
public activity: string
|
||||
|
||||
@prop()
|
||||
public step: number
|
||||
/**
|
||||
* 0: 未完成
|
||||
* 1: 已完成
|
||||
* -1: 无效
|
||||
*/
|
||||
@prop({ default: 0 })
|
||||
public status: number
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
user: this.user,
|
||||
activity: this.activity,
|
||||
step: this.step,
|
||||
status: this.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ExploreRecord = getModelForClass(ExploreRecordClass, { existingConnection: ExploreRecordClass['db'] })
|
@ -5,6 +5,8 @@ import { BaseModule } from './Base'
|
||||
export const DAILY_SIGN = 'daily_sign'
|
||||
// 累计签到奖励
|
||||
export const SIGN_TOTAL = 'sign_total'
|
||||
// 连续签到奖励
|
||||
export const SIGN_SEQ = 'sign_seq'
|
||||
// 使用门票
|
||||
export const USE_TICKET = 'use_ticket'
|
||||
|
||||
|
@ -28,6 +28,9 @@ export class CheckInClass extends BaseModule {
|
||||
// 连签天数
|
||||
@prop({ default: 1 })
|
||||
public count: number
|
||||
// 最大连签天数
|
||||
@prop({ default: 1 })
|
||||
public maxSeq: number
|
||||
// 累计签到天数
|
||||
@prop({ default: 1 })
|
||||
public total: number
|
||||
|
66
src/models/chain/GeneralScription.ts
Normal file
66
src/models/chain/GeneralScription.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { BaseModule } from '../Base'
|
||||
import { hexToUtf8 } from 'zutils/utils/string.util'
|
||||
import logger from 'logger/logger'
|
||||
|
||||
@dbconn('chain')
|
||||
@index({ from: 1, op: 1 }, { unique: false })
|
||||
@index({ from: 1, op: 1, data: 1 }, { unique: true })
|
||||
@index({ hash: 1 }, { unique: true })
|
||||
@index({ from: 1, blockTime: 1 }, { unique: false })
|
||||
@modelOptions({
|
||||
schemaOptions: { collection: 'general_scription_record', timestamps: true },
|
||||
})
|
||||
export class GeneralScriptionClass extends BaseModule {
|
||||
@prop({ required: true })
|
||||
public from!: string
|
||||
@prop()
|
||||
public to: string
|
||||
@prop({ required: true })
|
||||
public hash: string
|
||||
@prop()
|
||||
public blockNumber: string
|
||||
@prop()
|
||||
public blockHash: string
|
||||
@prop()
|
||||
public blockTime: number
|
||||
@prop()
|
||||
public dateTag: string
|
||||
@prop()
|
||||
public data: string
|
||||
@prop()
|
||||
public op: string
|
||||
@prop()
|
||||
public value: string
|
||||
@prop()
|
||||
public input: string
|
||||
@prop({ default: 0 })
|
||||
public stat: number
|
||||
|
||||
public static async saveEvent(event: any) {
|
||||
const dataStr = hexToUtf8(event.input)
|
||||
const regexp = /data:,{\"p\":\"cf-20\",\"op\":\"(.+?)\",\"val\":\"(.+?)\"}/
|
||||
const match = dataStr.match(regexp)
|
||||
if (!match) {
|
||||
logger.log('not a general scription:', event.hash)
|
||||
return
|
||||
}
|
||||
event.op = match[1]
|
||||
event.data = match[2]
|
||||
logger.log('general scription with op:', event.op, ' data:', event.data)
|
||||
return GeneralScription.insertOrUpdate({ hash: event.hash }, event)
|
||||
}
|
||||
|
||||
public toJson() {
|
||||
return {
|
||||
address: this.from,
|
||||
day: this.dateTag,
|
||||
time: this.blockTime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const GeneralScription = getModelForClass(GeneralScriptionClass, {
|
||||
existingConnection: GeneralScriptionClass['db'],
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user