增加分布式锁
This commit is contained in:
parent
1a4da3ca73
commit
37aca45c9d
@ -58,7 +58,7 @@
|
|||||||
#### 20240423
|
#### 20240423
|
||||||
1. 签到列表接口(11)增加返回字段: 是否已领取奖励
|
1. 签到列表接口(11)增加返回字段: 是否已领取奖励
|
||||||
1. 检查签到并领取奖励(23), 增加post参数, 可以领取指定天数的奖励
|
1. 检查签到并领取奖励(23), 增加post参数, 可以领取指定天数的奖励
|
||||||
1. 宝箱助力(18), 增加返回scoreBonus, 表示当前宝箱已得到的助力积分
|
1. 宝箱助力(18), 宝箱助力状态查询(26) 增加返回scoreBonus, 表示当前宝箱已得到的助力积分
|
||||||
|
|
||||||
### 1. 钱包预登录
|
### 1. 钱包预登录
|
||||||
|
|
||||||
@ -803,6 +803,7 @@ body:
|
|||||||
chestMax: 10, // 宝箱最大可助力次数
|
chestMax: 10, // 宝箱最大可助力次数
|
||||||
nickname: '11', // 宝箱所有者昵称
|
nickname: '11', // 宝箱所有者昵称
|
||||||
avatar: '', // 宝箱所有者头像, 可能为空
|
avatar: '', // 宝箱所有者头像, 可能为空
|
||||||
|
scoreBonus: 1 //当前箱子已助力积分
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -33,10 +33,12 @@
|
|||||||
"fastify": "^4.15.0",
|
"fastify": "^4.15.0",
|
||||||
"fastify-plugin": "^3.0.0",
|
"fastify-plugin": "^3.0.0",
|
||||||
"fastify-xml-body-parser": "^2.2.0",
|
"fastify-xml-body-parser": "^2.2.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
"mongodb-extended-json": "^1.11.1",
|
"mongodb-extended-json": "^1.11.1",
|
||||||
"mongoose": "8.2.3",
|
"mongoose": "8.2.3",
|
||||||
"nanoid": "^3.1.23",
|
"nanoid": "^3.1.23",
|
||||||
"node-schedule": "^2.0.0",
|
"node-schedule": "^2.0.0",
|
||||||
|
"redlock": "^5.0.0-beta.2",
|
||||||
"siwe": "^2.1.4",
|
"siwe": "^2.1.4",
|
||||||
"tracer": "^1.1.6",
|
"tracer": "^1.1.6",
|
||||||
"zutils": "link:packages/zutils"
|
"zutils": "link:packages/zutils"
|
||||||
|
@ -7,8 +7,8 @@ import { IncomingMessage, Server, ServerResponse } from 'http'
|
|||||||
import { mongoose } from '@typegoose/typegoose'
|
import { mongoose } from '@typegoose/typegoose'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import NonceRecordSchedule from 'schedule/noncerecord.schedule'
|
import NonceRecordSchedule from 'schedule/noncerecord.schedule'
|
||||||
import { RouterMap, SyncLocker, ZRedisClient } from 'zutils'
|
import { RouterMap, ZRedisClient } from 'zutils'
|
||||||
import CacheSchedule from 'schedule/cache.schedule'
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
|
|
||||||
const zReqParserPlugin = require('plugins/zReqParser')
|
const zReqParserPlugin = require('plugins/zReqParser')
|
||||||
|
|
||||||
|
66
src/common/SyncLocker.ts
Normal file
66
src/common/SyncLocker.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { singleton } from 'zutils'
|
||||||
|
import Client from 'ioredis'
|
||||||
|
import Redlock, { Lock, ResourceLockedError } from 'redlock'
|
||||||
|
import logger from 'logger/logger'
|
||||||
|
|
||||||
|
interface IRequest {
|
||||||
|
method: string
|
||||||
|
url: string
|
||||||
|
user?: {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
lock?: Lock
|
||||||
|
}
|
||||||
|
|
||||||
|
const redisA = new Client(process.env.REDIS)
|
||||||
|
const redlock = new Redlock(
|
||||||
|
// You should have one client for each independent redis node
|
||||||
|
// or cluster.
|
||||||
|
[redisA],
|
||||||
|
{
|
||||||
|
// The expected clock drift; for more details see:
|
||||||
|
// http://redis.io/topics/distlock
|
||||||
|
driftFactor: 0.01, // multiplied by lock ttl to determine drift time
|
||||||
|
|
||||||
|
// The max number of times Redlock will attempt to lock a resource
|
||||||
|
// before erroring.
|
||||||
|
retryCount: 10,
|
||||||
|
|
||||||
|
// the time in ms between attempts
|
||||||
|
retryDelay: 200, // time in ms
|
||||||
|
|
||||||
|
// the max time in ms randomly added to retries
|
||||||
|
// to improve performance under high contention
|
||||||
|
// see https://www.awsarchitectureblog.com/2015/03/backoff.html
|
||||||
|
retryJitter: 200, // time in ms
|
||||||
|
|
||||||
|
// The minimum remaining time on a lock before an extension is automatically
|
||||||
|
// attempted with the `using` API.
|
||||||
|
automaticExtensionThreshold: 500, // time in ms
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
redlock.on('error', error => {
|
||||||
|
// Ignore cases where a resource is explicitly marked as locked on a client.
|
||||||
|
if (error instanceof ResourceLockedError) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Log all other errors.
|
||||||
|
logger.error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
@singleton
|
||||||
|
export class SyncLocker {
|
||||||
|
public unlock(req: IRequest) {
|
||||||
|
if (req.lock) {
|
||||||
|
req.lock.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkLock(req: IRequest) {
|
||||||
|
const key = `${req.method}:${req.url}:${req.user?.id || ''}`
|
||||||
|
let lock = await redlock.acquire([key], 3000)
|
||||||
|
req.lock = lock
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import { ActivityInfo } from 'models/ActivityInfo'
|
import { ActivityInfo } from 'models/ActivityInfo'
|
||||||
import { ActivityUser } from 'models/ActivityUser'
|
import { ActivityUser } from 'models/ActivityUser'
|
||||||
import { rankKey, rankLevel } from 'services/rank.svr'
|
import { rankKey, rankLevel } from 'services/rank.svr'
|
||||||
import { BaseController, ROLE_ANON, SyncLocker, ZError, ZRedisClient, role, router } from 'zutils'
|
import { BaseController, ROLE_ANON, ZError, ZRedisClient, role, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { ScoreRecord } from 'models/ScoreRecord'
|
import { ScoreRecord } from 'models/ScoreRecord'
|
||||||
import { formatAddress } from 'zutils/utils/chain.util'
|
import { formatAddress } from 'zutils/utils/chain.util'
|
||||||
import { formatNumShow, isValidShareCode } from 'common/Utils'
|
import { formatNumShow, isValidShareCode } from 'common/Utils'
|
||||||
@ -32,7 +33,7 @@ export default class ActivityController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/activity/upload_invite_code')
|
@router('post /api/activity/upload_invite_code')
|
||||||
async uploadInviteCode(req) {
|
async uploadInviteCode(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('upload_invite_code', req)
|
logger.db('upload_invite_code', req)
|
||||||
await checkReCaptcha(req, 'invite_user')
|
await checkReCaptcha(req, 'invite_user')
|
||||||
let { code } = req.params
|
let { code } = req.params
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ZError, SyncLocker, BaseController, router } from 'zutils'
|
import { ZError, BaseController, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { FastifyRequest } from 'fastify'
|
import { FastifyRequest } from 'fastify'
|
||||||
import { ActivityItem } from 'models/ActivityItem'
|
import { ActivityItem } from 'models/ActivityItem'
|
||||||
import { TokenClaimHistory } from 'models/TokenClaimHistory'
|
import { TokenClaimHistory } from 'models/TokenClaimHistory'
|
||||||
@ -16,7 +17,7 @@ export default class ChainController extends BaseController {
|
|||||||
|
|
||||||
// @router('post /api/lottery/claim_usdt')
|
// @router('post /api/lottery/claim_usdt')
|
||||||
async preClaimUsdt(req: FastifyRequest) {
|
async preClaimUsdt(req: FastifyRequest) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
let user = req.user
|
let user = req.user
|
||||||
if (user.address) {
|
if (user.address) {
|
||||||
throw new ZError(11, 'no address')
|
throw new ZError(11, 'no address')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
import { ZError, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { ActivityChest, ChestStatusEnum } from 'models/ActivityChest'
|
import { ActivityChest, ChestStatusEnum } from 'models/ActivityChest'
|
||||||
import { ActivityUser } from 'models/ActivityUser'
|
import { ActivityUser } from 'models/ActivityUser'
|
||||||
import { rankKey, rankLevel, updateRankScore } from 'services/rank.svr'
|
import { rankKey, rankLevel, updateRankScore } from 'services/rank.svr'
|
||||||
@ -185,6 +186,7 @@ class BoxController extends BaseController {
|
|||||||
avatar: chestOwner?.twitterAvatar || '',
|
avatar: chestOwner?.twitterAvatar || '',
|
||||||
chestCurrent: chest.bonusUsers.length,
|
chestCurrent: chest.bonusUsers.length,
|
||||||
chestMax: chest.maxBounsCount,
|
chestMax: chest.maxBounsCount,
|
||||||
|
scoreBonus: chest.scoreBonus,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -196,6 +198,7 @@ class BoxController extends BaseController {
|
|||||||
? formatAddress(chestOwner.address)
|
? formatAddress(chestOwner.address)
|
||||||
: 'unknown',
|
: 'unknown',
|
||||||
avatar: chestOwner?.twitterAvatar || '',
|
avatar: chestOwner?.twitterAvatar || '',
|
||||||
|
scoreBonus: chest.scoreBonus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +207,7 @@ class BoxController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/chest/enhance')
|
@router('post /api/chest/enhance')
|
||||||
async enhance(req) {
|
async enhance(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('enhance', req)
|
logger.db('enhance', req)
|
||||||
await checkReCaptcha(req, 'chest_share')
|
await checkReCaptcha(req, 'chest_share')
|
||||||
const { code } = req.params
|
const { code } = req.params
|
||||||
@ -307,7 +310,7 @@ class BoxController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/chest/open')
|
@router('post /api/chest/open')
|
||||||
async openChest(req) {
|
async openChest(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('open_chest', req)
|
logger.db('open_chest', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const { chestId } = req.params
|
const { chestId } = req.params
|
||||||
|
@ -3,7 +3,8 @@ import { ActivityGame } from 'models/ActivityGame'
|
|||||||
import { DAILY_SIGN, SIGN_SEQ, 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 { queryCheckInList } from 'services/chain.svr'
|
||||||
import { checkInToday, seqSignCfg, seqSignScore, totalSignCfg, totalSignScore } from 'services/sign.svr'
|
import { checkInToday, seqSignCfg, seqSignScore, totalSignCfg, totalSignScore } from 'services/sign.svr'
|
||||||
import { ZError, SyncLocker, BaseController, router } from 'zutils'
|
import { ZError, BaseController, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { formatDate } from 'utils/utcdate.util'
|
import { formatDate } from 'utils/utcdate.util'
|
||||||
import { generateStepReward } from 'services/game.svr'
|
import { generateStepReward } from 'services/game.svr'
|
||||||
import { updateRankScore } from 'services/rank.svr'
|
import { updateRankScore } from 'services/rank.svr'
|
||||||
@ -23,7 +24,7 @@ class GameController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/user/checkin')
|
@router('post /api/user/checkin')
|
||||||
async checkIn(req) {
|
async checkIn(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('checkin', req)
|
logger.db('checkin', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const { address } = user
|
const { address } = user
|
||||||
@ -99,7 +100,7 @@ class GameController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/user/checkin/claim')
|
@router('post /api/user/checkin/claim')
|
||||||
async claimCheckResult(req) {
|
async claimCheckResult(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('claim_checkin_total', req)
|
logger.db('claim_checkin_total', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
let { days } = req.params
|
let { days } = req.params
|
||||||
@ -150,7 +151,7 @@ class GameController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/user/checkin/claim_seq')
|
@router('post /api/user/checkin/claim_seq')
|
||||||
async claimCheckSeqResult(req) {
|
async claimCheckSeqResult(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('claim_checkin_seq', req)
|
logger.db('claim_checkin_seq', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
let { days } = req.params
|
let { days } = req.params
|
||||||
@ -302,7 +303,7 @@ class GameController extends BaseController {
|
|||||||
@router('post /api/game/pre_step')
|
@router('post /api/game/pre_step')
|
||||||
async gameChest(req) {
|
async gameChest(req) {
|
||||||
const user = req.user
|
const user = req.user
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
let { step } = req.params
|
let { step } = req.params
|
||||||
step = step || '1'
|
step = step || '1'
|
||||||
if (isNaN(step)) {
|
if (isNaN(step)) {
|
||||||
@ -335,7 +336,7 @@ class GameController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@router('post /api/game/step')
|
@router('post /api/game/step')
|
||||||
async gameStep(req, res) {
|
async gameStep(req, res) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('step', req)
|
logger.db('step', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { EMPTY_REWARD, SCORE_DRAW } from 'common/Constants'
|
import { EMPTY_REWARD, SCORE_DRAW } from 'common/Constants'
|
||||||
import { LotteryCache } from 'common/LotteryCache'
|
import { LotteryCache } from 'common/LotteryCache'
|
||||||
import { ZError, SyncLocker, BaseController, router } from 'zutils'
|
import { ZError, BaseController, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { FUSION_CFG } from 'configs/fusion'
|
import { FUSION_CFG } from 'configs/fusion'
|
||||||
import { ALL_ITEMS } from 'configs/items'
|
import { ALL_ITEMS } from 'configs/items'
|
||||||
import { LOTTERY_CFG } from 'configs/lottery'
|
import { LOTTERY_CFG } from 'configs/lottery'
|
||||||
@ -80,7 +81,7 @@ export default class LotteryController extends BaseController {
|
|||||||
|
|
||||||
// @router('get /api/lottery/draw')
|
// @router('get /api/lottery/draw')
|
||||||
async draw(req) {
|
async draw(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
let user = req.user
|
let user = req.user
|
||||||
const { start, end, rewards } = LOTTERY_CFG
|
const { start, end, rewards } = LOTTERY_CFG
|
||||||
const startTime = new Date(start).getTime()
|
const startTime = new Date(start).getTime()
|
||||||
@ -144,7 +145,7 @@ export default class LotteryController extends BaseController {
|
|||||||
|
|
||||||
// @router('get /api/lottery/fusion')
|
// @router('get /api/lottery/fusion')
|
||||||
async fusion(req) {
|
async fusion(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
let user = req.user
|
let user = req.user
|
||||||
let items = await ActivityItem.find({ user: user.id, activity: user.activity })
|
let items = await ActivityItem.find({ user: user.id, activity: user.activity })
|
||||||
let itemCountMap = new Map()
|
let itemCountMap = new Map()
|
||||||
|
@ -5,7 +5,8 @@ import { NFTHolderRecord } from 'models/NFTHodlerRecord'
|
|||||||
import { queryNftBalance } from 'services/chain.svr'
|
import { queryNftBalance } from 'services/chain.svr'
|
||||||
import { generateChestLevel, generateNewChest } from 'services/game.svr'
|
import { generateChestLevel, generateNewChest } from 'services/game.svr'
|
||||||
import { checkDiscordRole } from 'services/oauth.svr'
|
import { checkDiscordRole } from 'services/oauth.svr'
|
||||||
import { SyncLocker, BaseController, router, role, ROLE_ANON, ZError } from 'zutils'
|
import { BaseController, router, role, ROLE_ANON, ZError } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
|
|
||||||
const sourceList = require('../../configs/partner_nft_list.json')
|
const sourceList = require('../../configs/partner_nft_list.json')
|
||||||
const tierMap = {
|
const tierMap = {
|
||||||
@ -103,7 +104,7 @@ class NftController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
// @router('post /api/partner/claim')
|
// @router('post /api/partner/claim')
|
||||||
async claimNftHolderReward(req) {
|
async claimNftHolderReward(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
let { contract } = req.params
|
let { contract } = req.params
|
||||||
if (!contract) {
|
if (!contract) {
|
||||||
@ -142,7 +143,7 @@ class NftController extends BaseController {
|
|||||||
|
|
||||||
@router('post /api/partner/claim')
|
@router('post /api/partner/claim')
|
||||||
async claimNftHolderRewardDC(req) {
|
async claimNftHolderRewardDC(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
// throw new ZError(20, 'maintance now, please try later')
|
// throw new ZError(20, 'maintance now, please try later')
|
||||||
|
|
||||||
logger.db('claim_partner', req)
|
logger.db('claim_partner', req)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
import { ZError, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { BOOST_CFG } from 'configs/boost'
|
import { BOOST_CFG } from 'configs/boost'
|
||||||
import { ActivityUser } from 'models/ActivityUser'
|
import { ActivityUser } from 'models/ActivityUser'
|
||||||
import { DEFAULT_EXPIRED, NonceRecord } from 'models/NonceRecord'
|
import { DEFAULT_EXPIRED, NonceRecord } from 'models/NonceRecord'
|
||||||
@ -163,7 +164,7 @@ class SignController extends BaseController {
|
|||||||
|
|
||||||
// @router('get /api/user/state/boost')
|
// @router('get /api/user/state/boost')
|
||||||
async boost(req) {
|
async boost(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) {
|
if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) {
|
||||||
throw new ZError(11, 'already boosted')
|
throw new ZError(11, 'already boosted')
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ZError, SyncLocker, BaseController, router } from 'zutils'
|
import { ZError, BaseController, router } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo'
|
import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo'
|
||||||
import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser'
|
import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
@ -77,7 +78,7 @@ export default class TasksController extends BaseController {
|
|||||||
|
|
||||||
@router('post /api/tasks/begin_task')
|
@router('post /api/tasks/begin_task')
|
||||||
async beginTask(req) {
|
async beginTask(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('begin_task', req)
|
logger.db('begin_task', req)
|
||||||
let user = req.user
|
let user = req.user
|
||||||
let activity = req.activity
|
let activity = req.activity
|
||||||
@ -175,7 +176,7 @@ export default class TasksController extends BaseController {
|
|||||||
|
|
||||||
@router('post /api/tasks/claim')
|
@router('post /api/tasks/claim')
|
||||||
async claimTask(req) {
|
async claimTask(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('claim_task', req)
|
logger.db('claim_task', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const activity = req.activity
|
const activity = req.activity
|
||||||
|
@ -3,7 +3,8 @@ import logger from 'logger/logger'
|
|||||||
import { ChestStatusEnum } from 'models/ActivityChest'
|
import { ChestStatusEnum } from 'models/ActivityChest'
|
||||||
import { SourceEnum, VoucherRecord, VoucherStatusEnum } from 'models/VoucherRecord'
|
import { SourceEnum, VoucherRecord, VoucherStatusEnum } from 'models/VoucherRecord'
|
||||||
import { generateNewChest } from 'services/game.svr'
|
import { generateNewChest } from 'services/game.svr'
|
||||||
import { SyncLocker, BaseController, router, ZError } from 'zutils'
|
import { BaseController, router, ZError } from 'zutils'
|
||||||
|
import { SyncLocker } from 'common/SyncLocker'
|
||||||
import { customAlphabet } from 'nanoid'
|
import { customAlphabet } from 'nanoid'
|
||||||
import { BASE52_ALPHABET } from 'common/Constants'
|
import { BASE52_ALPHABET } from 'common/Constants'
|
||||||
/**
|
/**
|
||||||
@ -90,7 +91,7 @@ class VoucherController extends BaseController {
|
|||||||
|
|
||||||
@router('post /api/voucher/claim')
|
@router('post /api/voucher/claim')
|
||||||
async claimVoucherReward(req) {
|
async claimVoucherReward(req) {
|
||||||
new SyncLocker().checkLock(req)
|
await new SyncLocker().checkLock(req)
|
||||||
logger.db('claim_voucher', req)
|
logger.db('claim_voucher', req)
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
|
57
yarn.lock
57
yarn.lock
@ -815,6 +815,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917"
|
||||||
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==
|
integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==
|
||||||
|
|
||||||
|
"@ioredis/commands@^1.1.1":
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11"
|
||||||
|
integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
|
||||||
|
|
||||||
"@istanbuljs/load-nyc-config@^1.0.0":
|
"@istanbuljs/load-nyc-config@^1.0.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
|
||||||
@ -2231,6 +2236,11 @@ clone-response@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-response "^1.0.0"
|
mimic-response "^1.0.0"
|
||||||
|
|
||||||
|
cluster-key-slot@^1.1.0:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac"
|
||||||
|
integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
|
||||||
|
|
||||||
co@^4.6.0:
|
co@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
@ -2507,6 +2517,11 @@ denque@^1.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||||
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
||||||
|
|
||||||
|
denque@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1"
|
||||||
|
integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
|
||||||
|
|
||||||
depd@2.0.0:
|
depd@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
||||||
@ -3801,6 +3816,21 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
|
|||||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
ioredis@^5.4.1:
|
||||||
|
version "5.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40"
|
||||||
|
integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==
|
||||||
|
dependencies:
|
||||||
|
"@ioredis/commands" "^1.1.1"
|
||||||
|
cluster-key-slot "^1.1.0"
|
||||||
|
debug "^4.3.4"
|
||||||
|
denque "^2.1.0"
|
||||||
|
lodash.defaults "^4.2.0"
|
||||||
|
lodash.isarguments "^3.1.0"
|
||||||
|
redis-errors "^1.2.0"
|
||||||
|
redis-parser "^3.0.0"
|
||||||
|
standard-as-callback "^2.1.0"
|
||||||
|
|
||||||
ipaddr.js@1.9.1:
|
ipaddr.js@1.9.1:
|
||||||
version "1.9.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
|
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
|
||||||
@ -4508,6 +4538,16 @@ locate-path@^6.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^5.0.0"
|
p-locate "^5.0.0"
|
||||||
|
|
||||||
|
lodash.defaults@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
|
integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
|
||||||
|
|
||||||
|
lodash.isarguments@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
|
||||||
|
|
||||||
lodash.isfunction@^3.0.6:
|
lodash.isfunction@^3.0.6:
|
||||||
version "3.0.9"
|
version "3.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
|
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
|
||||||
@ -4908,6 +4948,11 @@ next-tick@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
|
||||||
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==
|
||||||
|
|
||||||
|
node-abort-controller@^3.0.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||||
|
integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==
|
||||||
|
|
||||||
node-addon-api@^2.0.0:
|
node-addon-api@^2.0.0:
|
||||||
version "2.0.2"
|
version "2.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
|
||||||
@ -5427,6 +5472,13 @@ redis@^3.1.2:
|
|||||||
redis-errors "^1.2.0"
|
redis-errors "^1.2.0"
|
||||||
redis-parser "^3.0.0"
|
redis-parser "^3.0.0"
|
||||||
|
|
||||||
|
redlock@^5.0.0-beta.2:
|
||||||
|
version "5.0.0-beta.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44"
|
||||||
|
integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw==
|
||||||
|
dependencies:
|
||||||
|
node-abort-controller "^3.0.1"
|
||||||
|
|
||||||
reflect-metadata@^0.2.1:
|
reflect-metadata@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74"
|
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74"
|
||||||
@ -5868,6 +5920,11 @@ stack-utils@^2.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp "^2.0.0"
|
escape-string-regexp "^2.0.0"
|
||||||
|
|
||||||
|
standard-as-callback@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
|
||||||
|
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user