from typing import Dict, Union from datetime import datetime, timedelta from fastapi import HTTPException, Request class IPRateLimit: def __init__(self, count: int, minutes: int): self.ips: Dict[str, Dict[str, Union[int, datetime]]] = {} self.count = count self.minutes = minutes def check_ip(self, ip: str) -> bool: if ip in self.ips: ip_info = self.ips[ip] if ip_info["count"] >= self.count: if ip_info["time"] + timedelta(minutes=self.minutes) > datetime.now(): return False self.ips.pop(ip) return True def add_ip(self, ip: str) -> int: ip_info = self.ips.get(ip, {"count": 0, "time": datetime.now()}) ip_info["count"] += 1 ip_info["time"] = datetime.now() self.ips[ip] = ip_info return ip_info["count"] async def remove_expired_ip(self) -> None: now = datetime.now() expiration = timedelta(minutes=self.minutes) self.ips = { ip: info for ip, info in self.ips.items() if info["time"] + expiration >= now } def __call__(self, request: Request) -> str: ip = ( request.headers.get("X-Real-IP") or request.headers.get("X-Forwarded-For") or request.client.host ) if not self.check_ip(ip): raise HTTPException(status_code=423, detail="请求次数过多,请稍后再试") return ip