诚实性修复: API限流Retry-After(wait=60s)+断言 / purge定时接线(04:00 cron+actor+测试) / Channel表单校验(ref+rules+validate) / README PG数字513

This commit is contained in:
seagull
2026-09-12 02:20:50 +08:00
parent b3f3095d53
commit 67540e8d30
40 changed files with 1805 additions and 31 deletions
+13
View File
@@ -72,6 +72,8 @@ async def login(request):
"""商城登录:{phone, password, tenant_code?} → {token, customer}"""
from django.conf import settings
from apps.core import ratelimit as rl
payload = request.data or {}
phone = (payload.get("phone") or "").strip()
password = payload.get("password") or ""
@@ -81,6 +83,15 @@ async def login(request):
if not phone or not password:
raise ValidationError({"detail": "phone 与 password 必填"})
# P0-5:商城登录同样防爆破(按 phone + IP)。
ip = rl.client_ip(request)
login_id = f"{tenant_code}:{phone}"
if rl.is_login_locked(login_id, ip):
return Response(
{"code": "login_locked", "detail": "登录失败次数过多,账号已临时锁定 15 分钟"},
status=status.HTTP_429_TOO_MANY_REQUESTS,
)
def _do():
tenant = resolve_tenant(tenant_code)
if tenant is None:
@@ -106,9 +117,11 @@ async def login(request):
raise
if account is None:
rl.record_login_failure(login_id, ip)
return Response({"code": "invalid_credentials", "detail": "手机号或密码不正确"},
status=status.HTTP_401_UNAUTHORIZED)
rl.clear_login_failures(login_id, ip)
token = await sync_to_async(issue_session_token)(account)
return Response({
"token": token,