diff --git a/.idea/claudeCodeTabState.xml b/.idea/claudeCodeTabState.xml index 08b9482..9ad4cd3 100644 --- a/.idea/claudeCodeTabState.xml +++ b/.idea/claudeCodeTabState.xml @@ -8,7 +8,7 @@ diff --git a/api/router.py b/api/router.py index dd4ab2a..f39101e 100644 --- a/api/router.py +++ b/api/router.py @@ -3,8 +3,7 @@ """ from fastapi import APIRouter -from api.routers import auth, health +from api.routers import auth api_router = APIRouter() -api_router.include_router(health.router, prefix="/api", tags=["系统"]) api_router.include_router(auth.router, prefix="/api", tags=["认证"]) \ No newline at end of file diff --git a/api/routers/auth.py b/api/routers/auth.py index 358ae82..df51975 100644 --- a/api/routers/auth.py +++ b/api/routers/auth.py @@ -1,38 +1,21 @@ -"""认证路由:登录签发 JWT + 当前用户信息(get_current_user 依赖注入示例)。""" +"""认证路由:登录 + 当前用户(业务在 service/auth.py,路由只做编排)。""" from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from api.deps import get_current_user from config.deps import get_db -from schemas.auth import LoginReq from model.sys_user import SysUser -from repositories.sys_user import SysUserRepo -from service.auth import create_token, verify_password -from utils.exceptions import ForbiddenError, ParamError +from schemas.auth import LoginReq +from service.auth import login as auth_login +from service.auth import user_payload from utils.response import success router = APIRouter() -def user_payload(u: SysUser) -> dict: - return { - "id": u.id, - "username": u.username, - "user_type": u.user_type, - "employee_role": u.employee_role, - "customer_level": u.customer_level, - "status": u.status, - } - - @router.post("/auth/login") async def login(body: LoginReq, db: AsyncSession = Depends(get_db)): - user = await SysUserRepo(db).get_by_username(body.username) - if user is None or not verify_password(body.password, user.password_hash): - raise ParamError("用户名或密码错误") - if user.status != "正常": - raise ForbiddenError("账号状态异常,无法登录") - return success({"token": create_token(user.id), "user": user_payload(user)}) + return success(await auth_login(db, body.username, body.password)) @router.get("/auth/me") diff --git a/api/routers/health.py b/api/routers/health.py deleted file mode 100644 index 526fa25..0000000 --- a/api/routers/health.py +++ /dev/null @@ -1,17 +0,0 @@ -"""系统健康检查:四库就绪状态 + 各库探测耗时(任一失败返回 503 + 明细)。""" -from fastapi import APIRouter -from fastapi.responses import JSONResponse - -from config.database import check_ready_detail - -router = APIRouter() - - -@router.get("/health/ready") -async def health_ready(): - dbs = await check_ready_detail() - all_ok = all(v["status"] == "ok" for v in dbs.values()) - return JSONResponse( - status_code=200 if all_ok else 503, - content={"status": "ready" if all_ok else "degraded", "dbs": dbs}, - ) \ No newline at end of file diff --git a/service/auth.py b/service/auth.py index 9e2060d..3ca1a2e 100644 --- a/service/auth.py +++ b/service/auth.py @@ -1,4 +1,4 @@ -"""认证服务:JWT 签发/校验(HS256)+ 密码哈希/校验。 +"""认证服务:登录业务 + JWT 签发/校验(HS256)+ 密码哈希/校验。 密码用 stdlib PBKDF2(免 bcrypt 依赖),存储格式自描述: pbkdf2_sha256$600000$$ @@ -9,8 +9,12 @@ import secrets import time import jwt +from sqlalchemy.ext.asyncio import AsyncSession from config.settings import settings +from model.sys_user import SysUser +from repositories.sys_user import SysUserRepo +from utils.exceptions import ForbiddenError, ParamError _ITERATIONS = 600_000 @@ -43,4 +47,27 @@ def verify_password(password: str, stored: str) -> bool: except ValueError: return False calc = hashlib.pbkdf2_hmac("sha256", password.encode(), bytes.fromhex(salt), int(iters)) - return hmac.compare_digest(calc.hex(), digest) \ No newline at end of file + return hmac.compare_digest(calc.hex(), digest) + + +# ---- 登录业务(路由层只编排,不碰数据/逻辑) ---- +def user_payload(u: SysUser) -> dict: + """对外暴露的用户信息(不含密码等敏感字段)。""" + return { + "id": u.id, + "username": u.username, + "user_type": u.user_type, + "employee_role": u.employee_role, + "customer_level": u.customer_level, + "status": u.status, + } + + +async def login(db: AsyncSession, username: str, password: str) -> dict: + """校验账号密码与状态,成功签发 token 并返回用户信息;失败抛业务异常。""" + user = await SysUserRepo(db).get_by_username(username) + if user is None or not verify_password(password, user.password_hash): + raise ParamError("用户名或密码错误") + if user.status != "正常": + raise ForbiddenError("账号状态异常,无法登录") + return {"token": create_token(user.id), "user": user_payload(user)} \ No newline at end of file