60 lines
1.5 KiB
TypeScript
60 lines
1.5 KiB
TypeScript
// 简单的内存速率限制器(基于 IP)
|
|
|
|
interface RateLimitEntry {
|
|
count: number;
|
|
resetAt: number;
|
|
}
|
|
|
|
const store = new Map<string, RateLimitEntry>();
|
|
|
|
// 定期清理过期条目,防止内存泄漏
|
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 分钟
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, entry] of store) {
|
|
if (now >= entry.resetAt) {
|
|
store.delete(key);
|
|
}
|
|
}
|
|
}, CLEANUP_INTERVAL_MS);
|
|
|
|
/**
|
|
* 检查请求是否超过速率限制
|
|
* @param key 限制键(通常是 IP 地址)
|
|
* @param maxAttempts 窗口期内最大尝试次数
|
|
* @param windowMs 窗口期(毫秒)
|
|
* @returns 如果超限返回 true,否则返回 false
|
|
*/
|
|
export function isRateLimited(
|
|
key: string,
|
|
maxAttempts: number = 5,
|
|
windowMs: number = 60 * 1000
|
|
): boolean {
|
|
const now = Date.now();
|
|
const entry = store.get(key);
|
|
|
|
if (!entry || now >= entry.resetAt) {
|
|
store.set(key, { count: 1, resetAt: now + windowMs });
|
|
return false;
|
|
}
|
|
|
|
entry.count += 1;
|
|
return entry.count > maxAttempts;
|
|
}
|
|
|
|
/**
|
|
* 从 Request 对象中提取客户端 IP
|
|
*/
|
|
export function getClientIp(req: Request): string {
|
|
// 常见的代理头
|
|
const forwarded = req.headers.get("x-forwarded-for");
|
|
if (forwarded) {
|
|
return forwarded.split(",")[0].trim();
|
|
}
|
|
const realIp = req.headers.get("x-real-ip");
|
|
if (realIp) {
|
|
return realIp.trim();
|
|
}
|
|
return "unknown";
|
|
}
|