/** * 速率限制中间件 * 使用内存存储(生产环境建议使用Redis) */ interface RateLimitStore { [key: string]: { count: number; resetTime: number; }; } class RateLimiter { private store: RateLimitStore = {}; private windowMs: number; private maxRequests: number; constructor(windowMs: number = 60000, maxRequests: number = 100) { this.windowMs = windowMs; this.maxRequests = maxRequests; // 定期清理过期记录 setInterval(() => this.cleanup(), this.windowMs); } /** * 检查是否超过速率限制 */ check(identifier: string): { allowed: boolean; remaining: number; resetTime: number } { const now = Date.now(); const record = this.store[identifier]; if (!record || now > record.resetTime) { // 创建新记录 this.store[identifier] = { count: 1, resetTime: now + this.windowMs, }; return { allowed: true, remaining: this.maxRequests - 1, resetTime: now + this.windowMs, }; } if (record.count >= this.maxRequests) { return { allowed: false, remaining: 0, resetTime: record.resetTime, }; } record.count++; return { allowed: true, remaining: this.maxRequests - record.count, resetTime: record.resetTime, }; } /** * 清理过期记录 */ private cleanup(): void { const now = Date.now(); for (const key in this.store) { if (this.store[key].resetTime < now) { delete this.store[key]; } } } } // 创建不同级别的速率限制器 export const rateLimiters = { // 通用API限制:每分钟100次请求 api: new RateLimiter(60000, 100), // 管理后台接口:设置页会并发加载大量配置,不能和公开 API 共用低阈值 admin: new RateLimiter(60000, 3000), // 文件上传接口:每分钟60次请求(独立于通用API限流) upload: new RateLimiter(60000, 60), // 图片代理接口:每分钟3000次请求(图片列表/画布高频加载) imageProxy: new RateLimiter(60000, 3000), // 高频只读接口(首页/画布轮询):每分钟1200次请求 uiPolling: new RateLimiter(60000, 1200), // 画布保存接口:每分钟300次请求(含自动保存) canvas: new RateLimiter(60000, 300), // 实时画布相关接口:每分钟600次请求(配置/指标/代理) realtime: new RateLimiter(60000, 600), // 聊天接口限制:每分钟60次请求(避免被高频轮询接口挤占额度) chat: new RateLimiter(60000, 60), // 严格限制:每分钟10次请求(用于敏感操作) strict: new RateLimiter(60000, 10), // 登录限制:每分钟5次请求 auth: new RateLimiter(60000, 5), // IP限制:每分钟50次请求 ip: new RateLimiter(60000, 50), }; /** * 获取客户端标识符(IP地址) */ export function getClientIdentifier(request: Request): string { // 优先使用x-forwarded-for(考虑代理) const forwarded = request.headers.get("x-forwarded-for"); if (forwarded) { return forwarded.split(",")[0].trim(); } // 使用x-real-ip const realIP = request.headers.get("x-real-ip"); if (realIP) { return realIP; } // 降级到unknown return "unknown"; } /** * 速率限制检查函数 */ export function checkRateLimit( request: Request, limiter: RateLimiter = rateLimiters.api ): { allowed: boolean; remaining: number; resetTime: number } { const identifier = getClientIdentifier(request); return limiter.check(identifier); } export function checkRateLimitByIdentifier( identifier: string, limiter: RateLimiter = rateLimiters.api ): { allowed: boolean; remaining: number; resetTime: number } { return limiter.check(identifier); }