feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复

- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
This commit is contained in:
snowy-yang committed 2026-10-23 21:55:45 +08:00
1 parent 800871ef6e
commit dfc06bf4de
182 files changed
+26620

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
"""全局限流 —— per-IP 滑动窗口(内存实现,无额外依赖)。
RATE_LIMIT 环境变量:每 IP 每分钟允许的请求数(默认 120;设 0 关闭)。
在鉴权之前计数(401 爆破秘钥同样受限);超限返回 429 + Retry-After。
"""
from __future__ import annotations
import time
from starlette.requests import Request
from starlette.responses import JSONResponse
WINDOW_SECONDS = 60.0
def make_rate_limiter(limit_per_minute: int):
hits: dict[str, list[float]] = {}
last_cleanup = 0.0
async def middleware(request: Request, call_next):
nonlocal last_cleanup
if request.method == "OPTIONS" or limit_per_minute <= 0:
return await call_next(request)
now = time.monotonic()
# 惰性清理:每窗口顺带删除已过期的 IP 记录,防内存无限增长
if now - last_cleanup > WINDOW_SECONDS:
threshold = now - WINDOW_SECONDS
for ip in [ip for ip, times in hits.items() if not times or times[-1] <= threshold]:
hits.pop(ip, None)
last_cleanup = now
ip = request.client.host if request.client else "unknown"
times = [t for t in hits.get(ip, []) if t > now - WINDOW_SECONDS]
times.append(now)
hits[ip] = times
if len(times) > limit_per_minute:
retry_after = max(1, int(times[0] + WINDOW_SECONDS - now) + 1)
return JSONResponse(
{"code": 429, "msg": f"请求过于频繁(限 {limit_per_minute} 次/分钟),请 {retry_after} 秒后重试"},
status_code=429,
headers={"Retry-After": str(retry_after)},
)
return await call_next(request)
return middleware