接口增加防刷机制

This commit is contained in:
CounterFire2023 2024-01-11 16:05:18 +08:00
parent ae3eab82a8
commit feb3ff15fb
7 changed files with 53 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import { mongoose } from '@typegoose/typegoose'
import logger from 'logger/logger' import logger from 'logger/logger'
import { RedisClient } from 'redis/RedisClient' import { RedisClient } from 'redis/RedisClient'
import NonceRecordSchedule from 'schedule/noncerecord.schedule' import NonceRecordSchedule from 'schedule/noncerecord.schedule'
import { SyncLocker } from 'common/SyncLocker'
const zReqParserPlugin = require('plugins/zReqParser') const zReqParserPlugin = require('plugins/zReqParser')
@ -115,7 +116,7 @@ export class ApiServer {
) { ) {
reply.send({ errcode: 404, errmsg: 'page not found' }) 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 let statusCode = (error && error.statusCode) || 100
if (statusCode >= 500) { if (statusCode >= 500) {
logger.error(error) logger.error(error)
@ -137,8 +138,9 @@ export class ApiServer {
* @private * @private
*/ */
private setFormatSend() { 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') reply.header('X-Powered-By', 'PHP/5.4.16')
new SyncLocker().unlock(req)
// @ts-ignore // @ts-ignore
if (!payload.errcode) { if (!payload.errcode) {
payload = { payload = {

View File

@ -14,7 +14,6 @@ export class LotteryCache {
} }
return this.map.get(user+dateTag); return this.map.get(user+dateTag);
} }
public async flush() { public async flush() {
for (let record of this.map.values()) { for (let record of this.map.values()) {
await record.save(); await record.save();

36
src/common/SyncLocker.ts Normal file
View File

@ -0,0 +1,36 @@
import { singleton } from "decorators/singleton";
import { FastifyRequest } from "fastify";
import { ZError } from "./ZError";
@singleton
export class SyncLocker {
map: Map<string, boolean> = 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);
}
}

View File

@ -1,3 +1,4 @@
import { SyncLocker } from "common/SyncLocker";
import { ZError } from "common/ZError"; import { ZError } from "common/ZError";
import BaseController, { ROLE_ANON } from "common/base.controller"; import BaseController, { ROLE_ANON } from "common/base.controller";
import { role, router } from "decorators/router"; import { role, router } from "decorators/router";
@ -31,6 +32,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);
let { code } = req.params let { code } = req.params
let user = req.user; let user = req.user;
if (user.inviteUser) { if (user.inviteUser) {

View File

@ -1,5 +1,6 @@
import { EMPTY_REWARD, ITEM_FRAME } from "common/Constants"; import { EMPTY_REWARD, ITEM_FRAME } from "common/Constants";
import { LotteryCache } from "common/LotteryCache"; import { LotteryCache } from "common/LotteryCache";
import { SyncLocker } from "common/SyncLocker";
import { ZError } from "common/ZError"; import { ZError } from "common/ZError";
import BaseController from "common/base.controller"; import BaseController from "common/base.controller";
import { FUSION_CFG } from "configs/fusion"; import { FUSION_CFG } from "configs/fusion";
@ -84,6 +85,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);
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();
@ -145,6 +147,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);
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();

View File

@ -1,4 +1,5 @@
import BaseController, {ROLE_ANON} from 'common/base.controller' import BaseController, {ROLE_ANON} from 'common/base.controller'
import { SyncLocker } from 'common/SyncLocker'
import {ZError} from 'common/ZError' import {ZError} from 'common/ZError'
import { role, router } from 'decorators/router' import { role, router } from 'decorators/router'
import logger from 'logger/logger' import logger from 'logger/logger'
@ -120,6 +121,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);
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')

View File

@ -1,8 +1,9 @@
import { SyncLocker } from "common/SyncLocker";
import { ZError } from "common/ZError"; 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 { router } from "decorators/router";
import { ActivityInfo, TaskCfg, TaskTypeEnum } from "models/ActivityInfo"; import { TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser"; import { TaskStatus, TaskStatusEnum } from "models/ActivityUser";
import { join } from 'path' import { join } from 'path'
import { formatDate } from "utils/date.util"; import { formatDate } from "utils/date.util";
const fs = require('fs') const fs = require('fs')
@ -74,6 +75,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);
let user = req.user; let user = req.user;
let activity = req.activity; let activity = req.activity;
let { task } = req.params; let { task } = req.params;
@ -161,6 +163,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);
const user = req.user; const user = req.user;
const activity = req.activity; const activity = req.activity;
const { task } = req.params; const { task } = req.params;