forked from jiaoly/financial_system
267 lines
9.2 KiB
Python
267 lines
9.2 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile
|
|
|
|
from ...core.logger import AppLogger
|
|
from ...core.permissions import PERMISSION_USER_CREATE, PERMISSION_USER_LIST
|
|
from ...database.orm import UserORM
|
|
from ...services import AuthService, OperationLogService
|
|
from ..dependencies import get_auth_service, get_current_user, get_operation_log_service, require_permission
|
|
from ..request_meta import operation_request_meta
|
|
from ..schemas.auth import (
|
|
ChangePasswordRequest,
|
|
ChangePasswordResponse,
|
|
CreateUserRequest,
|
|
LoginRequest,
|
|
LoginResponse,
|
|
UpdateProfileRequest,
|
|
UserProfileDTO,
|
|
UserResponse,
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
logger = AppLogger.get_logger(__name__)
|
|
|
|
|
|
@router.post("/login", response_model=LoginResponse)
|
|
def login(
|
|
http_request: Request,
|
|
request: LoginRequest,
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> LoginResponse:
|
|
"""登录成功后返回 token、可见菜单和可执行操作权限。"""
|
|
try:
|
|
session = service.login(request.username, request.password)
|
|
except ValueError as exc:
|
|
operation_logs.record(
|
|
actor=None,
|
|
module="auth",
|
|
action="login",
|
|
target_type="user",
|
|
target_id=request.username,
|
|
status="failed",
|
|
detail=str(exc),
|
|
**operation_request_meta(http_request),
|
|
)
|
|
logger.error("登录接口失败 username=%s reason=%s", request.username, exc)
|
|
raise HTTPException(status_code=401, detail=str(exc)) from exc
|
|
operation_logs.record(
|
|
actor=session.user,
|
|
module="auth",
|
|
action="login",
|
|
target_type="user",
|
|
target_id=str(session.user.id),
|
|
status="success",
|
|
detail="用户登录成功",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return LoginResponse.from_session(session)
|
|
|
|
|
|
@router.get("/me", response_model=UserProfileDTO)
|
|
def me(current_user: UserORM = Depends(get_current_user)) -> UserProfileDTO:
|
|
"""前端刷新页面时用它恢复当前用户权限上下文。"""
|
|
return UserProfileDTO.from_orm_model(current_user)
|
|
|
|
|
|
@router.put("/me", response_model=UserProfileDTO)
|
|
def update_me(
|
|
http_request: Request,
|
|
request: UpdateProfileRequest,
|
|
current_user: UserORM = Depends(get_current_user),
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> UserProfileDTO:
|
|
"""当前登录用户修改个人基础信息。"""
|
|
try:
|
|
user = service.update_profile(
|
|
current_user.id,
|
|
display_name=request.display_name,
|
|
email=request.email,
|
|
phone=request.phone,
|
|
department=request.department,
|
|
position_title=request.position_title,
|
|
)
|
|
except ValueError as exc:
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="auth",
|
|
action="profile.update",
|
|
target_type="user",
|
|
target_id=str(current_user.id),
|
|
status="failed",
|
|
detail=str(exc),
|
|
**operation_request_meta(http_request),
|
|
)
|
|
logger.error("修改个人资料接口失败 user_id=%s reason=%s", current_user.id, exc)
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
operation_logs.record(
|
|
actor=user,
|
|
module="auth",
|
|
action="profile.update",
|
|
target_type="user",
|
|
target_id=str(user.id),
|
|
status="success",
|
|
detail="修改个人资料成功",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return UserProfileDTO.from_orm_model(user)
|
|
|
|
|
|
@router.put("/me/password", response_model=ChangePasswordResponse)
|
|
def change_my_password(
|
|
http_request: Request,
|
|
request: ChangePasswordRequest,
|
|
current_user: UserORM = Depends(get_current_user),
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> ChangePasswordResponse:
|
|
"""当前登录用户修改自己的登录密码。"""
|
|
try:
|
|
service.change_password(
|
|
current_user.id,
|
|
old_password=request.old_password,
|
|
new_password=request.new_password,
|
|
confirm_password=request.confirm_password,
|
|
)
|
|
except ValueError as exc:
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="auth",
|
|
action="password.change",
|
|
target_type="user",
|
|
target_id=str(current_user.id),
|
|
status="failed",
|
|
detail=str(exc),
|
|
**operation_request_meta(http_request),
|
|
)
|
|
logger.error("修改密码接口失败 user_id=%s reason=%s", current_user.id, exc)
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="auth",
|
|
action="password.change",
|
|
target_type="user",
|
|
target_id=str(current_user.id),
|
|
status="success",
|
|
detail="修改密码成功",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return ChangePasswordResponse(message="密码修改成功")
|
|
|
|
|
|
@router.post("/me/avatar", response_model=UserProfileDTO)
|
|
def update_my_avatar(
|
|
http_request: Request,
|
|
file: UploadFile = File(...),
|
|
current_user: UserORM = Depends(get_current_user),
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> UserProfileDTO:
|
|
"""当前登录用户上传并修改头像。"""
|
|
try:
|
|
user = service.update_avatar(
|
|
current_user.id,
|
|
filename=file.filename or "avatar.png",
|
|
stream=file.file,
|
|
)
|
|
except ValueError as exc:
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="auth",
|
|
action="avatar.update",
|
|
target_type="user",
|
|
target_id=str(current_user.id),
|
|
status="failed",
|
|
detail=str(exc),
|
|
**operation_request_meta(http_request),
|
|
)
|
|
logger.error("修改头像接口失败 user_id=%s filename=%s reason=%s", current_user.id, file.filename, exc)
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
operation_logs.record(
|
|
actor=user,
|
|
module="auth",
|
|
action="avatar.update",
|
|
target_type="user",
|
|
target_id=str(user.id),
|
|
status="success",
|
|
detail=f"修改头像成功 filename={file.filename or 'avatar.png'}",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return UserProfileDTO.from_orm_model(user)
|
|
|
|
|
|
@router.post(
|
|
"/users",
|
|
response_model=UserResponse,
|
|
)
|
|
def create_user(
|
|
http_request: Request,
|
|
request: CreateUserRequest,
|
|
current_user: UserORM = Depends(require_permission(PERMISSION_USER_CREATE)),
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> UserResponse:
|
|
"""只有超级用户可以创建用户,角色决定菜单和操作权限。"""
|
|
try:
|
|
user = service.create_user(
|
|
username=request.username,
|
|
password=request.password,
|
|
role=request.role,
|
|
display_name=request.display_name,
|
|
email=request.email,
|
|
phone=request.phone,
|
|
department=request.department,
|
|
position_title=request.position_title,
|
|
)
|
|
except ValueError as exc:
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="system",
|
|
action="user.create",
|
|
target_type="user",
|
|
target_id=request.username,
|
|
status="failed",
|
|
detail=str(exc),
|
|
**operation_request_meta(http_request),
|
|
)
|
|
logger.error("创建用户接口失败 username=%s role=%s reason=%s", request.username, request.role, exc)
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="system",
|
|
action="user.create",
|
|
target_type="user",
|
|
target_id=str(user.id),
|
|
status="success",
|
|
detail=f"创建用户成功 username={user.username} role={user.role}",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return UserResponse.from_orm_model(user)
|
|
|
|
|
|
@router.get(
|
|
"/users",
|
|
response_model=list[UserResponse],
|
|
)
|
|
def list_users(
|
|
http_request: Request,
|
|
current_user: UserORM = Depends(require_permission(PERMISSION_USER_LIST)),
|
|
service: AuthService = Depends(get_auth_service),
|
|
operation_logs: OperationLogService = Depends(get_operation_log_service),
|
|
) -> list[UserResponse]:
|
|
"""用户管理菜单使用的用户列表。"""
|
|
users = service.list_users()
|
|
operation_logs.record(
|
|
actor=current_user,
|
|
module="system",
|
|
action="user.list",
|
|
target_type="user",
|
|
target_id="",
|
|
status="success",
|
|
detail=f"查询用户列表 count={len(users)}",
|
|
**operation_request_meta(http_request),
|
|
)
|
|
return [UserResponse.from_orm_model(user) for user in users]
|