You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
80 lines
2.3 KiB
80 lines
2.3 KiB
/**
|
|
* 角色卡限流(独立于 agent 的 rate-limit.ts)。
|
|
*
|
|
* 仅对未登录用户(访客)生效,与 agent 采用相同的阈值与实现:
|
|
* - 每会话上限 20 条
|
|
* - 每 IP 每日上限 50 条
|
|
* 内存计数(Map),按日期字符串跨天自动重置;进程重启即清零(与 agent 一致)。
|
|
*/
|
|
|
|
const SESSION_LIMIT = 20;
|
|
const IP_DAILY_LIMIT = 50;
|
|
|
|
interface RateCounter {
|
|
count: number;
|
|
date: string;
|
|
}
|
|
|
|
const sessionCounters = new Map<string, RateCounter>();
|
|
const ipCounters = new Map<string, RateCounter>();
|
|
|
|
function getTodayStr(): string {
|
|
return new Date().toISOString().slice(0, 10);
|
|
}
|
|
|
|
function getCounter(map: Map<string, RateCounter>, key: string): RateCounter {
|
|
const today = getTodayStr();
|
|
let counter = map.get(key);
|
|
if (!counter || counter.date !== today) {
|
|
counter = { count: 0, date: today };
|
|
map.set(key, counter);
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
function buildSessionKey(cardSlug: string, sessionId: string): string {
|
|
return `character:${cardSlug}:${sessionId}`;
|
|
}
|
|
|
|
function buildIpKey(cardSlug: string, ip: string): string {
|
|
return `character:${cardSlug}:${ip}`;
|
|
}
|
|
|
|
export interface CharacterRateLimitResult {
|
|
sessionRemaining: number;
|
|
ipRemaining: number;
|
|
blocked: boolean;
|
|
}
|
|
|
|
export function checkCharacterRateLimit(
|
|
cardSlug: string,
|
|
sessionId: string,
|
|
ip: string,
|
|
): CharacterRateLimitResult {
|
|
const sessionKey = buildSessionKey(cardSlug, sessionId);
|
|
const ipKey = buildIpKey(cardSlug, ip);
|
|
const sessionCounter = getCounter(sessionCounters, sessionKey);
|
|
const ipCounter = getCounter(ipCounters, ipKey);
|
|
|
|
const sessionRemaining = Math.max(0, SESSION_LIMIT - sessionCounter.count);
|
|
const ipRemaining = Math.max(0, IP_DAILY_LIMIT - ipCounter.count);
|
|
const blocked = sessionRemaining === 0 || ipRemaining === 0;
|
|
|
|
return { sessionRemaining, ipRemaining, blocked };
|
|
}
|
|
|
|
export function incrementCharacterRateLimit(
|
|
cardSlug: string,
|
|
sessionId: string,
|
|
ip: string,
|
|
): void {
|
|
const sessionKey = buildSessionKey(cardSlug, sessionId);
|
|
const ipKey = buildIpKey(cardSlug, ip);
|
|
const sessionCounter = getCounter(sessionCounters, sessionKey);
|
|
const ipCounter = getCounter(ipCounters, ipKey);
|
|
sessionCounter.count++;
|
|
ipCounter.count++;
|
|
}
|
|
|
|
export const CHARACTER_SESSION_LIMIT = SESSION_LIMIT;
|
|
export const CHARACTER_IP_DAILY_LIMIT = IP_DAILY_LIMIT;
|
|
|