From 3f96c9c6502243b06efd4bb34a189e72e3a5557c Mon Sep 17 00:00:00 2001
From: yearning <10538594+wangweifeng1999@user.noreply.gitee.com>
Date: 星期一, 28 九月 2026 19:24:55 +0800
Subject: [PATCH] 复制项目
---
backend/app/login_guard.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 55 insertions(+), 0 deletions(-)
diff --git a/backend/app/login_guard.py b/backend/app/login_guard.py
new file mode 100644
index 0000000..7a7f9c8
--- /dev/null
+++ b/backend/app/login_guard.py
@@ -0,0 +1,55 @@
+from datetime import datetime, timedelta
+
+from fastapi import HTTPException
+from sqlalchemy.orm import Session
+
+from app.config import settings
+from app.models import LoginLock
+
+
+def _lock_message(locked_until: datetime) -> str:
+ remain = int((locked_until - datetime.utcnow()).total_seconds())
+ if remain < 60:
+ return f"鐧诲綍灏濊瘯杩囧锛岃 {max(remain, 1)} 绉掑悗閲嶈瘯"
+ minutes = max(remain // 60, 1)
+ return f"鐧诲綍灏濊瘯杩囧锛岃 {minutes} 鍒嗛挓鍚庨噸璇�"
+
+
+def ensure_login_allowed(db: Session, username: str) -> None:
+ key = username.strip().lower()
+ if not key:
+ return
+ row = db.get(LoginLock, key)
+ if not row or not row.locked_until:
+ return
+ if row.locked_until <= datetime.utcnow():
+ row.fail_count = 0
+ row.locked_until = None
+ db.commit()
+ return
+ raise HTTPException(429, detail=_lock_message(row.locked_until))
+
+
+def record_login_failure(db: Session, username: str) -> None:
+ key = username.strip().lower()
+ if not key:
+ return
+ row = db.get(LoginLock, key)
+ if not row:
+ row = LoginLock(username=key, fail_count=0)
+ db.add(row)
+ row.fail_count += 1
+ row.updated_at = datetime.utcnow()
+ if row.fail_count >= settings.login_max_failures:
+ row.locked_until = datetime.utcnow() + timedelta(minutes=settings.login_lock_minutes)
+ db.commit()
+
+
+def clear_login_lock(db: Session, username: str) -> None:
+ key = username.strip().lower()
+ if not key:
+ return
+ row = db.get(LoginLock, key)
+ if row:
+ db.delete(row)
+ db.commit()
--
Gitblit v1.8.0