ai-chatbot/lib/security/rate-limiter.ts
Your Name f0ede6f0c9
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
修复画布Agent误澄清与输入标签污染并优化chat限流
2026-02-14 10:35:11 +08:00

123 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 速率限制中间件
* 使用内存存储(生产环境建议使用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),
// 聊天接口限制:每分钟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);
}