接口增加防刷机制
This commit is contained in:
parent
ae3eab82a8
commit
feb3ff15fb
@ -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 = {
|
||||||
|
@ -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
36
src/common/SyncLocker.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
|
@ -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')
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user