|
| 1 | +""" |
| 2 | +域名限速器模块 |
| 3 | +""" |
| 4 | + |
| 5 | +import time |
| 6 | +import threading |
| 7 | +from collections import deque, defaultdict |
| 8 | +from urllib.parse import urlparse |
| 9 | +from typing import Dict |
| 10 | + |
| 11 | +from mcim_sync.config import Config |
| 12 | + |
| 13 | + |
| 14 | +class DomainRateLimiter: |
| 15 | + """简单的域名限速器""" |
| 16 | + |
| 17 | + def __init__(self): |
| 18 | + self.config = Config.load() |
| 19 | + self.domain_requests: Dict[str, deque] = defaultdict(deque) |
| 20 | + self.locks: Dict[str, threading.Lock] = defaultdict(threading.Lock) |
| 21 | + |
| 22 | + def get_domain_from_url(self, url: str) -> str: |
| 23 | + """从URL中提取域名""" |
| 24 | + try: |
| 25 | + parsed = urlparse(url) |
| 26 | + return parsed.netloc.lower() |
| 27 | + except Exception: |
| 28 | + return "unknown" |
| 29 | + |
| 30 | + def can_make_request(self, url: str) -> bool: |
| 31 | + """检查是否可以向指定URL发起请求""" |
| 32 | + domain = self.get_domain_from_url(url) |
| 33 | + |
| 34 | + # 如果域名没有配置限速,则允许请求 |
| 35 | + if domain not in self.config.domain_rate_limits: |
| 36 | + return True |
| 37 | + |
| 38 | + domain_config = self.config.domain_rate_limits[domain] |
| 39 | + current_time = time.time() |
| 40 | + |
| 41 | + with self.locks[domain]: |
| 42 | + requests = self.domain_requests[domain] |
| 43 | + |
| 44 | + # 清理超出时间窗口的请求记录 |
| 45 | + while requests and current_time - requests[0] > domain_config.time_window: |
| 46 | + requests.popleft() |
| 47 | + |
| 48 | + # 检查是否超过最大请求数 |
| 49 | + return len(requests) < domain_config.max_requests |
| 50 | + |
| 51 | + def record_request(self, url: str): |
| 52 | + """记录请求""" |
| 53 | + domain = self.get_domain_from_url(url) |
| 54 | + |
| 55 | + # 如果域名没有配置限速,则不记录 |
| 56 | + if domain not in self.config.domain_rate_limits: |
| 57 | + return |
| 58 | + |
| 59 | + current_time = time.time() |
| 60 | + |
| 61 | + with self.locks[domain]: |
| 62 | + self.domain_requests[domain].append(current_time) |
| 63 | + |
| 64 | + def wait_time(self, url: str) -> float: |
| 65 | + """计算需要等待的时间""" |
| 66 | + domain = self.get_domain_from_url(url) |
| 67 | + |
| 68 | + # 如果域名没有配置限速,则不需要等待 |
| 69 | + if domain not in self.config.domain_rate_limits: |
| 70 | + return 0.0 |
| 71 | + |
| 72 | + domain_config = self.config.domain_rate_limits[domain] |
| 73 | + current_time = time.time() |
| 74 | + |
| 75 | + with self.locks[domain]: |
| 76 | + requests = self.domain_requests[domain] |
| 77 | + |
| 78 | + # 清理超出时间窗口的请求记录 |
| 79 | + while requests and current_time - requests[0] > domain_config.time_window: |
| 80 | + requests.popleft() |
| 81 | + |
| 82 | + # 如果请求数已满,计算等待时间 |
| 83 | + if len(requests) >= domain_config.max_requests: |
| 84 | + oldest_request = requests[0] |
| 85 | + return domain_config.time_window - (current_time - oldest_request) |
| 86 | + |
| 87 | + return 0.0 |
| 88 | + |
| 89 | + def get_domain_status(self, domain: str) -> Dict: |
| 90 | + """获取域名的限速状态""" |
| 91 | + if domain not in self.config.domain_rate_limits: |
| 92 | + return {"configured": False} |
| 93 | + |
| 94 | + domain_config = self.config.domain_rate_limits[domain] |
| 95 | + current_time = time.time() |
| 96 | + |
| 97 | + with self.locks[domain]: |
| 98 | + requests = self.domain_requests[domain] |
| 99 | + |
| 100 | + # 清理超出时间窗口的请求记录 |
| 101 | + while requests and current_time - requests[0] > domain_config.time_window: |
| 102 | + requests.popleft() |
| 103 | + |
| 104 | + return { |
| 105 | + "configured": True, |
| 106 | + "max_requests": domain_config.max_requests, |
| 107 | + "time_window": domain_config.time_window, |
| 108 | + "current_requests": len(requests), |
| 109 | + "remaining_requests": domain_config.max_requests - len(requests), |
| 110 | + "next_reset_time": requests[0] + domain_config.time_window if requests else current_time |
| 111 | + } |
| 112 | + |
| 113 | + |
| 114 | +# 全局限速器实例 |
| 115 | +domain_rate_limiter = DomainRateLimiter() |
0 commit comments