第一步

This commit is contained in:
amb
2026-09-01 11:53:59 +08:00
commit 47bf6cc5ca
66 changed files with 6501 additions and 0 deletions
+74
View File
@@ -0,0 +1,74 @@
"""服务端 Session 管理(MVP: 内存 dict + TTL)。
- Session token: secrets.token_urlsafe(32)
- Cookie: HttpOnly, SameSite=Lax, 生产环境 Secure
- 默认过期: 7 天
未来切换 Redis: 改 _store 为 Redis 客户端,接口不变。
"""
import secrets
import time
from dataclasses import dataclass
from app.core.config import get_settings
# 内存存储:{token: SessionEntry}
_store: dict[str, "SessionEntry"] = {}
SESSION_COOKIE_NAME = "session_id"
SESSION_TTL_SECONDS = 7 * 24 * 3600 # 7 天
@dataclass
class SessionEntry:
user_id: str
created_at: float
last_accessed: float
def create_session(user_id: str) -> str:
"""创建 session,返回 token(写入 Cookie)。"""
_cleanup_expired()
token = secrets.token_urlsafe(32)
now = time.time()
_store[token] = SessionEntry(user_id=user_id, created_at=now, last_accessed=now)
return token
def get_session_user(token: str) -> str | None:
"""根据 token 获取 user_id;不存在或过期返回 None。"""
entry = _store.get(token)
if entry is None:
return None
now = time.time()
if now - entry.last_accessed > SESSION_TTL_SECONDS:
_store.pop(token, None)
return None
entry.last_accessed = now
return entry.user_id
def delete_session(token: str) -> None:
_store.pop(token, None)
def _cleanup_expired() -> None:
"""惰性清理过期 session。"""
now = time.time()
expired = [t for t, e in _store.items() if now - e.last_accessed > SESSION_TTL_SECONDS]
for t in expired:
_store.pop(t, None)
def get_cookie_params() -> dict:
"""构建 Set-Cookie 参数。"""
settings = get_settings()
return {
"key": SESSION_COOKIE_NAME,
"httponly": True,
"samesite": "lax",
"secure": settings.is_production,
"max_age": SESSION_TTL_SECONDS,
"path": "/",
}