"""用户管理接口(需求 2.5.1:用户账号管理)。 全部要求管理员权限 —— 按需求「系统管理员(全部权限)、普通用户(业务操作)」, 用户管理属管理员专属。 删除、禁用、改角色三处都有**自我保护**:不允许对当前登录账号执行, 否则管理员误操作会立刻失去全部管理权限且无法自行恢复。 """ from typing import Annotated from fastapi import APIRouter, Path, Query from app.api.deps import AdminUser, DbSession from app.core.response import ApiResponse, PageResult, ok from app.schemas.user import ( PasswordResetIn, RoleOut, UserCreateIn, UserOut, UserQuery, UserStatusIn, UserUpdateIn, ) from app.services import user_service router = APIRouter(prefix="/users", tags=["系统管理 · 用户"]) @router.get("", response_model=ApiResponse[PageResult[UserOut]], summary="用户列表") def list_users( session: DbSession, _: AdminUser, query: Annotated[UserQuery, Query()], ) -> ApiResponse[PageResult[UserOut]]: """分页查询用户,支持按用户名/姓名模糊、状态与角色筛选。""" return ok(user_service.list_users(session, query)) @router.get("/{user_id}", response_model=ApiResponse[UserOut], summary="用户详情") def get_user( session: DbSession, _: AdminUser, user_id: Annotated[int, Path(ge=1)], ) -> ApiResponse[UserOut]: """查询用户详情。""" return ok(user_service.get_user(session, user_id)) @router.post("", response_model=ApiResponse[UserOut], summary="新增用户") def create_user( session: DbSession, _: AdminUser, payload: UserCreateIn, ) -> ApiResponse[UserOut]: """新增用户。用户名须唯一,至少指定一个角色。""" return ok(user_service.create_user(session, payload), message="用户已创建") @router.put("/{user_id}", response_model=ApiResponse[UserOut], summary="编辑用户") def update_user( session: DbSession, current_user: AdminUser, user_id: Annotated[int, Path(ge=1)], payload: UserUpdateIn, ) -> ApiResponse[UserOut]: """编辑用户的基本信息与角色。 **不含用户名与密码**:用户名是审计追溯的锚点;密码走专门的重置接口。 """ return ok( user_service.update_user(session, user_id, payload, operator_id=current_user.id), message="已保存", ) @router.delete("/{user_id}", response_model=ApiResponse[None], summary="删除用户") def delete_user( session: DbSession, current_user: AdminUser, user_id: Annotated[int, Path(ge=1)], ) -> ApiResponse[None]: """删除用户。 物理删除。历史操作日志通过 `user_id` 置空 + 冗余用户名保留可追溯性。 业务上更推荐用「禁用」代替删除 —— 禁用保留完整审计线索。 """ user_service.delete_user(session, user_id, operator_id=current_user.id) return ok(message="用户已删除") @router.put("/{user_id}/password", response_model=ApiResponse[None], summary="重置密码") def reset_password( session: DbSession, _: AdminUser, user_id: Annotated[int, Path(ge=1)], payload: PasswordResetIn, ) -> ApiResponse[None]: """重置用户密码。 会同时提升该用户的令牌版本,使其**已签发的令牌立即失效** —— 否则改密后旧令牌在有效期内仍可用。 """ user_service.reset_password(session, user_id, payload.new_password) return ok(message="密码已重置,该用户需重新登录") @router.put("/{user_id}/status", response_model=ApiResponse[UserOut], summary="启用/禁用用户") def set_status( session: DbSession, current_user: AdminUser, user_id: Annotated[int, Path(ge=1)], payload: UserStatusIn, ) -> ApiResponse[UserOut]: """启用或禁用用户。 禁用会提升令牌版本使已签发令牌立即失效,否则被禁用者仍可凭旧令牌 继续访问直到过期(最长 2 小时)。 """ result = user_service.set_status(session, user_id, payload.status, operator_id=current_user.id) return ok(result, message="已启用" if payload.status == 1 else "已禁用") @router.get("/meta/roles", response_model=ApiResponse[list[RoleOut]], summary="角色下拉选项") def list_roles(session: DbSession, _: AdminUser) -> ApiResponse[list[RoleOut]]: """取全部启用角色,供用户表单的角色选择使用。 路径放在 `/users/meta/roles` 而非 `/roles`,是为了避免与 `/{user_id}` 的路由匹配冲突(`roles` 会被当成 user_id 解析而报 422)。 """ return ok(user_service.list_roles(session))