import time from collections import defaultdict from typing import Dict, Tuple from fastapi import HTTPException, Request class InMemoryRateLimiter: """基于滑动窗口的内存限流器。 按 IP 地址追踪请求次数,超出阈值时抛出 429。 适用于单实例部署;集群部署应替换为 Redis 后端。 """ def __init__(self, max_requests: int = 5, window_seconds: int = 60): self._max_requests = max_requests self._window_seconds = window_seconds self._requests: Dict[str, list[float]] = defaultdict(list) def _get_client_ip(self, request: Request) -> str: forwarded = request.headers.get("X-Forwarded-For") if forwarded: return forwarded.split(",")[0].strip() return request.client.host if request.client else "unknown" def _cleanup(self, key: str, now: float) -> None: cutoff = now - self._window_seconds self._requests[key] = [ t for t in self._requests[key] if t > cutoff ] def check(self, request: Request) -> None: now = time.time() key = self._get_client_ip(request) self._cleanup(key, now) if len(self._requests[key]) >= self._max_requests: raise HTTPException( status_code=429, detail="请求过于频繁,请稍后再试", ) self._requests[key].append(now) register_limiter = InMemoryRateLimiter(max_requests=5, window_seconds=60) login_limiter = InMemoryRateLimiter(max_requests=10, window_seconds=60)