forked from genz27/SanHub
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrate-limit.ts
More file actions
112 lines (97 loc) · 3.32 KB
/
Copy pathrate-limit.ts
File metadata and controls
112 lines (97 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// ========================================
// 请求限流(基于内存的滑动窗口)
// ========================================
interface RateLimitEntry {
count: number;
resetAt: number;
}
class RateLimiter {
private limits = new Map<string, RateLimitEntry>();
private cleanupInterval: NodeJS.Timeout | null = null;
constructor() {
// 每分钟清理过期记录
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
}
/**
* 检查是否超过限制
* @param key 限流键(如 IP 或用户 ID)
* @param maxRequests 时间窗口内最大请求数
* @param windowSeconds 时间窗口(秒)
* @returns { allowed: boolean, remaining: number, resetAt: number }
*/
check(
key: string,
maxRequests: number,
windowSeconds: number
): { allowed: boolean; remaining: number; resetAt: number } {
const now = Date.now();
const entry = this.limits.get(key);
// 如果没有记录或已过期,创建新记录
if (!entry || now > entry.resetAt) {
const resetAt = now + windowSeconds * 1000;
this.limits.set(key, { count: 1, resetAt });
return { allowed: true, remaining: maxRequests - 1, resetAt };
}
// 检查是否超过限制
if (entry.count >= maxRequests) {
return { allowed: false, remaining: 0, resetAt: entry.resetAt };
}
// 增加计数
entry.count++;
return { allowed: true, remaining: maxRequests - entry.count, resetAt: entry.resetAt };
}
private cleanup(): void {
const now = Date.now();
const entries = Array.from(this.limits.entries());
entries.forEach(([key, entry]) => {
if (now > entry.resetAt) {
this.limits.delete(key);
}
});
}
}
// 全局限流器实例
export const rateLimiter = new RateLimiter();
// 预定义的限流配置
export const RateLimitConfig = {
// API 通用限流:每分钟 60 次
API: { maxRequests: 60, windowSeconds: 60 },
// 生成 API:每分钟 10 次
GENERATE: { maxRequests: 30, windowSeconds: 60 },
// 聊天 API:每分钟 30 次
CHAT: { maxRequests: 30, windowSeconds: 60 },
// 登录 API:每分钟 5 次
AUTH: { maxRequests: 5, windowSeconds: 60 },
} as const;
// 获取客户端 IP
export function getClientIP(request: Request): string {
const trustProxy = process.env.TRUST_PROXY === 'true';
const forwarded = request.headers.get('x-forwarded-for');
if (trustProxy && forwarded) {
return forwarded.split(',')[0].trim();
}
const realIP = request.headers.get('x-real-ip');
if (realIP) {
return realIP;
}
return 'unknown';
}
// 限流检查辅助函数
export function checkRateLimit(
request: Request,
config: { maxRequests: number; windowSeconds: number },
keyPrefix = 'api'
): { allowed: boolean; remaining: number; resetAt: number; headers: Record<string, string> } {
const ip = getClientIP(request);
const key = `${keyPrefix}:${ip}`;
const result = rateLimiter.check(key, config.maxRequests, config.windowSeconds);
const headers: Record<string, string> = {
'X-RateLimit-Limit': String(config.maxRequests),
'X-RateLimit-Remaining': String(result.remaining),
'X-RateLimit-Reset': String(Math.ceil(result.resetAt / 1000)),
};
if (!result.allowed) {
headers['Retry-After'] = String(Math.ceil((result.resetAt - Date.now()) / 1000));
}
return { ...result, headers };
}