133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
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,
|
||
job_title=user.job_title,
|
||
unit_id=user.unit_id,
|
||
unit_name=user.unit.name if user.unit else None,
|
||
department_id=user.department_id,
|
||
department_name=user.department.name if user.department 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)
|