From feb3ff15fb320bfdb564f7d780f37a4d3135c0af Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:05:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=A2=9E=E5=8A=A0=E9=98=B2?= =?UTF-8?q?=E5=88=B7=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.server.ts | 6 +++-- src/common/LotteryCache.ts | 1 - src/common/SyncLocker.ts | 36 ++++++++++++++++++++++++++ src/controllers/activity.controller.ts | 2 ++ src/controllers/lottery.controller.ts | 3 +++ src/controllers/sign.controller.ts | 2 ++ src/controllers/tasks.controller.ts | 9 ++++--- 7 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 src/common/SyncLocker.ts diff --git a/src/api.server.ts b/src/api.server.ts index 8d629ee..41ac70f 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -9,6 +9,7 @@ import { mongoose } from '@typegoose/typegoose' import logger from 'logger/logger' import { RedisClient } from 'redis/RedisClient' import NonceRecordSchedule from 'schedule/noncerecord.schedule' +import { SyncLocker } from 'common/SyncLocker' const zReqParserPlugin = require('plugins/zReqParser') @@ -115,7 +116,7 @@ export class ApiServer { ) { reply.send({ errcode: 404, errmsg: 'page not found' }) }) - this.server.setErrorHandler(function (error: FastifyError, request: FastifyRequest, reply: FastifyReply) { + this.server.setErrorHandler(function (error: FastifyError, req: FastifyRequest, reply: FastifyReply) { let statusCode = (error && error.statusCode) || 100 if (statusCode >= 500) { logger.error(error) @@ -137,8 +138,9 @@ export class ApiServer { * @private */ private setFormatSend() { - this.server.addHook('preSerialization', async (request: FastifyRequest, reply: FastifyReply, payload) => { + this.server.addHook('preSerialization', async (req: FastifyRequest, reply: FastifyReply, payload) => { reply.header('X-Powered-By', 'PHP/5.4.16') + new SyncLocker().unlock(req) // @ts-ignore if (!payload.errcode) { payload = { diff --git a/src/common/LotteryCache.ts b/src/common/LotteryCache.ts index fa9ce6a..83ee02a 100644 --- a/src/common/LotteryCache.ts +++ b/src/common/LotteryCache.ts @@ -14,7 +14,6 @@ export class LotteryCache { } return this.map.get(user+dateTag); } - public async flush() { for (let record of this.map.values()) { await record.save(); diff --git a/src/common/SyncLocker.ts b/src/common/SyncLocker.ts new file mode 100644 index 0000000..65dcf59 --- /dev/null +++ b/src/common/SyncLocker.ts @@ -0,0 +1,36 @@ +import { singleton } from "decorators/singleton"; +import { FastifyRequest } from "fastify"; +import { ZError } from "./ZError"; + +@singleton +export class SyncLocker { + map: Map = new Map(); + + public lock(req: FastifyRequest) { + const key = `${req.method}:${req.url}:${req.user?.id || ''}` + if (this.map.has(key)) { + return false; + } + this.map.set(key, true); + return true; + } + + public unlock(req: FastifyRequest) { + const key = `${req.method}:${req.url}:${req.user?.id || ''}` + this.map.delete(key); + } + + public checkLock(req: FastifyRequest) { + const key = `${req.method}:${req.url}:${req.user?.id || ''}` + if (this.map.has(key)) { + throw new ZError(100, 'request too fast'); + } + this.lock(req); + return true; + } + + public isLocked(req: FastifyRequest) { + const key = `${req.method}:${req.url}:${req.user?.id || ''}` + return this.map.has(key); + } +} \ No newline at end of file diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts index 689bca3..deae0af 100644 --- a/src/controllers/activity.controller.ts +++ b/src/controllers/activity.controller.ts @@ -1,3 +1,4 @@ +import { SyncLocker } from "common/SyncLocker"; import { ZError } from "common/ZError"; import BaseController, { ROLE_ANON } from "common/base.controller"; import { role, router } from "decorators/router"; @@ -31,6 +32,7 @@ export default class ActivityController extends BaseController { */ @router('post /api/activity/upload_invite_code') async uploadInviteCode(req) { + new SyncLocker().checkLock(req); let { code } = req.params let user = req.user; if (user.inviteUser) { diff --git a/src/controllers/lottery.controller.ts b/src/controllers/lottery.controller.ts index 1af683a..1c87039 100644 --- a/src/controllers/lottery.controller.ts +++ b/src/controllers/lottery.controller.ts @@ -1,5 +1,6 @@ import { EMPTY_REWARD, ITEM_FRAME } from "common/Constants"; import { LotteryCache } from "common/LotteryCache"; +import { SyncLocker } from "common/SyncLocker"; import { ZError } from "common/ZError"; import BaseController from "common/base.controller"; import { FUSION_CFG } from "configs/fusion"; @@ -84,6 +85,7 @@ export default class LotteryController extends BaseController { @router('get /api/lottery/draw') async draw(req) { + new SyncLocker().checkLock(req); let user = req.user; const { start, end, rewards } = LOTTERY_CFG; const startTime = new Date(start).getTime(); @@ -145,6 +147,7 @@ export default class LotteryController extends BaseController { @router('get /api/lottery/fusion') async fusion(req) { + new SyncLocker().checkLock(req); let user = req.user; let items = await ActivityItem.find({user: user.id, activity: user.activity}); let itemCountMap = new Map(); diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index 55d785f..c9fffc8 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -1,4 +1,5 @@ import BaseController, {ROLE_ANON} from 'common/base.controller' +import { SyncLocker } from 'common/SyncLocker' import {ZError} from 'common/ZError' import { role, router } from 'decorators/router' import logger from 'logger/logger' @@ -120,6 +121,7 @@ class SignController extends BaseController { } @router('get /api/user/state/boost') async boost(req){ + new SyncLocker().checkLock(req); const user = req.user; if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) { throw new ZError(11, 'already boosted') diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts index 03df86e..006d6be 100644 --- a/src/controllers/tasks.controller.ts +++ b/src/controllers/tasks.controller.ts @@ -1,8 +1,9 @@ +import { SyncLocker } from "common/SyncLocker"; import { ZError } from "common/ZError"; -import BaseController, { ROLE_ANON } from "common/base.controller"; +import BaseController from "common/base.controller"; import { router } from "decorators/router"; -import { ActivityInfo, TaskCfg, TaskTypeEnum } from "models/ActivityInfo"; -import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser"; +import { TaskCfg, TaskTypeEnum } from "models/ActivityInfo"; +import { TaskStatus, TaskStatusEnum } from "models/ActivityUser"; import { join } from 'path' import { formatDate } from "utils/date.util"; const fs = require('fs') @@ -74,6 +75,7 @@ export default class TasksController extends BaseController { @router('post /api/tasks/begin_task') async beginTask(req) { + new SyncLocker().checkLock(req); let user = req.user; let activity = req.activity; let { task } = req.params; @@ -161,6 +163,7 @@ export default class TasksController extends BaseController { @router('post /api/tasks/claim') async claimTask(req) { + new SyncLocker().checkLock(req); const user = req.user; const activity = req.activity; const { task } = req.params;