rate-limiter.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. // IP限制工具类
  2. interface RateLimitRecord {
  3. count: number;
  4. firstRequest: number;
  5. endpoint: string;
  6. }
  7. class RateLimiter {
  8. private records: Map<string, RateLimitRecord[]> = new Map();
  9. private readonly windowMs = 120 * 1000; // 1分钟
  10. private readonly maxRequests = 1; // 每个API每分钟最多1次
  11. /**
  12. * 清理过期记录
  13. */
  14. private cleanup(): void {
  15. const now = Date.now();
  16. const entries = Array.from(this.records.entries());
  17. for (const [ip, requests] of entries) {
  18. const validRequests = requests.filter(
  19. (record: RateLimitRecord) => now - record.firstRequest < this.windowMs
  20. );
  21. if (validRequests.length === 0) {
  22. this.records.delete(ip);
  23. } else {
  24. this.records.set(ip, validRequests);
  25. }
  26. }
  27. }
  28. /**
  29. * 检查IP是否被限制
  30. */
  31. public isRateLimited(ip: string, endpoint: string): boolean {
  32. this.cleanup();
  33. const requests = this.records.get(ip) || [];
  34. const now = Date.now();
  35. // 过滤出时间窗口内的请求
  36. const validRequests = requests.filter(
  37. record => now - record.firstRequest < this.windowMs
  38. );
  39. // 检查该IP在指定endpoint上的请求次数
  40. const endpointRequests = validRequests.filter(record => record.endpoint === endpoint);
  41. if (endpointRequests.length >= this.maxRequests) {
  42. return true;
  43. }
  44. // 检查该IP的总请求次数是否超过2(两个API各一次)
  45. const totalEndpoints = new Set(validRequests.map(record => record.endpoint));
  46. if (totalEndpoints.size >= 2 && !totalEndpoints.has(endpoint)) {
  47. return true;
  48. }
  49. return false;
  50. }
  51. /**
  52. * 记录请求
  53. */
  54. public recordRequest(ip: string, endpoint: string): void {
  55. const requests = this.records.get(ip) || [];
  56. const now = Date.now();
  57. requests.push({
  58. count: 1,
  59. firstRequest: now,
  60. endpoint
  61. });
  62. this.records.set(ip, requests);
  63. }
  64. /**
  65. * 获取剩余时间(毫秒)
  66. */
  67. public getTimeUntilReset(ip: string, endpoint: string): number {
  68. const requests = this.records.get(ip) || [];
  69. const endpointRequests = requests.filter(record => record.endpoint === endpoint);
  70. if (endpointRequests.length === 0) {
  71. return 0;
  72. }
  73. const oldestRequest = Math.min(...endpointRequests.map(record => record.firstRequest));
  74. const resetTime = oldestRequest + this.windowMs;
  75. return Math.max(0, resetTime - Date.now());
  76. }
  77. }
  78. // 全局实例
  79. const rateLimiter = new RateLimiter();
  80. /**
  81. * 获取客户端真实IP地址
  82. */
  83. type RequestWithIP = {
  84. headers: Record<string, string | string[] | undefined>;
  85. connection?: { remoteAddress?: string };
  86. socket?: { remoteAddress?: string };
  87. body?: unknown;
  88. };
  89. export function getClientIP(req: RequestWithIP): string {
  90. const forwarded = req.headers['x-forwarded-for'];
  91. const realIP = req.headers['x-real-ip'];
  92. const remoteAddress = req.connection?.remoteAddress || req.socket?.remoteAddress;
  93. if (typeof forwarded === 'string') {
  94. return forwarded.split(',')[0].trim();
  95. }
  96. if (typeof realIP === 'string') {
  97. return realIP;
  98. }
  99. return remoteAddress || 'unknown';
  100. }
  101. /**
  102. * IP限制中间件
  103. */
  104. export function withRateLimit(endpoint: string) {
  105. return function <T extends RequestWithIP, U>(handler: (req: T, res: U) => Promise<unknown>) {
  106. return async function (req: T, res: U & { status: (code: number) => { json: (data: unknown) => unknown } }) {
  107. try {
  108. const ip = getClientIP(req);
  109. console.log(`[Rate Limiter] IP: ${ip}, Endpoint: ${endpoint}`);
  110. // 检查IP是否被限制
  111. if (rateLimiter.isRateLimited(ip, endpoint)) {
  112. const timeUntilReset = rateLimiter.getTimeUntilReset(ip, endpoint);
  113. const resetTime = Math.ceil(timeUntilReset / 1000);
  114. console.log(`[Rate Limiter] 限制访问 - IP: ${ip}, Endpoint: ${endpoint}, 重置时间: ${resetTime}秒`);
  115. // 打印请求的 body(如果是 POST 请求)
  116. if (req.body) {
  117. console.log(`[Rate Limiter] 被限制的请求 body:`, JSON.stringify(req.body, null, 2));
  118. }
  119. return res.status(429).json({
  120. error: '请求过于频繁',
  121. message: `同一IP每分钟只能调用每个API一次,请等待 ${resetTime} 秒后再试`,
  122. retryAfter: resetTime
  123. });
  124. }
  125. // 记录请求
  126. rateLimiter.recordRequest(ip, endpoint);
  127. console.log(`[Rate Limiter] 记录请求 - IP: ${ip}, Endpoint: ${endpoint}`);
  128. // 继续执行原有的处理函数
  129. return await handler(req, res);
  130. } catch (error) {
  131. console.error(`[Rate Limiter] 错误:`, error);
  132. // 如果 rate limiter 出错,继续执行原有的处理函数
  133. return await handler(req, res);
  134. }
  135. };
  136. };
  137. }
  138. export default rateLimiter;