feat(worker-risk): 新增大厅阶梯式风控中间件与鉴权短缓存,惩罚外挂死循环狂刷并支持恶意封号

1. 新增 worker-risk-control 阶梯式风控:单打手/IP 限流 3次/秒(超限返 429);1 分钟累计违规 10 次升级为二级惩罚(锁定 5 分钟秒拒并带 Retry-After);惩罚期内持续暴力刷接口累计 30 次自动判定恶意不改,直接封禁打手账号(status='frozen')并作废所有 session;
2. 风控限流前置挂载在 requireWorkerSession 之前:命中限流或处于风控期时,微秒级在内存中基于 JWT payload 与 IP 拦截,彻底杜绝外挂流量穿透到 PostgreSQL 数据库;
3. verifyWorkerSessionToken 增加 3 秒内存校验缓存:并发请求直接复用内存鉴权对象,同一页面并发发出的多个接口请求仅查 1 次数据库,极大减轻连接池争抢压力;
4. 全套新增风控阶梯机制与自动封号自动化测试,后端 549 项测试全绿通过。
This commit is contained in:
yml2213
2026-10-02 16:30:53 +08:00
parent 5c13599dff
commit adaf4ed240
5 changed files with 721 additions and 1 deletions
@@ -41,6 +41,18 @@ export async function logoutWorkerSession(token: unknown) {
}
}
interface CachedVerifiedSession {
session: WorkerSession
expiresAtMs: number
}
const verifiedSessionCache = new Map<string, CachedVerifiedSession>()
const VERIFIED_SESSION_CACHE_TTL_MS = 3_000
export function clearVerifiedWorkerSessionCacheForTest(): void {
verifiedSessionCache.clear()
}
export async function verifyWorkerSessionToken(token: unknown): Promise<WorkerSession> {
ensureWorkerAuthConfigured()
const normalizedToken = String(token || '').trim()
@@ -51,6 +63,13 @@ export async function verifyWorkerSessionToken(token: unknown): Promise<WorkerSe
})
}
// 命中 3 秒内存校验缓存:并发请求直接复用,避免同一页面连续发 3~5 个请求打爆数据库连接池
const now = Date.now()
const cached = verifiedSessionCache.get(normalizedToken)
if (cached && cached.expiresAtMs > now) {
return cached.session
}
const parts = normalizedToken.split('.')
const encodedPayload = parts[0]
const signature = parts[1]
@@ -123,7 +142,7 @@ export async function verifyWorkerSessionToken(token: unknown): Promise<WorkerSe
})
}
return {
const verifiedSession: WorkerSession = {
sessionId,
workerId: Number(worker.id),
username: worker.username,
@@ -131,6 +150,23 @@ export async function verifyWorkerSessionToken(token: unknown): Promise<WorkerSe
expiresAt,
sessionVersion: currentSessionVersion,
}
verifiedSessionCache.set(normalizedToken, {
session: verifiedSession,
expiresAtMs: now + VERIFIED_SESSION_CACHE_TTL_MS,
})
// 控制缓存总数,防止内存泄漏
if (verifiedSessionCache.size > 10_000) {
const expiredNow = Date.now()
for (const [key, item] of verifiedSessionCache.entries()) {
if (item.expiresAtMs <= expiredNow) {
verifiedSessionCache.delete(key)
}
}
}
return verifiedSession
}
export async function getWorkerSessions(session: WorkerSession) {