finai/backend/app/api/auth.py

132 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.orm import Session
from datetime import datetime
from app.core.security import create_access_token
from app.config import get_settings
from app.db import get_db
from app.deps import get_current_user
from app.integrations.sms_client import SmsClient
from app.integrations.wechat_client import WechatMiniClient, WechatMiniClientError
from app.models import Permission, User
from app.schemas.auth import (
CurrentUserResponse,
LoginRequest,
SmsCodeRequest,
TokenResponse,
WechatLoginRequest,
)
from app.services.audit_service import write_audit
from app.services.user_service import get_or_create_login_user
router = APIRouter()
def _build_current_user(db: Session, user: User) -> CurrentUserResponse:
if user.is_superuser:
permissions = sorted(db.scalars(select(Permission.code)).all())
else:
permissions = sorted({
p.code
for role in user.roles
for p in role.permissions
})
return CurrentUserResponse(
id=user.id,
phone=user.phone,
name=user.name,
department=user.department,
job_title=user.job_title,
unit_id=user.unit_id,
unit_name=user.unit.name if user.unit else None,
status=user.status,
roles=[role.code for role in user.roles],
permissions=permissions,
is_superuser=user.is_superuser,
)
@router.post("/sms-code")
def send_sms_code(payload: SmsCodeRequest) -> dict:
code = "123456"
SmsClient().send_code(payload.phone, code)
return {"message": "验证码已发送", "mock_code": code}
@router.post("/login", response_model=TokenResponse)
def login(payload: LoginRequest, db: Session = Depends(get_db)) -> TokenResponse:
if payload.code != "123456":
raise HTTPException(status_code=400, detail="验证码错误或已过期")
user = get_or_create_login_user(db, payload.phone)
if user.status != "active":
raise HTTPException(status_code=403, detail="账号已禁用")
write_audit(db, "auth.login", actor_id=user.id, target_type="user", target_id=str(user.id))
db.commit()
db.refresh(user)
return TokenResponse(
access_token=create_access_token(str(user.id)),
user=_build_current_user(db, user),
)
@router.post("/wechat-login", response_model=TokenResponse)
def wechat_login(payload: WechatLoginRequest, db: Session = Depends(get_db)) -> TokenResponse:
"""微信授权登录:拿手机号在用户表中匹配,找到则登录,找不到提示联系管理员。"""
settings = get_settings()
client = WechatMiniClient()
phone: str | None = None
if client.configured and payload.phone_code:
try:
phone = client.get_phone_number(payload.phone_code)
except WechatMiniClientError as exc:
raise HTTPException(status_code=400, detail=str(exc))
elif payload.phone:
# 未配置微信凭据或前端未拿到 phone_code 时的兜底(开发/联调)
phone = payload.phone.strip()
elif not client.configured:
# mock 模式且未传 phone用配置里的测试手机号方便本地联调
phone = settings.wx_mini_mock_phone
if not phone:
raise HTTPException(status_code=400, detail="未获取到微信手机号")
# 可选:换 openid 做审计追踪,失败不阻断登录
if client.configured and payload.js_code:
try:
client.code2session(payload.js_code)
except WechatMiniClientError:
pass
user = db.scalar(select(User).where(User.phone == phone))
if user is None:
raise HTTPException(status_code=403, detail="未找到匹配账号,请联系管理员添加账号后使用")
if user.status != "active":
raise HTTPException(status_code=403, detail="账号已禁用")
user.last_login_at = datetime.utcnow()
write_audit(db, "auth.wechat_login", actor_id=user.id, target_type="user", target_id=str(user.id))
db.commit()
db.refresh(user)
return TokenResponse(
access_token=create_access_token(str(user.id)),
user=_build_current_user(db, user),
)
@router.post("/logout")
def logout(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)) -> dict:
write_audit(db, "auth.logout", actor_id=current_user.id, target_type="user", target_id=str(current_user.id))
db.commit()
return {"message": "已退出"}
@router.get("/me", response_model=CurrentUserResponse)
def me(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
) -> CurrentUserResponse:
return _build_current_user(db, current_user)