modify: 权限认证完善

This commit is contained in:
2026-04-30 14:38:43 +08:00
parent 70a1ebdf4f
commit 072a59a169
25 changed files with 3317 additions and 361 deletions
+3 -2
View File
@@ -1,7 +1,8 @@
FROM debian:bookworm-slim AS builder # FROM debian:bookworm-slim AS builder
FROM ubuntu:24.04 AS builder
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates && \ apt-get install -y --no-install-recommends ca-certificates tzdata && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
COPY build/chongming /tmp/chongming COPY build/chongming /tmp/chongming
File diff suppressed because one or more lines are too long
+643 -4
View File
@@ -2,7 +2,7 @@
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client'; import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
import { client } from './client.gen'; import { client } from './client.gen';
import type { CreateTodoApiV1TodosPostData, CreateTodoApiV1TodosPostErrors, CreateTodoApiV1TodosPostResponses, DeleteTodoApiV1TodosTodoIdDeleteData, DeleteTodoApiV1TodosTodoIdDeleteErrors, DeleteTodoApiV1TodosTodoIdDeleteResponses, GetCurrentUserInfoApiV1AuthMeGetData, GetCurrentUserInfoApiV1AuthMeGetErrors, GetCurrentUserInfoApiV1AuthMeGetResponses, GetSessionsApiV1AuthSessionsGetData, GetSessionsApiV1AuthSessionsGetResponses, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponses, LogoutAllApiV1AuthLogoutAllPostData, LogoutAllApiV1AuthLogoutAllPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponses, ReadTodoApiV1TodosTodoIdGetData, ReadTodoApiV1TodosTodoIdGetErrors, ReadTodoApiV1TodosTodoIdGetResponses, ReadTodosApiV1TodosGetData, ReadTodosApiV1TodosGetErrors, ReadTodosApiV1TodosGetResponses, RefreshTokenApiV1AuthRefreshPostData, RefreshTokenApiV1AuthRefreshPostErrors, RefreshTokenApiV1AuthRefreshPostResponses, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponses, RootGetData, RootGetResponses, UpdateTodoApiV1TodosTodoIdPatchData, UpdateTodoApiV1TodosTodoIdPatchErrors, UpdateTodoApiV1TodosTodoIdPatchResponses, UploadImageApiV1UploadImagePostData, UploadImageApiV1UploadImagePostErrors, UploadImageApiV1UploadImagePostResponses } from './types.gen'; import type { AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostData, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostResponses, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostData, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostResponses, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostData, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostResponses, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostData, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostResponses, CreatePermissionApiV1PermissionsPostData, CreatePermissionApiV1PermissionsPostErrors, CreatePermissionApiV1PermissionsPostResponses, CreateRoleApiV1RolesPostData, CreateRoleApiV1RolesPostErrors, CreateRoleApiV1RolesPostResponses, CreateTodoApiV1TodosPostData, CreateTodoApiV1TodosPostErrors, CreateTodoApiV1TodosPostResponses, DeletePermissionApiV1PermissionsPermissionIdDeleteData, DeletePermissionApiV1PermissionsPermissionIdDeleteErrors, DeletePermissionApiV1PermissionsPermissionIdDeleteResponses, DeleteRoleApiV1RolesRoleIdDeleteData, DeleteRoleApiV1RolesRoleIdDeleteErrors, DeleteRoleApiV1RolesRoleIdDeleteResponses, DeleteTodoApiV1TodosTodoIdDeleteData, DeleteTodoApiV1TodosTodoIdDeleteErrors, DeleteTodoApiV1TodosTodoIdDeleteResponses, GetCurrentUserInfoApiV1AuthMeGetData, GetCurrentUserInfoApiV1AuthMeGetErrors, GetCurrentUserInfoApiV1AuthMeGetResponses, GetPermissionApiV1PermissionsPermissionIdGetData, GetPermissionApiV1PermissionsPermissionIdGetErrors, GetPermissionApiV1PermissionsPermissionIdGetResponses, GetRoleApiV1RolesRoleIdGetData, GetRoleApiV1RolesRoleIdGetErrors, GetRoleApiV1RolesRoleIdGetResponses, GetRolePermissionsApiV1RolesRoleIdPermissionsGetData, GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors, GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses, GetSessionsApiV1AuthSessionsGetData, GetSessionsApiV1AuthSessionsGetResponses, ListPermissionsApiV1PermissionsGetData, ListPermissionsApiV1PermissionsGetErrors, ListPermissionsApiV1PermissionsGetResponses, ListRolesApiV1RolesGetData, ListRolesApiV1RolesGetErrors, ListRolesApiV1RolesGetResponses, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponses, LogoutAllApiV1AuthLogoutAllPostData, LogoutAllApiV1AuthLogoutAllPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponses, ReadTodoApiV1TodosTodoIdGetData, ReadTodoApiV1TodosTodoIdGetErrors, ReadTodoApiV1TodosTodoIdGetResponses, ReadTodosApiV1TodosGetData, ReadTodosApiV1TodosGetErrors, ReadTodosApiV1TodosGetResponses, RefreshTokenApiV1AuthRefreshPostData, RefreshTokenApiV1AuthRefreshPostErrors, RefreshTokenApiV1AuthRefreshPostResponses, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponses, RootGetData, RootGetResponses, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostData, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostResponses, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostData, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostResponses, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostData, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostResponses, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostData, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostResponses, UpdatePermissionApiV1PermissionsPermissionIdPutData, UpdatePermissionApiV1PermissionsPermissionIdPutErrors, UpdatePermissionApiV1PermissionsPermissionIdPutResponses, UpdateRoleApiV1RolesRoleIdPutData, UpdateRoleApiV1RolesRoleIdPutErrors, UpdateRoleApiV1RolesRoleIdPutResponses, UpdateTodoApiV1TodosTodoIdPatchData, UpdateTodoApiV1TodosTodoIdPatchErrors, UpdateTodoApiV1TodosTodoIdPatchResponses, UploadImageApiV1UploadImagePostData, UploadImageApiV1UploadImagePostErrors, UploadImageApiV1UploadImagePostResponses } from './types.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & { export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
/** /**
@@ -20,6 +20,16 @@ export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends
/** /**
* Read Todos * Read Todos
*
* 获取待办事项列表
*
* Args:
* offset: 分页偏移量,默认为0,用于指定从第几条记录开始查询
* limit: 每页返回的最大记录数,默认为100,用于限制返回结果的数量
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* List[TodoRead]: 待办事项列表,包含指定范围内的所有待办事项
*/ */
export const readTodosApiV1TodosGet = <ThrowOnError extends boolean = false>(options?: Options<ReadTodosApiV1TodosGetData, ThrowOnError>) => (options?.client ?? client).get<ReadTodosApiV1TodosGetResponses, ReadTodosApiV1TodosGetErrors, ThrowOnError>({ export const readTodosApiV1TodosGet = <ThrowOnError extends boolean = false>(options?: Options<ReadTodosApiV1TodosGetData, ThrowOnError>) => (options?.client ?? client).get<ReadTodosApiV1TodosGetResponses, ReadTodosApiV1TodosGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -29,6 +39,15 @@ export const readTodosApiV1TodosGet = <ThrowOnError extends boolean = false>(opt
/** /**
* Create Todo * Create Todo
*
* 创建新的待办事项
*
* Args:
* todo: 待办事项创建请求体,包含待办事项的详细信息
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* TodoRead: 创建成功的待办事项对象,包含完整的待办事项信息
*/ */
export const createTodoApiV1TodosPost = <ThrowOnError extends boolean = false>(options: Options<CreateTodoApiV1TodosPostData, ThrowOnError>) => (options.client ?? client).post<CreateTodoApiV1TodosPostResponses, CreateTodoApiV1TodosPostErrors, ThrowOnError>({ export const createTodoApiV1TodosPost = <ThrowOnError extends boolean = false>(options: Options<CreateTodoApiV1TodosPostData, ThrowOnError>) => (options.client ?? client).post<CreateTodoApiV1TodosPostResponses, CreateTodoApiV1TodosPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -42,6 +61,18 @@ export const createTodoApiV1TodosPost = <ThrowOnError extends boolean = false>(o
/** /**
* Delete Todo * Delete Todo
*
* 删除待办事项
*
* Args:
* todo_id: 待办事项的唯一标识符
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* dict: 包含操作结果的字典,{"ok": True} 表示删除成功
*
* Raises:
* HTTPException: 当待办事项不存在时抛出404错误
*/ */
export const deleteTodoApiV1TodosTodoIdDelete = <ThrowOnError extends boolean = false>(options: Options<DeleteTodoApiV1TodosTodoIdDeleteData, ThrowOnError>) => (options.client ?? client).delete<DeleteTodoApiV1TodosTodoIdDeleteResponses, DeleteTodoApiV1TodosTodoIdDeleteErrors, ThrowOnError>({ export const deleteTodoApiV1TodosTodoIdDelete = <ThrowOnError extends boolean = false>(options: Options<DeleteTodoApiV1TodosTodoIdDeleteData, ThrowOnError>) => (options.client ?? client).delete<DeleteTodoApiV1TodosTodoIdDeleteResponses, DeleteTodoApiV1TodosTodoIdDeleteErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -51,6 +82,18 @@ export const deleteTodoApiV1TodosTodoIdDelete = <ThrowOnError extends boolean =
/** /**
* Read Todo * Read Todo
*
* 根据ID获取单个待办事项
*
* Args:
* todo_id: 待办事项的唯一标识符
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* TodoRead: 查询到的待办事项对象
*
* Raises:
* HTTPException: 当待办事项不存在时抛出404错误
*/ */
export const readTodoApiV1TodosTodoIdGet = <ThrowOnError extends boolean = false>(options: Options<ReadTodoApiV1TodosTodoIdGetData, ThrowOnError>) => (options.client ?? client).get<ReadTodoApiV1TodosTodoIdGetResponses, ReadTodoApiV1TodosTodoIdGetErrors, ThrowOnError>({ export const readTodoApiV1TodosTodoIdGet = <ThrowOnError extends boolean = false>(options: Options<ReadTodoApiV1TodosTodoIdGetData, ThrowOnError>) => (options.client ?? client).get<ReadTodoApiV1TodosTodoIdGetResponses, ReadTodoApiV1TodosTodoIdGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -60,6 +103,16 @@ export const readTodoApiV1TodosTodoIdGet = <ThrowOnError extends boolean = false
/** /**
* Update Todo * Update Todo
*
* 更新待办事项信息
*
* Args:
* todo_id: 待办事项的唯一标识符
* todo_update: 待办事项更新请求体,包含需要更新的字段信息
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* TodoRead: 更新后的待办事项对象,包含最新的待办事项信息
*/ */
export const updateTodoApiV1TodosTodoIdPatch = <ThrowOnError extends boolean = false>(options: Options<UpdateTodoApiV1TodosTodoIdPatchData, ThrowOnError>) => (options.client ?? client).patch<UpdateTodoApiV1TodosTodoIdPatchResponses, UpdateTodoApiV1TodosTodoIdPatchErrors, ThrowOnError>({ export const updateTodoApiV1TodosTodoIdPatch = <ThrowOnError extends boolean = false>(options: Options<UpdateTodoApiV1TodosTodoIdPatchData, ThrowOnError>) => (options.client ?? client).patch<UpdateTodoApiV1TodosTodoIdPatchResponses, UpdateTodoApiV1TodosTodoIdPatchErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -73,6 +126,20 @@ export const updateTodoApiV1TodosTodoIdPatch = <ThrowOnError extends boolean = f
/** /**
* Register * Register
*
* 用户注册接口
*
* 创建新用户账户,检查邮箱是否已被注册,如果邮箱未被注册则创建新用户。
*
* Args:
* user_data: 用户注册数据,包含邮箱、密码、用户名和全名等信息
* session: 数据库会话对象,用于执行数据库操作
*
* Returns:
* UserRead: 创建成功的用户信息,包含用户ID、邮箱、用户名等字段
*
* Raises:
* HTTPException: 当邮箱已被注册时抛出400错误
*/ */
export const registerApiV1AuthRegisterPost = <ThrowOnError extends boolean = false>(options: Options<RegisterApiV1AuthRegisterPostData, ThrowOnError>) => (options.client ?? client).post<RegisterApiV1AuthRegisterPostResponses, RegisterApiV1AuthRegisterPostErrors, ThrowOnError>({ export const registerApiV1AuthRegisterPost = <ThrowOnError extends boolean = false>(options: Options<RegisterApiV1AuthRegisterPostData, ThrowOnError>) => (options.client ?? client).post<RegisterApiV1AuthRegisterPostResponses, RegisterApiV1AuthRegisterPostErrors, ThrowOnError>({
url: '/api/v1/auth/register', url: '/api/v1/auth/register',
@@ -85,6 +152,24 @@ export const registerApiV1AuthRegisterPost = <ThrowOnError extends boolean = fal
/** /**
* Login * Login
*
* 用户登录接口
*
* 验证用户凭据并生成访问令牌和刷新令牌。依次检查邮箱是否存在、密码是否正确、用户是否被禁用。
*
* Args:
* user_data: 用户登录数据,包含邮箱和密码
* request: FastAPI请求对象,用于获取客户端信息
* session: 数据库会话对象,用于查询用户信息
*
* Returns:
* TokenResponse: 包含访问令牌(access_token)、刷新令牌(refresh_token)和令牌类型(token_type)的响应对象
*
* Raises:
* HTTPException:
* - 400错误:当邮箱不存在时
* - 401错误:当密码错误时
* - 402错误:当用户账户被禁用时
*/ */
export const loginApiV1AuthLoginPost = <ThrowOnError extends boolean = false>(options: Options<LoginApiV1AuthLoginPostData, ThrowOnError>) => (options.client ?? client).post<LoginApiV1AuthLoginPostResponses, LoginApiV1AuthLoginPostErrors, ThrowOnError>({ export const loginApiV1AuthLoginPost = <ThrowOnError extends boolean = false>(options: Options<LoginApiV1AuthLoginPostData, ThrowOnError>) => (options.client ?? client).post<LoginApiV1AuthLoginPostResponses, LoginApiV1AuthLoginPostErrors, ThrowOnError>({
url: '/api/v1/auth/login', url: '/api/v1/auth/login',
@@ -97,6 +182,21 @@ export const loginApiV1AuthLoginPost = <ThrowOnError extends boolean = false>(op
/** /**
* 刷新访问令牌 * 刷新访问令牌
*
* 刷新访问令牌接口
*
* 使用有效的刷新令牌获取新的访问令牌。验证刷新令牌的有效性,如果有效则生成新的访问令牌。
*
* Args:
* request: FastAPI请求对象,用于获取客户端信息
* credentials: HTTP认证凭证,从请求头中提取的刷新令牌
* session: 数据库会话对象,用于验证令牌状态
*
* Returns:
* RefreshTokenResponse: 包含新的访问令牌(access_token)和令牌类型(token_type)的响应对象
*
* Raises:
* HTTPException: 当刷新令牌无效或已过期时抛出401错误
*/ */
export const refreshTokenApiV1AuthRefreshPost = <ThrowOnError extends boolean = false>(options?: Options<RefreshTokenApiV1AuthRefreshPostData, ThrowOnError>) => (options?.client ?? client).post<RefreshTokenApiV1AuthRefreshPostResponses, RefreshTokenApiV1AuthRefreshPostErrors, ThrowOnError>({ export const refreshTokenApiV1AuthRefreshPost = <ThrowOnError extends boolean = false>(options?: Options<RefreshTokenApiV1AuthRefreshPostData, ThrowOnError>) => (options?.client ?? client).post<RefreshTokenApiV1AuthRefreshPostResponses, RefreshTokenApiV1AuthRefreshPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -106,6 +206,16 @@ export const refreshTokenApiV1AuthRefreshPost = <ThrowOnError extends boolean =
/** /**
* Logout * Logout
*
* 用户登出接口
*
* 使当前的访问令牌失效,实现单设备登出功能。
*
* Args:
* credentials: HTTP认证凭证,从请求头中提取的访问令牌
*
* Returns:
* Userlogout: 登出成功响应,包含成功消息和状态码200
*/ */
export const logoutApiV1AuthLogoutPost = <ThrowOnError extends boolean = false>(options?: Options<LogoutApiV1AuthLogoutPostData, ThrowOnError>) => (options?.client ?? client).post<LogoutApiV1AuthLogoutPostResponses, unknown, ThrowOnError>({ export const logoutApiV1AuthLogoutPost = <ThrowOnError extends boolean = false>(options?: Options<LogoutApiV1AuthLogoutPostData, ThrowOnError>) => (options?.client ?? client).post<LogoutApiV1AuthLogoutPostResponses, unknown, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -115,6 +225,16 @@ export const logoutApiV1AuthLogoutPost = <ThrowOnError extends boolean = false>(
/** /**
* 登出所有设备 * 登出所有设备
*
* 登出所有设备接口
*
* 使指定用户的所有活跃会话令牌失效,实现多设备同时登出功能。
*
* Args:
* current_user: 当前登录用户的令牌数据,包含用户ID等信息
*
* Returns:
* Userlogout: 登出成功响应,包含成功消息和状态码200
*/ */
export const logoutAllApiV1AuthLogoutAllPost = <ThrowOnError extends boolean = false>(options?: Options<LogoutAllApiV1AuthLogoutAllPostData, ThrowOnError>) => (options?.client ?? client).post<LogoutAllApiV1AuthLogoutAllPostResponses, unknown, ThrowOnError>({ export const logoutAllApiV1AuthLogoutAllPost = <ThrowOnError extends boolean = false>(options?: Options<LogoutAllApiV1AuthLogoutAllPostData, ThrowOnError>) => (options?.client ?? client).post<LogoutAllApiV1AuthLogoutAllPostResponses, unknown, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -125,7 +245,15 @@ export const logoutAllApiV1AuthLogoutAllPost = <ThrowOnError extends boolean = f
/** /**
* 获取用户会话 * 获取用户会话
* *
* 获取用户的所有活跃会话 * 获取用户会话列表接口
*
* 查询并返回指定用户的所有活跃会话信息,包括每个会话的设备信息和创建时间等。
*
* Args:
* current_user: 当前登录用户的令牌数据,包含用户ID等信息
*
* Returns:
* UserSession: 用户会话信息对象,包含用户ID、会话列表和会话总数
*/ */
export const getSessionsApiV1AuthSessionsGet = <ThrowOnError extends boolean = false>(options?: Options<GetSessionsApiV1AuthSessionsGetData, ThrowOnError>) => (options?.client ?? client).get<GetSessionsApiV1AuthSessionsGetResponses, unknown, ThrowOnError>({ export const getSessionsApiV1AuthSessionsGet = <ThrowOnError extends boolean = false>(options?: Options<GetSessionsApiV1AuthSessionsGetData, ThrowOnError>) => (options?.client ?? client).get<GetSessionsApiV1AuthSessionsGetResponses, unknown, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -136,7 +264,19 @@ export const getSessionsApiV1AuthSessionsGet = <ThrowOnError extends boolean = f
/** /**
* 获取当前用户信息 * 获取当前用户信息
* *
* 获取当前登录用户的信息 * 获取当前用户信息接口
*
* 根据当前登录用户的令牌信息,从数据库中查询并返回完整的用户资料。
*
* Args:
* session: 数据库会话对象,用于查询用户信息
* current_user: 当前登录用户的令牌数据,包含用户ID等信息
*
* Returns:
* UserRead: 用户详细信息,包含用户ID、邮箱、用户名、全名、激活状态等字段
*
* Raises:
* HTTPException: 当用户不存在时抛出404错误
*/ */
export const getCurrentUserInfoApiV1AuthMeGet = <ThrowOnError extends boolean = false>(options?: Options<GetCurrentUserInfoApiV1AuthMeGetData, ThrowOnError>) => (options?.client ?? client).get<GetCurrentUserInfoApiV1AuthMeGetResponses, GetCurrentUserInfoApiV1AuthMeGetErrors, ThrowOnError>({ export const getCurrentUserInfoApiV1AuthMeGet = <ThrowOnError extends boolean = false>(options?: Options<GetCurrentUserInfoApiV1AuthMeGetData, ThrowOnError>) => (options?.client ?? client).get<GetCurrentUserInfoApiV1AuthMeGetResponses, GetCurrentUserInfoApiV1AuthMeGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }], security: [{ scheme: 'bearer', type: 'http' }],
@@ -146,6 +286,30 @@ export const getCurrentUserInfoApiV1AuthMeGet = <ThrowOnError extends boolean =
/** /**
* Upload Image * Upload Image
*
* 上传图片文件接口
*
* 该接口用于接收用户上传的图片文件,进行格式验证后保存到服务器,
* 并返回文件的访问URL、文件名和文件类型信息。
*
* Args:
* file (UploadFile): 上传的文件对象,必须为图片格式
*
* Returns:
* UploadResponse: 包含以下字段的响应对象:
* - url (str): 上传文件的访问URL
* - filename (str): 保存后的文件名
* - file_type (str): 文件扩展名(如 .jpg, .png 等)
*
* Raises:
* HTTPException:
* - 400: 当文件类型不是图片格式时
* - 400: 当文件名为空时
* - 400: 当文件扩展名不在允许列表中时
*
* Note:
* 支持的图片格式包括:.jpg, .jpeg, .png, .gif, .webp
* 需要用户认证才能访问此接口
*/ */
export const uploadImageApiV1UploadImagePost = <ThrowOnError extends boolean = false>(options: Options<UploadImageApiV1UploadImagePostData, ThrowOnError>) => (options.client ?? client).post<UploadImageApiV1UploadImagePostResponses, UploadImageApiV1UploadImagePostErrors, ThrowOnError>({ export const uploadImageApiV1UploadImagePost = <ThrowOnError extends boolean = false>(options: Options<UploadImageApiV1UploadImagePostData, ThrowOnError>) => (options.client ?? client).post<UploadImageApiV1UploadImagePostResponses, UploadImageApiV1UploadImagePostErrors, ThrowOnError>({
...formDataBodySerializer, ...formDataBodySerializer,
@@ -158,9 +322,484 @@ export const uploadImageApiV1UploadImagePost = <ThrowOnError extends boolean = f
} }
}); });
/**
* 获取权限列表
*
* 获取权限列表
*
* 返回系统中的所有权限项,支持分页和按资源名称筛选。
*
* Args:
* skip: 跳过的记录数,用于分页
* limit: 返回的最大记录数,用于分页
* resource: 资源名称筛选条件,可选
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* list[Permission]: 权限对象列表
*/
export const listPermissionsApiV1PermissionsGet = <ThrowOnError extends boolean = false>(options?: Options<ListPermissionsApiV1PermissionsGetData, ThrowOnError>) => (options?.client ?? client).get<ListPermissionsApiV1PermissionsGetResponses, ListPermissionsApiV1PermissionsGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions',
...options
});
/**
* 创建权限
*
* 创建权限
*
* 创建新的权限项,权限名称格式应为"资源:操作",如"user:create"。
*
* Args:
* permission_data: 权限创建数据,包含名称、资源、操作和描述等信息
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Permission: 创建成功的权限对象
*
* Raises:
* HTTPException: 当权限名称已存在时抛出400错误
*/
export const createPermissionApiV1PermissionsPost = <ThrowOnError extends boolean = false>(options: Options<CreatePermissionApiV1PermissionsPostData, ThrowOnError>) => (options.client ?? client).post<CreatePermissionApiV1PermissionsPostResponses, CreatePermissionApiV1PermissionsPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* 删除权限
*
* 删除权限
*
* 删除指定权限。如果权限已被分配给角色,则无法删除。
*
* Args:
* permission_id: 权限ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 删除成功响应消息
*
* Raises:
* HTTPException: 当权限被角色使用时抛出400错误,权限不存在时抛出404错误
*/
export const deletePermissionApiV1PermissionsPermissionIdDelete = <ThrowOnError extends boolean = false>(options: Options<DeletePermissionApiV1PermissionsPermissionIdDeleteData, ThrowOnError>) => (options.client ?? client).delete<DeletePermissionApiV1PermissionsPermissionIdDeleteResponses, DeletePermissionApiV1PermissionsPermissionIdDeleteErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}',
...options
});
/**
* 获取权限详情
*
* 获取权限详情
*
* 根据权限ID获取指定权限的详细信息。
*
* Args:
* permission_id: 权限ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Permission: 权限对象
*
* Raises:
* HTTPException: 当权限不存在时抛出404错误
*/
export const getPermissionApiV1PermissionsPermissionIdGet = <ThrowOnError extends boolean = false>(options: Options<GetPermissionApiV1PermissionsPermissionIdGetData, ThrowOnError>) => (options.client ?? client).get<GetPermissionApiV1PermissionsPermissionIdGetResponses, GetPermissionApiV1PermissionsPermissionIdGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}',
...options
});
/**
* 更新权限
*
* 更新权限信息
*
* 更新指定权限的名称、描述、资源或操作等信息。
*
* Args:
* permission_id: 权限ID
* permission_update: 权限更新数据,所有字段可选
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Permission: 更新后的权限对象
*
* Raises:
* HTTPException: 当权限不存在时抛出404错误
*/
export const updatePermissionApiV1PermissionsPermissionIdPut = <ThrowOnError extends boolean = false>(options: Options<UpdatePermissionApiV1PermissionsPermissionIdPutData, ThrowOnError>) => (options.client ?? client).put<UpdatePermissionApiV1PermissionsPermissionIdPutResponses, UpdatePermissionApiV1PermissionsPermissionIdPutErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* 将权限分配给角色
*
* 将权限分配给角色
*
* 将指定权限分配给指定角色。
*
* Args:
* permission_id: 权限ID
* role_id: 角色ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 分配成功响应消息
*
* Raises:
* HTTPException: 当权限已分配给角色或角色/权限不存在时抛出错误
*/
export const assignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPost = <ThrowOnError extends boolean = false>(options: Options<AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostData, ThrowOnError>) => (options.client ?? client).post<AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostResponses, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}/assign-to-role/{role_id}',
...options
});
/**
* 从角色解除权限绑定
*
* 从角色解除权限绑定
*
* 将指定权限从指定角色中移除。
*
* Args:
* permission_id: 权限ID
* role_id: 角色ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 解绑成功响应消息
*
* Raises:
* HTTPException: 当角色不存在时抛出404错误
*/
export const unbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPost = <ThrowOnError extends boolean = false>(options: Options<UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostData, ThrowOnError>) => (options.client ?? client).post<UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostResponses, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}/unbind-from-role/{role_id}',
...options
});
/**
* 将权限分配给用户
*
* 将权限分配给用户
*
* 将指定权限分配给指定用户。如果用户没有专属角色,则会自动创建一个专属角色。
*
* Args:
* permission_id: 权限ID
* user_id: 用户UUID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 分配成功响应消息
*
* Raises:
* HTTPException: 当用户或权限不存在时抛出404错误
*/
export const assignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPost = <ThrowOnError extends boolean = false>(options: Options<AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostData, ThrowOnError>) => (options.client ?? client).post<AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostResponses, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}/assign-to-user/{user_id}',
...options
});
/**
* 从用户解除权限绑定
*
* 从用户解除权限绑定
*
* 将指定权限从指定用户中移除。
*
* Args:
* permission_id: 权限ID
* user_id: 用户UUID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 解绑成功响应消息
*
* Raises:
* HTTPException: 当用户或角色不存在时抛出404错误
*/
export const unbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPost = <ThrowOnError extends boolean = false>(options: Options<UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostData, ThrowOnError>) => (options.client ?? client).post<UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostResponses, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/permissions/{permission_id}/unbind-from-user/{user_id}',
...options
});
/**
* 获取角色列表
*
* 获取角色列表
*
* 返回系统中的所有角色,支持分页。
*
* Args:
* skip: 跳过的记录数,用于分页
* limit: 返回的最大记录数,用于分页
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* list[Role]: 角色对象列表
*/
export const listRolesApiV1RolesGet = <ThrowOnError extends boolean = false>(options?: Options<ListRolesApiV1RolesGetData, ThrowOnError>) => (options?.client ?? client).get<ListRolesApiV1RolesGetResponses, ListRolesApiV1RolesGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles',
...options
});
/**
* 创建角色
*
* 创建角色
*
* 创建新的角色,如 admin、manager、operator 等。
*
* Args:
* name: 角色名称
* description: 角色描述,可选
* is_only_user: 是否仅用于单个用户,默认为False
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Role: 创建成功的角色对象
*
* Raises:
* HTTPException: 当角色名称已存在时抛出400错误
*/
export const createRoleApiV1RolesPost = <ThrowOnError extends boolean = false>(options: Options<CreateRoleApiV1RolesPostData, ThrowOnError>) => (options.client ?? client).post<CreateRoleApiV1RolesPostResponses, CreateRoleApiV1RolesPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles',
...options
});
/**
* 删除角色
*
* 删除角色
*
* 删除指定角色。如果角色已被分配给用户,则无法删除。
*
* Args:
* role_id: 角色ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 删除成功响应消息
*
* Raises:
* HTTPException: 当角色被用户使用时抛出400错误,角色不存在时抛出404错误
*/
export const deleteRoleApiV1RolesRoleIdDelete = <ThrowOnError extends boolean = false>(options: Options<DeleteRoleApiV1RolesRoleIdDeleteData, ThrowOnError>) => (options.client ?? client).delete<DeleteRoleApiV1RolesRoleIdDeleteResponses, DeleteRoleApiV1RolesRoleIdDeleteErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}',
...options
});
/**
* 获取角色详情
*
* 获取角色详情
*
* 根据角色ID获取指定角色的详细信息。
*
* Args:
* role_id: 角色ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Role: 角色对象
*
* Raises:
* HTTPException: 当角色不存在时抛出404错误
*/
export const getRoleApiV1RolesRoleIdGet = <ThrowOnError extends boolean = false>(options: Options<GetRoleApiV1RolesRoleIdGetData, ThrowOnError>) => (options.client ?? client).get<GetRoleApiV1RolesRoleIdGetResponses, GetRoleApiV1RolesRoleIdGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}',
...options
});
/**
* 更新角色
*
* 更新角色信息
*
* 更新指定角色的名称或描述等信息。
*
* Args:
* role_id: 角色ID
* role_update: 角色更新数据,所有字段可选
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* Role: 更新后的角色对象
*
* Raises:
* HTTPException: 当角色不存在时抛出404错误
*/
export const updateRoleApiV1RolesRoleIdPut = <ThrowOnError extends boolean = false>(options: Options<UpdateRoleApiV1RolesRoleIdPutData, ThrowOnError>) => (options.client ?? client).put<UpdateRoleApiV1RolesRoleIdPutResponses, UpdateRoleApiV1RolesRoleIdPutErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}',
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
});
/**
* 将角色分配给用户
*
* 将角色分配给用户
*
* 将指定角色分配给指定用户。
*
* Args:
* role_id: 角色ID
* user_id: 用户UUID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 分配成功响应消息
*
* Raises:
* HTTPException: 当角色或用户不存在时抛出404错误
*/
export const assignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPost = <ThrowOnError extends boolean = false>(options: Options<AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostData, ThrowOnError>) => (options.client ?? client).post<AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostResponses, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}/assign-to-user/{user_id}',
...options
});
/**
* 从用户解除角色绑定
*
* 从用户解除角色绑定
*
* 将指定角色从指定用户中移除。
*
* Args:
* role_id: 角色ID
* user_id: 用户UUID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 解绑成功响应消息
*
* Raises:
* HTTPException: 当用户不存在时抛出404错误
*/
export const unbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPost = <ThrowOnError extends boolean = false>(options: Options<UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostData, ThrowOnError>) => (options.client ?? client).post<UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostResponses, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}/unbind-from-user/{user_id}',
...options
});
/**
* 获取角色的权限列表
*
* 获取指定角色的所有权限
*
* Args:
* role_id: 角色ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* list: 权限对象列表
*/
export const getRolePermissionsApiV1RolesRoleIdPermissionsGet = <ThrowOnError extends boolean = false>(options: Options<GetRolePermissionsApiV1RolesRoleIdPermissionsGetData, ThrowOnError>) => (options.client ?? client).get<GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses, GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}/permissions',
...options
});
/**
* 为角色分配权限
*
* 为角色分配权限
*
* 将指定权限分配给指定角色。
*
* Args:
* role_id: 角色ID
* permission_id: 权限ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 分配成功响应消息
*
* Raises:
* HTTPException: 当权限已分配给角色或角色/权限不存在时抛出错误
*/
export const assignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPost = <ThrowOnError extends boolean = false>(options: Options<AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostData, ThrowOnError>) => (options.client ?? client).post<AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostResponses, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}/assign-permission/{permission_id}',
...options
});
/**
* 从角色解除权限绑定
*
* 从角色解除权限绑定
*
* 将指定权限从指定角色中移除。
*
* Args:
* role_id: 角色ID
* permission_id: 权限ID
* session: 数据库会话对象
* current_user: 当前登录用户
*
* Returns:
* dict: 解绑成功响应消息
*
* Raises:
* HTTPException: 当角色不存在时抛出404错误
*/
export const unbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPost = <ThrowOnError extends boolean = false>(options: Options<UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostData, ThrowOnError>) => (options.client ?? client).post<UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostResponses, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors, ThrowOnError>({
security: [{ scheme: 'bearer', type: 'http' }],
url: '/api/v1/roles/{role_id}/unbind-permission/{permission_id}',
...options
});
/** /**
* 根路径 * 根路径
* *
* 根路径,返回应用信息 * 根路径端点,返回应用的基本信息。
*
* 返回:
* dict: 包含应用名称、版本、文档地址和健康检查地址的字典
*/ */
export const rootGet = <ThrowOnError extends boolean = false>(options?: Options<RootGetData, ThrowOnError>) => (options?.client ?? client).get<RootGetResponses, unknown, ThrowOnError>({ url: '/', ...options }); export const rootGet = <ThrowOnError extends boolean = false>(options?: Options<RootGetData, ThrowOnError>) => (options?.client ?? client).get<RootGetResponses, unknown, ThrowOnError>({ url: '/', ...options });
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -4,7 +4,8 @@ API 路由
from fastapi import APIRouter from fastapi import APIRouter
from .routers import todo, auth, upload from .routers import todo, auth, upload, permission, role
# 创建主路由 # 创建主路由
api_router = APIRouter() api_router = APIRouter()
@@ -13,6 +14,8 @@ api_router = APIRouter()
api_router.include_router(todo.router, prefix="/todos", tags=["todo"]) api_router.include_router(todo.router, prefix="/todos", tags=["todo"])
api_router.include_router(auth.router, prefix="/auth", tags=["authentication"]) api_router.include_router(auth.router, prefix="/auth", tags=["authentication"])
api_router.include_router(upload.router, prefix="/upload", tags=["upload"]) api_router.include_router(upload.router, prefix="/upload", tags=["upload"])
api_router.include_router(permission.router, prefix="/permissions", tags=["permission"])
api_router.include_router(role.router, prefix="/roles", tags=["role"])
# 导出路由 # 导出路由
+181
View File
@@ -1,7 +1,14 @@
from typing import List, Set, Union
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.ext.asyncio import AsyncSession
from ..service.auth import get_auth_service from ..service.auth import get_auth_service
from ..service.permission import PermissionService
from ..service.user import UserService
from ..core.database import get_session
from plugins.jwt.jwt_cache import TokenData from plugins.jwt.jwt_cache import TokenData
@@ -40,3 +47,177 @@ async def get_current_user(
) )
return token_data return token_data
class PermissionChecker:
"""
权限校验依赖类
用于校验当前用户是否拥有访问特定接口所需的权限。
支持以下特性:
- 超级用户自动拥有所有权限(需要额外注入 RequireSuperuser 或由业务层检查)
- 支持单权限校验
- 支持多权限校验(all模式:需要拥有全部权限;any模式:拥有任一权限即可)
使用示例:
```python
# 单权限校验
@router.get("/orders")
async def list_orders(
_: bool = Depends(PermissionChecker("sales_order:read")),
):
...
# 多权限 all 模式(需要同时拥有创建和审核权限)
@router.post("/outbounds")
async def create_outbound(
_: bool = Depends(PermissionChecker(
["sales_outbound:create", "sales_outbound:approve"],
require_all=True
)),
):
...
# 多权限 any 模式(拥有任一权限即可)
@router.get("/reports/sales")
async def view_sales_report(
_: bool = Depends(PermissionChecker(
["report_sales:view", "sales_order:read"],
require_all=False
)),
):
...
```
"""
def __init__(
self,
permissions: Union[str, List[str]],
require_all: bool = True,
require_superuser: bool = False,
):
"""
初始化权限校验器
Args:
permissions: 所需的权限标识符,可以是单个字符串或字符串列表
- 字符串格式:resource:action(如 "sales_order:read")
- 列表格式:["perm1:action1", "perm2:action2"]
require_all: 多权限校验模式
- True: 用户必须拥有所有指定权限
- False: 用户只需拥有任一指定权限
require_superuser: 是否要求超级用户身份(超级用户自动拥有所有权限)
"""
self.permissions = [permissions] if isinstance(permissions, str) else permissions
self.require_all = require_all
self.require_superuser = require_superuser
async def __call__(
self,
current_user: TokenData = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
) -> bool:
"""
执行权限校验
Args:
current_user: 当前登录用户的Token数据
session: 数据库会话对象
Returns:
bool: 权限校验通过返回True
Raises:
HTTPException: 权限不足时抛出403禁止访问异常
"""
from uuid import UUID
# 1. 如果配置了超级用户要求,先检查超级用户
if self.require_superuser:
user = await UserService.get_user_by_id(UUID(current_user.user_id), session)
if user and user.is_superuser:
return True
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"message": "仅超级管理员可执行此操作",
"code": status.HTTP_403_FORBIDDEN,
},
)
# 2. 如果没有需要校验的权限,直接放行
if not self.permissions:
return True
# 3. 获取用户的权限列表(通过角色链汇总)
user_permissions = await PermissionService.get_permissions_by_user(
session=session,
user_id=UUID(current_user.user_id),
)
# 构建用户拥有的权限名称集合
user_perm_names: Set[str] = {perm.name for perm in user_permissions}
# 4. 权限匹配检查
if self.require_all:
# all模式:必须拥有所有指定权限
missing = [perm for perm in self.permissions if perm not in user_perm_names]
if missing:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"message": "权限不足",
"code": status.HTTP_403_FORBIDDEN,
"missing_permissions": missing,
},
)
else:
# any模式:拥有任一权限即可
has_any = any(perm in user_perm_names for perm in self.permissions)
if not has_any:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"message": "权限不足",
"code": status.HTTP_403_FORBIDDEN,
"required_permissions": self.permissions,
},
)
return True
class RequireSuperuser:
"""
超级用户校验依赖
要求当前用户必须是超级用户(is_superuser=True),否则返回403。
通常用于系统管理类接口。
使用示例:
```python
@router.delete("/users/{user_id}")
async def delete_user(
_: bool = Depends(RequireSuperuser()),
):
...
```
"""
async def __call__(
self,
current_user: TokenData = Depends(get_current_user),
session: AsyncSession = Depends(get_session),
) -> bool:
from uuid import UUID
user = await UserService.get_user_by_id(UUID(current_user.user_id), session)
if not user or not user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail={
"message": "仅超级管理员可执行此操作",
"code": status.HTTP_403_FORBIDDEN,
},
)
return True
+369
View File
@@ -0,0 +1,369 @@
from typing import Optional
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.database import get_session
from ...model.permission import Permission, PermissionCreate, PermissionUpdate
from ...model.http_error import HTTPError, Detail
from ...service.permission import PermissionService
from ..deps import get_current_user, PermissionChecker
from ...constant.permission import PermissionMgtConstant, RoleConstant
from plugins.jwt.jwt_cache import TokenData
router = APIRouter(tags=["permission"])
@router.post(
"",
response_model=Permission,
responses={
400: {"model": HTTPError, "description": "权限名称已存在"},
},
summary="创建权限",
)
async def create_permission(
permission_data: PermissionCreate,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_CREATE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
创建权限
创建新的权限项,权限名称格式应为"资源:操作",如"user:create"。
Args:
permission_data: 权限创建数据,包含名称、资源、操作和描述等信息
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Permission: 创建成功的权限对象
Raises:
HTTPException: 当权限名称已存在时抛出400错误
"""
existing = await PermissionService.get_permission_by_name(
session, permission_data.name
)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=Detail(
message="权限名称已存在", code=status.HTTP_400_BAD_REQUEST
).model_dump(),
)
permission = await PermissionService.create_permission(
session=session,
name=permission_data.name,
resource=permission_data.resource,
action=permission_data.action,
description=permission_data.description,
)
return permission
@router.get(
"",
response_model=list[Permission],
summary="获取权限列表",
)
async def list_permissions(
skip: int = 0,
limit: int = 100,
resource: Optional[str] = None,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker([PermissionMgtConstant.PERMISSION_READ, RoleConstant.ROLE_READ], require_all=False)),
__: TokenData = Depends(get_current_user),
):
"""
获取权限列表
返回系统中的所有权限项,支持分页和按资源名称筛选。
Args:
skip: 跳过的记录数,用于分页
limit: 返回的最大记录数,用于分页
resource: 资源名称筛选条件,可选
session: 数据库会话对象
current_user: 当前登录用户
Returns:
list[Permission]: 权限对象列表
"""
permissions = await PermissionService.list_permissions(
session=session,
skip=skip,
limit=limit,
resource=resource,
)
return list(permissions)
@router.get(
"/{permission_id}",
response_model=Permission,
responses={
404: {"model": HTTPError, "description": "权限不存在"},
},
summary="获取权限详情",
)
async def get_permission(
permission_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_READ)),
__: TokenData = Depends(get_current_user),
):
"""
获取权限详情
根据权限ID获取指定权限的详细信息。
Args:
permission_id: 权限ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Permission: 权限对象
Raises:
HTTPException: 当权限不存在时抛出404错误
"""
permission = await PermissionService.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=Detail(
message="权限不存在", code=status.HTTP_404_NOT_FOUND
).model_dump(),
)
return permission
@router.put(
"/{permission_id}",
response_model=Permission,
responses={
404: {"model": HTTPError, "description": "权限不存在"},
},
summary="更新权限",
)
async def update_permission(
permission_id: int,
permission_update: PermissionUpdate,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_UPDATE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
更新权限信息
更新指定权限的名称、描述、资源或操作等信息。
Args:
permission_id: 权限ID
permission_update: 权限更新数据,所有字段可选
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Permission: 更新后的权限对象
Raises:
HTTPException: 当权限不存在时抛出404错误
"""
permission = await PermissionService.update_permission(
session=session,
permission_id=permission_id,
permission_update=permission_update,
)
return permission
@router.delete(
"/{permission_id}",
responses={
400: {"model": HTTPError, "description": "权限已被角色使用,无法删除"},
404: {"model": HTTPError, "description": "权限不存在"},
},
summary="删除权限",
)
async def delete_permission(
permission_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_DELETE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
删除权限
删除指定权限。如果权限已被分配给角色,则无法删除。
Args:
permission_id: 权限ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 删除成功响应消息
Raises:
HTTPException: 当权限被角色使用时抛出400错误,权限不存在时抛出404错误
"""
await PermissionService.delete_permission(session=session, permission_id=permission_id)
return {"message": "权限删除成功", "code": 200}
@router.post(
"/{permission_id}/assign-to-role/{role_id}",
summary="将权限分配给角色",
responses={
400: {"model": HTTPError, "description": "权限已分配给角色"},
404: {"model": HTTPError, "description": "角色或权限不存在"},
},
)
async def assign_permission_to_role(
permission_id: int,
role_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_BIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
将权限分配给角色
将指定权限分配给指定角色。
Args:
permission_id: 权限ID
role_id: 角色ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 分配成功响应消息
Raises:
HTTPException: 当权限已分配给角色或角色/权限不存在时抛出错误
"""
await PermissionService.assign_permission_to_role(
session=session, role_id=role_id, permission_id=permission_id
)
return {"message": "权限分配成功", "code": 200}
@router.post(
"/{permission_id}/unbind-from-role/{role_id}",
summary="从角色解除权限绑定",
responses={
404: {"model": HTTPError, "description": "角色不存在"},
},
)
async def unbind_permission_from_role(
permission_id: int,
role_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_UNBIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
从角色解除权限绑定
将指定权限从指定角色中移除。
Args:
permission_id: 权限ID
role_id: 角色ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 解绑成功响应消息
Raises:
HTTPException: 当角色不存在时抛出404错误
"""
await PermissionService.unbind_permission_from_role(
session=session, role_id=role_id, permission_id=permission_id
)
return {"message": "权限解除绑定成功", "code": 200}
@router.post(
"/{permission_id}/assign-to-user/{user_id}",
summary="将权限分配给用户",
responses={
404: {"model": HTTPError, "description": "用户或权限不存在"},
},
)
async def assign_permission_to_user(
permission_id: int,
user_id: UUID,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_BIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
将权限分配给用户
将指定权限分配给指定用户。如果用户没有专属角色,则会自动创建一个专属角色。
Args:
permission_id: 权限ID
user_id: 用户UUID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 分配成功响应消息
Raises:
HTTPException: 当用户或权限不存在时抛出404错误
"""
await PermissionService.assign_permission_to_user(
session=session, user_id=user_id, permission_id=permission_id
)
return {"message": "权限分配成功", "code": 200}
@router.post(
"/{permission_id}/unbind-from-user/{user_id}",
summary="从用户解除权限绑定",
responses={
404: {"model": HTTPError, "description": "用户或权限不存在"},
},
)
async def unbind_permission_from_user(
permission_id: int,
user_id: UUID,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(PermissionMgtConstant.PERMISSION_UNBIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
从用户解除权限绑定
将指定权限从指定用户中移除。
Args:
permission_id: 权限ID
user_id: 用户UUID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 解绑成功响应消息
Raises:
HTTPException: 当用户或角色不存在时抛出404错误
"""
await PermissionService.unbind_permission_from_user(
session=session, user_id=user_id, permission_id=permission_id
)
return {"message": "权限解除绑定成功", "code": 200}
+351
View File
@@ -0,0 +1,351 @@
from typing import Optional
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.database import get_session
from ...model.role import Role, RoleUpdate
from ...model.http_error import HTTPError, Detail
from ...service.role import RoleService
from ...service.permission import PermissionService
from ..deps import get_current_user, PermissionChecker
from ...constant.permission import RoleConstant
from plugins.jwt.jwt_cache import TokenData
router = APIRouter(tags=["role"])
@router.post(
"",
response_model=Role,
responses={
400: {"model": HTTPError, "description": "角色名称已存在"},
},
summary="创建角色",
)
async def create_role(
name: str,
description: Optional[str] = None,
is_only_user: bool = False,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_CREATE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
创建角色
创建新的角色,如 admin、manager、operator 等。
Args:
name: 角色名称
description: 角色描述,可选
is_only_user: 是否仅用于单个用户,默认为False
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Role: 创建成功的角色对象
Raises:
HTTPException: 当角色名称已存在时抛出400错误
"""
existing = await RoleService.get_role_by_name(session, name)
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=Detail(
message="角色名称已存在", code=status.HTTP_400_BAD_REQUEST
).model_dump(),
)
role = await RoleService.create_role(
session=session,
name=name,
description=description,
is_only_user=is_only_user,
)
return role
@router.get(
"",
response_model=list[Role],
summary="获取角色列表",
)
async def list_roles(
skip: int = 0,
limit: int = 100,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_READ)),
__: TokenData = Depends(get_current_user),
):
"""
获取角色列表
返回系统中的所有角色,支持分页。
Args:
skip: 跳过的记录数,用于分页
limit: 返回的最大记录数,用于分页
session: 数据库会话对象
current_user: 当前登录用户
Returns:
list[Role]: 角色对象列表
"""
roles = await RoleService.list_roles(
session=session,
skip=skip,
limit=limit,
)
return list(roles)
@router.get(
"/{role_id}",
response_model=Role,
responses={
404: {"model": HTTPError, "description": "角色不存在"},
},
summary="获取角色详情",
)
async def get_role(
role_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_READ)),
__: TokenData = Depends(get_current_user),
):
"""
获取角色详情
根据角色ID获取指定角色的详细信息。
Args:
role_id: 角色ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Role: 角色对象
Raises:
HTTPException: 当角色不存在时抛出404错误
"""
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=Detail(
message="角色不存在", code=status.HTTP_404_NOT_FOUND
).model_dump(),
)
return role
@router.put(
"/{role_id}",
response_model=Role,
responses={
404: {"model": HTTPError, "description": "角色不存在"},
},
summary="更新角色",
)
async def update_role(
role_id: int,
role_update: RoleUpdate,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_UPDATE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
更新角色信息
更新指定角色的名称或描述等信息。
Args:
role_id: 角色ID
role_update: 角色更新数据,所有字段可选
session: 数据库会话对象
current_user: 当前登录用户
Returns:
Role: 更新后的角色对象
Raises:
HTTPException: 当角色不存在时抛出404错误
"""
role = await RoleService.update_role(
session=session,
role_id=role_id,
role_update=role_update,
)
return role
@router.delete(
"/{role_id}",
responses={
400: {"model": HTTPError, "description": "角色不为空,无法删除"},
404: {"model": HTTPError, "description": "角色不存在"},
},
summary="删除角色",
)
async def delete_role(
role_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_DELETE, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
删除角色
删除指定角色。如果角色已被分配给用户,则无法删除。
Args:
role_id: 角色ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 删除成功响应消息
Raises:
HTTPException: 当角色被用户使用时抛出400错误,角色不存在时抛出404错误
"""
await RoleService.delete_role(
role_id=role_id,
session=session,
)
return {"message": "角色删除成功", "code": 200}
@router.post(
"/{role_id}/assign-to-user/{user_id}",
summary="将角色分配给用户",
responses={
404: {"model": HTTPError, "description": "角色或用户不存在"},
},
)
async def assign_role_to_user(
role_id: int,
user_id: UUID,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_BIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
将角色分配给用户
将指定角色分配给指定用户。
Args:
role_id: 角色ID
user_id: 用户UUID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 分配成功响应消息
Raises:
HTTPException: 当角色或用户不存在时抛出404错误
"""
# 检查角色是否存在
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=Detail(
message="角色不存在", code=status.HTTP_404_NOT_FOUND
).model_dump(),
)
# 检查用户是否存在
from ...service.user import UserService
user = await UserService.get_user_by_id(user_id, session)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=Detail(
message="用户不存在", code=status.HTTP_404_NOT_FOUND
).model_dump(),
)
await RoleService.assign_role_to_user(
session=session,
user_id=user_id,
role_id=role_id,
)
return {"message": "角色分配成功", "code": 200}
@router.post(
"/{role_id}/unbind-from-user/{user_id}",
summary="从用户解除角色绑定",
responses={
404: {"model": HTTPError, "description": "用户不存在"},
},
)
async def unbind_role_from_user(
role_id: int,
user_id: UUID,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_UNBIND, require_superuser=True)),
__: TokenData = Depends(get_current_user),
):
"""
从用户解除角色绑定
将指定角色从指定用户中移除。
Args:
role_id: 角色ID
user_id: 用户UUID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
dict: 解绑成功响应消息
Raises:
HTTPException: 当用户不存在时抛出404错误
"""
await RoleService.unbind_role_from_user(
session=session,
user_id=user_id,
role_id=role_id,
)
return {"message": "角色解除绑定成功", "code": 200}
@router.get(
"/{role_id}/permissions",
response_model=list,
summary="获取角色的权限列表",
)
async def get_role_permissions(
role_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(RoleConstant.ROLE_READ)),
__: TokenData = Depends(get_current_user),
):
"""
获取指定角色的所有权限
Args:
role_id: 角色ID
session: 数据库会话对象
current_user: 当前登录用户
Returns:
list: 权限对象列表
"""
permissions = await PermissionService.get_permissions_by_role(
session=session,
role_id=role_id,
)
return list(permissions)
+21 -5
View File
@@ -5,8 +5,9 @@ from sqlalchemy.ext.asyncio import AsyncSession
from ...model.todo import TodoCreate, TodoUpdate, TodoRead from ...model.todo import TodoCreate, TodoUpdate, TodoRead
from ...core.database import get_session from ...core.database import get_session
from ...constant.permission import TodoConstant
from ...service.todo import TodoService from ...service.todo import TodoService
from ..deps import get_current_user from ..deps import get_current_user, PermissionChecker
router = APIRouter(dependencies=[Depends(get_current_user)]) router = APIRouter(dependencies=[Depends(get_current_user)])
@@ -17,6 +18,7 @@ router = APIRouter(dependencies=[Depends(get_current_user)])
async def create_todo( async def create_todo(
todo: TodoCreate, todo: TodoCreate,
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(TodoConstant.TODO_CREATE)),
): ):
""" """
创建新的待办事项 创建新的待办事项
@@ -33,7 +35,10 @@ async def create_todo(
@router.get("/", response_model=List[TodoRead]) @router.get("/", response_model=List[TodoRead])
async def read_todos( async def read_todos(
offset: int = 0, limit: int = 100, session: AsyncSession = Depends(get_session) offset: int = 0,
limit: int = 100,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(TodoConstant.TODO_READ)),
): ):
""" """
获取待办事项列表 获取待办事项列表
@@ -50,7 +55,11 @@ async def read_todos(
@router.get("/{todo_id}", response_model=TodoRead) @router.get("/{todo_id}", response_model=TodoRead)
async def read_todo(todo_id: int, session: AsyncSession = Depends(get_session)): async def read_todo(
todo_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(TodoConstant.TODO_READ)),
):
""" """
根据ID获取单个待办事项 根据ID获取单个待办事项
@@ -72,7 +81,10 @@ async def read_todo(todo_id: int, session: AsyncSession = Depends(get_session)):
@router.patch("/{todo_id}", response_model=TodoRead) @router.patch("/{todo_id}", response_model=TodoRead)
async def update_todo( async def update_todo(
todo_id: int, todo_update: TodoUpdate, session: AsyncSession = Depends(get_session) todo_id: int,
todo_update: TodoUpdate,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(TodoConstant.TODO_UPDATE)),
): ):
""" """
更新待办事项信息 更新待办事项信息
@@ -90,7 +102,11 @@ async def update_todo(
@router.delete("/{todo_id}") @router.delete("/{todo_id}")
async def delete_todo(todo_id: int, session: AsyncSession = Depends(get_session)): async def delete_todo(
todo_id: int,
session: AsyncSession = Depends(get_session),
_: bool = Depends(PermissionChecker(TodoConstant.TODO_DELETE)),
):
""" """
删除待办事项 删除待办事项
@@ -0,0 +1,76 @@
from .personnel import PersonnelConstant
from .role import RoleConstant
from .permission_mgt import PermissionMgtConstant
from .system_config import SystemConfigConstant
from .todo import TodoConstant
class PermissionConstant:
"""
权限常量类 - 按业务模块分组管理所有系统权限
该类定义了系统中各个业务模块的权限标识符,采用分层结构组织:
- 每个业务域作为一个内部类(Personnel, Role, Permission, SystemConfig, Todo)
- 每个权限使用 "资源:操作" 格式定义
使用示例:
>>> PermissionConstant.Personnel.PERSONNEL_CREATE
'personnel:create'
>>> PermissionConstant.get_all()
['personnel:create', 'personnel:read', ...]
"""
Personnel = PersonnelConstant
PREFIX_PERSONNEL = "personnel"
Role = RoleConstant
PREFIX_ROLE = "role"
Permission = PermissionMgtConstant
PREFIX_PERMISSION = "permission"
SystemConfig = SystemConfigConstant
PREFIX_SYSTEM_CONFIG = "system_config"
PREFIX_AUDIT_LOG = "audit_log"
PREFIX_BACKUP = "backup"
Todo = TodoConstant
PREFIX_TODO = "todo"
@classmethod
def get_all(cls):
"""
获取系统中定义的所有权限字符串列表
通过反射机制自动收集所有内部类中定义的权限常量。
Returns:
list[str]: 所有权限字符串的列表
"""
perms = []
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if attr_name.startswith("_") or not hasattr(attr, "__dict__"):
continue
# 遍历内部类的属性
for sub_attr in dir(attr):
if sub_attr.startswith("_"):
continue
val = getattr(attr, sub_attr)
if isinstance(val, str) and ":" in val:
perms.append(val)
return perms
@classmethod
def get_description_map(cls) -> dict:
"""
获取系统中所有权限的描述映射
Returns:
dict[str, str]: 权限字符串到描述的映射字典
"""
description_map = {}
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if attr_name.startswith("_") or not hasattr(attr, "__dict__"):
continue
description_dict = getattr(attr, "permission_description_dict", None)
if isinstance(description_dict, dict):
description_map.update(description_dict)
return description_map
@@ -0,0 +1,18 @@
class PermissionMgtConstant:
"""权限管理权限常量"""
PERMISSION_CREATE = "permission:create" # 创建权限
PERMISSION_UPDATE = "permission:update" # 修改权限
PERMISSION_DELETE = "permission:delete" # 删除权限
PERMISSION_READ = "permission:read" # 读取权限
PERMISSION_BIND = "permission:bind" # 分配权限
PERMISSION_UNBIND = "permission:unbind" # 解绑权限
permission_description_dict = {
PERMISSION_CREATE: "创建权限",
PERMISSION_UPDATE: "修改权限",
PERMISSION_DELETE: "删除权限",
PERMISSION_READ: "读取权限",
PERMISSION_BIND: "分配权限",
PERMISSION_UNBIND: "解绑权限",
}
@@ -0,0 +1,14 @@
class PersonnelConstant:
"""人员管理权限常量"""
PERSONNEL_CREATE = "personnel:create" # 创建人员
PERSONNEL_READ = "personnel:read" # 读取人员
PERSONNEL_UPDATE = "personnel:update" # 更新人员
PERSONNEL_DELETE = "personnel:delete" # 删除人员
permission_description_dict = {
PERSONNEL_CREATE: "创建人员",
PERSONNEL_READ: "读取人员",
PERSONNEL_UPDATE: "更新人员",
PERSONNEL_DELETE: "删除人员",
}
@@ -0,0 +1,18 @@
class RoleConstant:
"""角色管理权限常量"""
ROLE_CREATE = "role:create" # 创建角色
ROLE_READ = "role:read" # 读取角色
ROLE_UPDATE = "role:update" # 更新角色
ROLE_DELETE = "role:delete" # 删除角色
ROLE_BIND = "role:assign" # 分配角色
ROLE_UNBIND = "role:unbind" # 解绑角色
permission_description_dict = {
ROLE_CREATE: "创建角色",
ROLE_READ: "读取角色",
ROLE_UPDATE: "更新角色",
ROLE_DELETE: "删除角色",
ROLE_BIND: "分配角色",
ROLE_UNBIND: "解绑角色",
}
@@ -0,0 +1,16 @@
class SystemConfigConstant:
"""系统配置、审计日志与备份恢复权限常量"""
CONFIG_READ = "system_config:read" # 读取系统配置
CONFIG_UPDATE = "system_config:update" # 更新系统配置
AUDIT_LOG_READ = "audit_log:read" # 读取审计日志
BACKUP_CREATE = "backup:create" # 创建备份
BACKUP_RESTORE = "backup:restore" # 恢复备份
permission_description_dict = {
CONFIG_READ: "读取系统配置",
CONFIG_UPDATE: "更新系统配置",
AUDIT_LOG_READ: "读取审计日志",
BACKUP_CREATE: "创建备份",
BACKUP_RESTORE: "恢复备份",
}
@@ -0,0 +1,14 @@
class TodoConstant:
"""Todo任务管理权限常量"""
TODO_CREATE = "todo:create" # 创建待办事项
TODO_READ = "todo:read" # 读取待办事项
TODO_UPDATE = "todo:update" # 更新待办事项
TODO_DELETE = "todo:delete" # 删除待办事项
permission_description_dict = {
TODO_CREATE: "创建待办事项",
TODO_READ: "读取待办事项",
TODO_UPDATE: "更新待办事项",
TODO_DELETE: "删除待办事项",
}
+60 -14
View File
@@ -6,7 +6,7 @@
""" """
import time import time
from typing import Any, Optional from typing import Any, Callable, Optional
import diskcache import diskcache
@@ -347,7 +347,7 @@ def get_cache() -> Cache:
# 装饰器:缓存函数结果 # 装饰器:缓存函数结果
def cached(ttl: Optional[int] = None): def cached(ttl: Optional[int] = None):
""" """
函数结果缓存装饰器 函数结果缓存装饰器(同步版本)
自动缓存函数的返回值,相同参数的调用直接从缓存返回, 自动缓存函数的返回值,相同参数的调用直接从缓存返回,
避免重复计算。适用于纯函数或副作用小的函数。 避免重复计算。适用于纯函数或副作用小的函数。
@@ -363,30 +363,76 @@ def cached(ttl: Optional[int] = None):
... def get_user(user_id: int): ... def get_user(user_id: int):
... return {"id": user_id, "name": "test"} ... return {"id": user_id, "name": "test"}
>>> >>>
>>> # 第一次调用执行函数并缓存
>>> get_user(1) >>> get_user(1)
>>> # 60秒内再次调用直接返回缓存结果
>>> get_user(1)
Note:
- 缓存键由函数名 + 位置参数 + 关键字参数组成
- 参数必须是可哈希的,否则会导致错误
- 不适合缓存大型对象或频繁变化的数据
- 不同参数组合会生成不同的缓存键
""" """
def decorator(func): def decorator(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
# 生成缓存key
key = f"{func.__name__}:{str(args)}:{str(kwargs)}" key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
cache = get_cache() cache = get_cache()
# 尝试从缓存获取
result = cache.get(key) result = cache.get(key)
if result is not None: if result is not None:
return result return result
# 执行函数
result = func(*args, **kwargs) result = func(*args, **kwargs)
cache.set(key, result, ex=ttl)
return result
return wrapper
return decorator
# 装饰器:异步函数结果缓存
def acached(ttl: Optional[int] = None, key_func: Optional[Callable] = None):
"""
函数结果缓存装饰器(异步版本)
自动缓存异步函数的返回值,相同参数的调用直接从缓存返回。
支持自定义缓存键生成函数,以便排除 `session` 等无法序列化
或不应作为缓存键一部分的参数。
Args:
ttl: 缓存生存时间(秒),None 表示永不过期
key_func: 自定义缓存键生成函数,接收与原始函数相同的参数
(args, kwargs),返回字符串作为缓存键。
默认使用 f"{func.__name__}:{str(args)}:{str(kwargs)}"
Returns:
callable: 装饰器函数
Example:
>>> @acached(ttl=120, key_func=lambda cls, session, user_id: f"user_perms:{user_id}")
... async def get_permissions_by_user(cls, session, user_id):
... return await some_db_query(session, user_id)
>>>
>>> # 需要手动清除缓存时:
>>> cache = get_cache()
>>> cache.delete("user_perms:xxx-uuid-xxx")
Note:
- 装饰器应放在 @classmethod(如果有的话)下方
- 使用 key_func 可以精确控制缓存键,避免 session 等对象影响
- 缓存失效需通过 cache.delete(key) 手动操作
"""
def decorator(func):
async def wrapper(*args, **kwargs):
# 生成缓存key
if key_func is not None:
key = key_func(*args, **kwargs)
else:
key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
cache = get_cache()
# 尝试从缓存获取
result = cache.get(key)
if result is not None:
print(f"缓存输出: {result}")
return result
# 执行异步函数
result = await func(*args, **kwargs)
# 存入缓存 # 存入缓存
cache.set(key, result, ex=ttl) cache.set(key, result, ex=ttl)
-277
View File
@@ -1,277 +0,0 @@
class PermissionConstant:
"""
权限常量类 - 按业务模块分组管理所有系统权限
该类定义了系统中各个业务模块的权限标识符,采用分层结构组织:
- 每个业务域作为一个内部类(如 Sales, Purchase, Inventory 等)
- 每个权限使用 "资源:操作" 格式定义
- 支持 CRUD 操作及业务特定操作(如 approve, export 等)
使用示例:
>>> Permission.Sales.ORDER_CREATE # 获取销售订单创建权限
'sales_order:create'
>>> Permission.get_all() # 获取所有权限列表
['sales_order:create', 'sales_order:read', ...]
"""
class Sales:
"""
销售域权限常量
包含销售相关的权限定义:
- 销售订单管理(ORDER_*)
- 销售出库管理(OUTBOUND_*)
- 客户管理(CUSTOMER_*)
权限格式: sales_order:create, customer:export 等
"""
ORDER_CREATE = "sales_order:create" # 创建销售订单
ORDER_READ = "sales_order:read" # 读取销售订单
ORDER_UPDATE = "sales_order:update" # 修改销售订单
ORDER_DELETE = "sales_order:delete" # 删除销售订单
OUTBOUND_CREATE = "sales_outbound:create" # 创建销售出库
OUTBOUND_READ = "sales_outbound:read" # 读取销售出库
OUTBOUND_UPDATE = "sales_outbound:update" # 修改销售出库
OUTBOUND_DELETE = "sales_outbound:delete" # 删除销售出库
OUTBOUND_APPROVE = "sales_outbound:approve" # 审批销售出库
CUSTOMER_CREATE = "customer:create" # 创建客户
CUSTOMER_READ = "customer:read" # 读取客户
CUSTOMER_UPDATE = "customer:update" # 修改客户
CUSTOMER_DELETE = "customer:delete" # 删除客户
CUSTOMER_EXPORT = "customer:export" # 导出客户
class Purchase:
"""
采购域权限常量
包含采购相关的权限定义:
- 采购订单管理(ORDER_*)
- 采购入库管理(INBOUND_*),包含审批和取消操作
- 供应商管理(SUPPLIER_*)
权限格式: purchase_order:create, supplier:update 等
"""
ORDER_CREATE = "purchase_order:create" # 创建采购订单
ORDER_READ = "purchase_order:read" # 读取采购订单
ORDER_UPDATE = "purchase_order:update" # 更新采购订单
ORDER_DELETE = "purchase_order:delete" # 删除采购订单
INBOUND_CREATE = "purchase_inbound:create" # 创建采购入库
INBOUND_READ = "purchase_inbound:read" # 读取采购入库
INBOUND_UPDATE = "purchase_inbound:update" # 更新采购入库
INBOUND_DELETE = "purchase_inbound:delete" # 删除采购入库
INBOUND_APPROVE = "purchase_inbound:approve" # 审批采购入库
INBOUND_CANCEL = "purchase_inbound:cancel" # 取消采购入库
SUPPLIER_CREATE = "supplier:create" # 创建供应商
SUPPLIER_READ = "supplier:read" # 读取供应商
SUPPLIER_UPDATE = "supplier:update" # 更新供应商
SUPPLIER_DELETE = "supplier:delete" # 删除供应商
class Inventory:
"""
库存域权限常量
包含库存管理相关的权限定义:
- 库存管理(STOCK_*)
- 仓库管理(WAREHOUSE_*)
- 其他出入库单据(OTHER_INBOUND_*, OTHER_OUTBOUND_*)
- 调拨单管理(TRANSFER_*)
- 盘点单管理(STOCK_CHECK_*)
特点: 大部分单据支持审批流程(APPROVE 权限)
权限格式: stock:read, transfer:approve 等
"""
STOCK_CREATE = "stock:create" # 创建库存
STOCK_READ = "stock:read" # 读取库存
STOCK_UPDATE = "stock:update" # 更新库存
STOCK_DELETE = "stock:delete" # 删除库存
WAREHOUSE_CREATE = "warehouse:create" # 创建仓库
WAREHOUSE_READ = "warehouse:read" # 读取仓库
WAREHOUSE_UPDATE = "warehouse:update" # 更新仓库
WAREHOUSE_DELETE = "warehouse:delete" # 删除仓库
# 库存单据
OTHER_INBOUND_CREATE = "other_inbound:create" # 创建其他入库
OTHER_INBOUND_READ = "other_inbound:read" # 读取其他入库
OTHER_INBOUND_UPDATE = "other_inbound:update" # 更新其他入库
OTHER_INBOUND_DELETE = "other_inbound:delete" # 删除其他入库
OTHER_INBOUND_APPROVE = "other_inbound:approve" # 审核其他入库
OTHER_OUTBOUND_CREATE = "other_outbound:create" # 创建其他出库
OTHER_OUTBOUND_READ = "other_outbound:read" # 读取其他出库
OTHER_OUTBOUND_UPDATE = "other_outbound:update" # 更新其他出库
OTHER_OUTBOUND_DELETE = "other_outbound:delete" # 删除其他出库
OTHER_OUTBOUND_APPROVE = "other_outbound:approve" # 审核其他出库
TRANSFER_CREATE = "transfer:create" # 创建调拨
TRANSFER_READ = "transfer:read" # 读取调拨
TRANSFER_UPDATE = "transfer:update" # 更新调拨
TRANSFER_DELETE = "transfer:delete" # 删除调拨
TRANSFER_APPROVE = "transfer:approve" # 审核调拨
STOCK_CHECK_CREATE = "stock_check:create" # 创建盘点
STOCK_CHECK_READ = "stock_check:read" # 读取盘点
STOCK_CHECK_UPDATE = "stock_check:update" # 更新盘点
STOCK_CHECK_DELETE = "stock_check:delete" # 删除盘点
STOCK_CHECK_APPROVE = "stock_check:approve" # 审核盘点
class Product:
"""
产品主数据权限常量
包含产品和基础数据管理的权限定义:
- 产品管理(PRODUCT_*),支持导入导出
- 产品分类管理(CATEGORY_*)
- 计量单位管理(UNIT_*)
权限格式: product:import, product_category:read 等
"""
PRODUCT_CREATE = "product:create" # 创建产品
PRODUCT_READ = "product:read" # 读取产品
PRODUCT_UPDATE = "product:update" # 更新产品
PRODUCT_DELETE = "product:delete" # 删除产品
PRODUCT_IMPORT = "product:import" # 导入产品
PRODUCT_EXPORT = "product:export" # 导出产品
CATEGORY_CREATE = "product_category:create" # 创建产品分类
CATEGORY_READ = "product_category:read" # 读取产品分类
CATEGORY_UPDATE = "product_category:update" # 更新产品分类
CATEGORY_DELETE = "product_category:delete" # 删除产品分类
UNIT_CREATE = "unit:create" # 创建单位
UNIT_READ = "unit:read" # 读取单位
UNIT_UPDATE = "unit:update" # 更新单位
UNIT_DELETE = "unit:delete" # 删除单位
class Finance:
"""
财务域权限常量
包含财务管理相关的权限定义:
- 应收应付查询(RECEIVABLE_READ, PAYABLE_READ)
- 付款管理(PAYMENT_*),包含审批流程
- 发票管理(INVOICE_*)
注意: 财务域以只读权限为主,写操作需要审批
权限格式: payment:approve, invoice:create 等
"""
RECEIVABLE_READ = "receivable:read" # 读取应收应付
PAYABLE_READ = "payable:read" # 读取应付
PAYMENT_CREATE = "payment:create" # 创建付款
PAYMENT_READ = "payment:read" # 读取付款
PAYMENT_APPROVE = "payment:approve" # 审批付款
INVOICE_CREATE = "invoice:create" # 创建发票
INVOICE_READ = "invoice:read" # 读取发票
class Report:
"""
报表与分析权限常量
包含各类报表查看权限:
- 销售报表(SALES_VIEW)
- 采购报表(PURCHASE_VIEW)
- 库存报表(STOCK_VIEW)
- 财务报表(FINANCE_VIEW)
- 仪表盘(DASHBOARD_VIEW)
特点: 所有权限均为只读查看权限
权限格式: report:sales:view, dashboard:view 等
"""
SALES_VIEW = "report_sales:view" # 销售报表
PURCHASE_VIEW = "report_purchase:view" # 采购报表
STOCK_VIEW = "report_stock:view" # 库存报表
FINANCE_VIEW = "report_finance:view" # 财务报表
DASHBOARD_VIEW = "dashboard:view" # 仪表盘
class System:
"""
系统管理权限常量
包含系统级管理功能的权限定义:
- 人员管理(PERSONNEL_*)
- 角色管理(ROLE_*),包含角色分配权限
- 系统配置(CONFIG_*)
- 审计日志(AUDIT_LOG_READ)
- 数据备份恢复(BACKUP_*)
注意: 这些是最高权限级别的操作,应谨慎分配
权限格式: role:assign, backup:restore 等
"""
PERSONNEL_CREATE = "personnel:create" # 创建人员
PERSONNEL_READ = "personnel:read" # 读取人员
PERSONNEL_UPDATE = "personnel:update" # 更新人员
PERSONNEL_DELETE = "personnel:delete" # 删除人员
ROLE_CREATE = "role:create" # 创建角色
ROLE_READ = "role:read" # 读取角色
ROLE_UPDATE = "role:update" # 更新角色
ROLE_DELETE = "role:delete" # 删除角色
ASSIGN_PERMISSION = "permission:assign" # 分配权限
ASSIGN_ROLE = "role:assign" # 分配角色
CONFIG_READ = "system_config:read" # 读取系统配置
CONFIG_UPDATE = "system_config:update" # 更新系统配置
AUDIT_LOG_READ = "audit_log:read" # 读取审计日志
BACKUP_CREATE = "backup:create" # 创建备份
BACKUP_RESTORE = "backup:restore" # 恢复备份
@classmethod
def get_all(cls):
"""
获取系统中定义的所有权限字符串列表
该方法通过反射机制自动收集所有内部类中定义的权限常量,
用于数据库同步、权限初始化等场景。
工作流程:
1. 遍历 Permission 类的所有属性
2. 跳过私有属性和非类属性
3. 对每个内部类,遍历其所有字符串类型的权限常量
4. 过滤出符合 "resource:action" 格式的权限标识
Returns:
list[str]: 所有权限字符串的列表,例如:
[
'sales_order:create',
'sales_order:read',
'customer:export',
...
]
使用示例:
>>> all_perms = Permission.get_all()
>>> len(all_perms) # 获取权限总数
85
>>> 'sales_order:create' in all_perms # 检查权限是否存在
True
Note:
- 该方法会自动排除以下划线开头的私有属性
- 只收集包含冒号的字符串值,确保是有效的权限标识
- 返回的列表顺序取决于 dir() 函数的返回顺序
"""
perms = []
for attr_name in dir(cls):
attr = getattr(cls, attr_name)
if attr_name.startswith("_") or not hasattr(attr, "__dict__"):
continue
# 遍历内部类的属性
for sub_attr in dir(attr):
if sub_attr.startswith("_"):
continue
val = getattr(attr, sub_attr)
if isinstance(val, str) and ":" in val:
perms.append(val)
return perms
+3 -1
View File
@@ -1,6 +1,6 @@
from .user import User from .user import User
from .role import Role, RoleUpdate from .role import Role, RoleUpdate
from .permission import Permission from .permission import Permission, PermissionUpdate, PermissionCreate
from .relationship_table import UserRole, RolePermission from .relationship_table import UserRole, RolePermission
from .todo import Todo from .todo import Todo
@@ -10,6 +10,8 @@ __all__ = [
"Role", "Role",
"RoleUpdate", "RoleUpdate",
"Permission", "Permission",
"PermissionUpdate",
"PermissionCreate",
"UserRole", "UserRole",
"RolePermission", "RolePermission",
"Todo", "Todo",
+37
View File
@@ -1,5 +1,6 @@
from typing import Optional, List, TYPE_CHECKING from typing import Optional, List, TYPE_CHECKING
from pydantic import BaseModel
from sqlmodel import SQLModel, Field, Relationship from sqlmodel import SQLModel, Field, Relationship
from .relationship_table import RolePermission from .relationship_table import RolePermission
@@ -34,3 +35,39 @@ class Permission(SQLModel, table=True):
roles: List["Role"] = Relationship( roles: List["Role"] = Relationship(
back_populates="permissions", link_model=RolePermission back_populates="permissions", link_model=RolePermission
) )
class PermissionUpdate(BaseModel):
"""
权限更新请求模型类,用于接收权限更新时的参数。
所有字段均为可选,允许部分更新权限信息。
Attributes:
name: 更新后的权限名称,可选字段
description: 更新后的权限描述信息,可选字段
resource: 更新后的资源名称,可选字段
action: 更新后的操作类型,可选字段
"""
name: Optional[str] = None
description: Optional[str] = None
resource: Optional[str] = None
action: Optional[str] = None
class PermissionCreate(BaseModel):
"""
权限创建请求模型类,用于接收权限创建时的参数。
Attributes:
name: 权限名称,格式为"资源:操作"
description: 权限描述信息,可选字段
resource: 资源名称,如 user、order、product 等
action: 操作类型,如 create、read、update、delete 等
"""
name: str
description: Optional[str] = None
resource: str
action: str
+237 -13
View File
@@ -1,15 +1,20 @@
from typing import Optional from typing import Optional, List, Sequence
from uuid import UUID from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import select from sqlmodel import select
from ..model import Permission, RolePermission, Role, User from ..model import Permission, PermissionUpdate, RolePermission, Role, User
from ..core.cache import acached, get_cache
from .user import UserService from .user import UserService
from .role import RoleService from .role import RoleService
# 用户权限缓存的 TTL(秒),权限变更后最多 2 分钟自动刷新
_USER_PERMS_CACHE_TTL = 120
class PermissionService: class PermissionService:
"""权限服务类,提供权限的创建、查询、分配和解除绑定等功能""" """权限服务类,提供权限的创建、查询、分配和解除绑定等功能"""
@@ -69,6 +74,189 @@ class PermissionService:
) )
return result.scalar_one_or_none() return result.scalar_one_or_none()
@classmethod
async def list_permissions(
cls,
session: AsyncSession,
skip: int = 0,
limit: int = 100,
resource: Optional[str] = None,
) -> Sequence[Permission]:
"""
获取权限列表,支持分页和按资源筛选
:param session: 数据库会话对象
:param skip: 跳过的记录数,用于分页
:param limit: 返回的最大记录数,用于分页
:param resource: 资源名称筛选条件,可选
:return: 权限对象列表
"""
query = select(Permission)
if resource:
query = query.where(Permission.resource == resource)
query = query.offset(skip).limit(limit)
result = await session.execute(query)
return result.scalars().all()
@classmethod
async def update_permission(
cls,
session: AsyncSession,
permission_id: int,
permission_update: PermissionUpdate,
) -> Permission:
"""
更新权限信息
:param session: 数据库会话对象
:param permission_id: 权限ID
:param permission_update: 权限更新数据
:return: 更新后的权限对象
:raises HTTPException: 当权限不存在时抛出404错误
"""
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
# 只更新提供的字段
update_data = permission_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(permission, field, value)
await session.commit()
await session.refresh(permission)
return permission
@classmethod
async def delete_permission(
cls,
session: AsyncSession,
permission_id: int,
) -> bool:
"""
删除权限
:param session: 数据库会话对象
:param permission_id: 权限ID
:return: 删除成功返回True
:raises HTTPException: 当权限不存在或正在被使用时抛出对应错误
"""
# 检查权限是否被角色使用
result = await session.execute(
select(RolePermission).where(
RolePermission.permission_id == permission_id
)
)
if result.scalars().all():
raise HTTPException(
status_code=400, detail="Permission is assigned to roles, cannot delete"
)
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
await session.delete(permission)
await session.commit()
return True
@classmethod
async def get_permissions_by_role(
cls,
session: AsyncSession,
role_id: int,
) -> Sequence[Permission]:
"""
获取指定角色的所有权限
使用 JOIN 查询直接从数据库获取,避免在异步上下文中触发懒加载。
:param session: 数据库会话对象
:param role_id: 角色ID
:return: 权限对象列表
:raises HTTPException: 当角色不存在时抛出404错误
"""
from ..model.relationship_table import RolePermission as RolePermTB
stmt = (
select(Permission)
.join(RolePermTB, RolePermTB.permission_id == Permission.id) # type: ignore
.where(RolePermTB.role_id == role_id)
)
result = await session.execute(stmt)
permissions = result.scalars().all()
if not permissions:
# 确认角色是否存在
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(status_code=404, detail="Role not found")
return permissions
# ---- TTL 缓存辅助方法 ----
@classmethod
def _get_user_perms_cache_key(cls, user_id: UUID) -> str:
"""生成用户权限缓存的键"""
return f"user_perms:{user_id}"
@classmethod
def _invalidate_user_perms_cache(cls, user_id: UUID):
"""
清除指定用户的权限缓存
:param user_id: 用户UUID
"""
cache = get_cache()
cache_key = cls._get_user_perms_cache_key(user_id)
cache.delete(cache_key)
# ---- 核心业务方法 ----
@classmethod
@acached(ttl=_USER_PERMS_CACHE_TTL, key_func=lambda cls, session, user_id: f"user_perms:{user_id}")
async def get_permissions_by_user(
cls,
session: AsyncSession,
user_id: UUID,
) -> List[Permission]:
"""
获取指定用户的所有权限(通过用户的所有角色汇总)
使用 @acached 装饰器自动缓存查询结果,缓存键为 user_perms:{user_id}。
TTL 120 秒作为兜底机制。当用户权限发生变更时,主动调用
_invalidate_user_perms_cache() 清除缓存。
:param session: 数据库会话对象
:param user_id: 用户UUID
:return: 去重后的权限对象列表
:raises HTTPException: 当用户不存在时抛出404错误
"""
# 通过 role_permission 和 user_role 关联表进行 JOIN 查询
from ..model.relationship_table import UserRole as UserRoleTB, RolePermission as RolePermTB
stmt = (
select(Permission)
.join(RolePermTB, RolePermTB.permission_id == Permission.id) # type: ignore
.join(Role, Role.id == RolePermTB.role_id) # type: ignore
.join(UserRoleTB, UserRoleTB.role_id == Role.id) # type: ignore
.where(UserRoleTB.user_id == user_id)
.distinct()
)
result = await session.execute(stmt)
permissions = result.scalars().all()
if not permissions:
# 确认用户是否存在
user = await UserService.get_user_by_id(user_id, session)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return list(permissions)
@classmethod @classmethod
async def assign_permission_to_role( async def assign_permission_to_role(
cls, session: AsyncSession, role_id: int, permission_id: int cls, session: AsyncSession, role_id: int, permission_id: int
@@ -80,7 +268,26 @@ class PermissionService:
:param role_id: 角色ID :param role_id: 角色ID
:param permission_id: 权限ID :param permission_id: 权限ID
:return: 创建的角色权限关联对象 :return: 创建的角色权限关联对象
:raises HTTPException: 当角色或权限不存在,或权限已分配给角色时抛出错误
""" """
role = await RoleService.get_role_by_id(session, role_id)
if not role:
raise HTTPException(status_code=404, detail="Role not found")
permission = await cls.get_permission_by_id(session, permission_id)
if not permission:
raise HTTPException(status_code=404, detail="Permission not found")
# 检查是否已经分配
existing = await session.execute(
select(RolePermission).where(
RolePermission.role_id == role_id,
RolePermission.permission_id == permission_id,
)
)
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Permission already assigned to role")
role_permission = RolePermission(role_id=role_id, permission_id=permission_id) role_permission = RolePermission(role_id=role_id, permission_id=permission_id)
session.add(role_permission) session.add(role_permission)
await session.commit() await session.commit()
@@ -133,6 +340,8 @@ class PermissionService:
user.roles.append(role) user.roles.append(role)
await session.commit() await session.commit()
await session.refresh(user) await session.refresh(user)
# 清除该用户的权限缓存,下次查询时重新加载
cls._invalidate_user_perms_cache(user_id)
return user return user
@classmethod @classmethod
@@ -142,6 +351,8 @@ class PermissionService:
""" """
从角色中解除权限绑定 从角色中解除权限绑定
直接通过 RolePermission 关联表删除记录,避免在异步上下文中触发懒加载。
:param session: 数据库会话对象 :param session: 数据库会话对象
:param role_id: 角色ID :param role_id: 角色ID
:param permission_id: 权限ID :param permission_id: 权限ID
@@ -153,8 +364,15 @@ class PermissionService:
if role is None: if role is None:
raise HTTPException(status_code=404, detail="Role not found") raise HTTPException(status_code=404, detail="Role not found")
# 从角色的权限列表中移除指定权限 # 通过关联表删除记录
role.permissions = [p for p in role.permissions if p.id != permission_id] from ..model.relationship_table import RolePermission as RolePermTB
from sqlmodel import delete
stmt = delete(RolePermTB).where(
RolePermTB.role_id == role_id, # type: ignore
RolePermTB.permission_id == permission_id, # type: ignore
)
await session.execute(stmt)
await session.commit() await session.commit()
await session.refresh(role) await session.refresh(role)
return role return role
@@ -166,7 +384,7 @@ class PermissionService:
""" """
从用户中解除权限绑定 从用户中解除权限绑定
通过找到用户的专属角色,从该角色中移除指定权限 通过找到用户的专属角色,从该角色中移除指定权限。
:param session: 数据库会话对象 :param session: 数据库会话对象
:param user_id: 用户UUID :param user_id: 用户UUID
@@ -183,11 +401,17 @@ class PermissionService:
if role is None: if role is None:
raise HTTPException(status_code=404, detail="Role not found") raise HTTPException(status_code=404, detail="Role not found")
# 从角色权限列表中移除指定权限 if role.id is not None:
role.permissions = [ # 直接通过关联表删除记录
permission from ..model.relationship_table import RolePermission as RolePermTB
for permission in role.permissions from sqlmodel import delete
if permission.id != permission_id
] stmt = delete(RolePermTB).where(
await session.commit() RolePermTB.role_id == role.id, # type: ignore
await session.refresh(role) RolePermTB.permission_id == permission_id, # type: ignore
)
await session.execute(stmt)
await session.commit()
await session.refresh(role)
# 清除该用户的权限缓存
cls._invalidate_user_perms_cache(user_id)
+48 -5
View File
@@ -1,4 +1,4 @@
from typing import Optional from typing import Optional, Sequence
from uuid import UUID from uuid import UUID
from fastapi import HTTPException from fastapi import HTTPException
@@ -89,6 +89,53 @@ class RoleService:
result = await session.get(Role, role_id) result = await session.get(Role, role_id)
return result return result
@classmethod
async def list_roles(
cls,
session: AsyncSession,
skip: int = 0,
limit: int = 100,
) -> Sequence[Role]:
"""
获取角色列表,支持分页
Args:
session: 数据库会话对象
skip: 跳过的记录数,用于分页
limit: 返回的最大记录数,用于分页
Returns:
Sequence[Role]: 角色对象列表
"""
query = select(Role).offset(skip).limit(limit)
result = await session.execute(query)
return result.scalars().all()
@classmethod
async def get_roles_by_user(
cls,
session: AsyncSession,
user_id: UUID,
) -> Sequence[Role]:
"""
获取指定用户的所有角色
Args:
session: 数据库会话对象
user_id: 用户UUID
Returns:
Sequence[Role]: 用户关联的角色对象列表
Raises:
HTTPException: 当用户不存在时抛出404错误
"""
user = await UserService.get_user_by_id(user_id, session)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user.roles
@staticmethod @staticmethod
async def unbind_role_from_user( async def unbind_role_from_user(
session: AsyncSession, session: AsyncSession,
@@ -103,9 +150,6 @@ class RoleService:
user_id: 用户UUID user_id: 用户UUID
role_id: 角色ID role_id: 角色ID
Returns:
User: 更新后的用户对象
Raises: Raises:
HTTPException: 当用户不存在时抛出404错误 HTTPException: 当用户不存在时抛出404错误
""" """
@@ -117,7 +161,6 @@ class RoleService:
user.roles = [role for role in user.roles if role.id != role_id] user.roles = [role for role in user.roles if role.id != role_id]
await session.commit() await session.commit()
await session.refresh(user) await session.refresh(user)
return user
@staticmethod @staticmethod
async def delete_role( async def delete_role(
+2 -1
View File
@@ -1,5 +1,6 @@
from typing import Optional from typing import Optional
from uuid import UUID from uuid import UUID
import time
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -60,7 +61,7 @@ class UserService:
IntegrityError: 当邮箱已存在时可能抛出完整性错误 IntegrityError: 当邮箱已存在时可能抛出完整性错误
""" """
hashed = get_password_hash(password) hashed = get_password_hash(password)
user = User(email=email, hashed_password=hashed, **kwargs) user = User(email=email, hashed_password=hashed, created_at=str(time.time() * 1000), **kwargs)
session.add(user) session.add(user)
await session.commit() await session.commit()
await session.refresh(user) await session.refresh(user)
+280 -31
View File
@@ -1,13 +1,69 @@
from sqlalchemy.orm import selectinload
from sqlmodel import select
from ..core.logger import get_logger from ..core.logger import get_logger
from ..core.constant import PermissionConstant from ..constant.permission import PermissionConstant
from ..service.user import UserService from ..service.user import UserService
from ..service.role import RoleService
from ..service.permission import PermissionService from ..service.permission import PermissionService
from ..model.role import Role
from ..model.user import User
from plugins.scheduler.scheduler import get_task_service_instance from plugins.scheduler.scheduler import get_task_service_instance
logger = get_logger("scheduler") logger = get_logger("scheduler")
# ──────────────────────────────────────────────
# 默认角色定义
# ──────────────────────────────────────────────
# 角色名称常量
ROLE_ADMIN = "admin" # 超级管理员 - 拥有所有权限
ROLE_MANAGER = "manager" # 经理 - 拥有业务模块完整操作权限
ROLE_OPERATOR = "operator" # 操作员 - 拥有业务模块基本操作权限
ROLE_VIEWER = "viewer" # 访客 - 仅有读取权限
async def init_permission(session):
"""
初始化系统权限数据
遍历所有预定义的权限常量,检查数据库中是否已存在对应权限记录。
对于不存在的权限,创建新的权限记录并关联对应的资源和操作类型,
同时使用 permission_description_dict 中的描述信息设置 description 字段。
Args:
session: 数据库会话对象,用于执行数据库操作
Returns:
None
Note:
- 权限格式为"resource:action",例如"user:create"
- 使用冒号分隔资源和操作,如果缺少操作部分则默认为空字符串
- 已存在的权限会被跳过,避免重复创建
- 每个新创建的权限都会记录到日志中
- 描述信息从 PermissionConstant.get_description_map() 获取
"""
description_map = PermissionConstant.get_description_map()
for permission in PermissionConstant.get_all():
parts = permission.split(":", 1)
resource = parts[0]
action = parts[1] if len(parts) > 1 else ""
existing_permission = await PermissionService.get_permission_by_name(
session, permission
)
if existing_permission:
logger.debug(f"权限已存在: {permission}")
continue
description = description_map.get(permission, None)
await PermissionService.create_permission(
session, permission, resource, action, description=description
)
logger.info(f"创建权限: {permission} - {description}")
async def dev_init_admin(session): async def dev_init_admin(session):
""" """
@@ -27,53 +83,242 @@ async def dev_init_admin(session):
- 如果管理员已存在,则跳过创建并记录日志 - 如果管理员已存在,则跳过创建并记录日志
- 新创建的用户具有超级管理员权限(is_superuser=True) - 新创建的用户具有超级管理员权限(is_superuser=True)
""" """
admin_user = await UserService.get_user_by_email("admin", session) # 先检查 admin@qq.com 邮箱是否已被使用
if admin_user: # type: ignore admin_user = await UserService.get_user_by_email("admin@qq.com", session)
logger.info("管理员用户已存在") # type: ignore if admin_user:
logger.info("管理员用户已存在")
return return
await UserService.create_user(session, "admin", "admin", is_superuser=True) # type: ignore # 再检查 "admin" 邮箱(兼容旧版初始化逻辑)
admin_user = await UserService.get_user_by_email("admin", session)
if admin_user:
logger.info("管理员用户已存在")
return
user = await UserService.create_user(session, "admin@qq.com", "admin", is_superuser=True)
admin_role = await RoleService.get_role_by_name(session, ROLE_ADMIN)
if admin_role is None or admin_role.id is None:
raise ValueError("管理员未创建")
await RoleService.assign_role_to_user(session, user.id, admin_role.id)
async def init_permission(session): def _get_admin_permissions() -> list:
""" """
初始化系统权限数据 获取管理员角色的所有权限 - 包含系统中定义的全部权限。
遍历所有预定义的权限常量,检查数据库中是否已存在对应权限记录。 Returns:
对于不存在的权限,创建新的权限记录并关联对应的资源和操作类型。 list[str]: 所有权限名称列表
"""
return PermissionConstant.get_all()
def _get_manager_permissions() -> list:
"""
获取经理角色的权限列表。
经理拥有所有业务模块的完整操作权限(创建、读取、更新、删除、审批),
但不包含系统管理权限(人员、角色、权限配置、系统配置、审计日志、备份恢复)。
Returns:
list[str]: 权限名称列表
"""
# 系统管理权限 - 经理不包含
exclude_prefixes = {
PermissionConstant.PREFIX_PERMISSION,
PermissionConstant.PREFIX_ROLE,
PermissionConstant.PREFIX_PERMISSION,
PermissionConstant.PREFIX_SYSTEM_CONFIG,
PermissionConstant.PREFIX_AUDIT_LOG,
PermissionConstant.PREFIX_BACKUP,
}
result = []
for p in PermissionConstant.get_all():
prefix = p.split(":")[0] if ":" in p else p
# 审计日志特殊处理
if prefix.startswith("report_"):
# 报表权限保留
result.append(p)
continue
if prefix not in exclude_prefixes:
result.append(p)
return result
def _get_operator_permissions() -> list:
"""
获取操作员角色的权限列表。
操作员拥有业务模块的基本操作权限(创建、读取、更新),
不包含删除、审批、导入导出等敏感操作,也不包含系统管理权限。
Returns:
list[str]: 权限名称列表
"""
# 操作员排除的操作类型
exclude_actions = {"delete", "approve", "cancel", "import", "export"}
# 操作员排除的资源前缀
exclude_prefixes = {
PermissionConstant.PREFIX_PERSONNEL,
PermissionConstant.PREFIX_ROLE,
PermissionConstant.PREFIX_PERMISSION,
PermissionConstant.PREFIX_SYSTEM_CONFIG,
PermissionConstant.PREFIX_AUDIT_LOG,
PermissionConstant.PREFIX_BACKUP,
}
result = []
for p in PermissionConstant.get_all():
prefix = p.split(":")[0] if ":" in p else p
action = p.split(":")[1] if ":" in p else ""
# 排除指定资源前缀
if prefix in exclude_prefixes:
continue
# 报表只保留查看
if prefix.startswith("report_") or prefix.startswith("dashboard"):
if action == "view":
result.append(p)
continue
# 排除敏感操作
if action in exclude_actions:
continue
result.append(p)
return result
def _get_viewer_permissions() -> list:
"""
获取访客角色的权限列表。
访客只有最基本的读取和查看权限,没有任何写入、修改或删除权限。
Returns:
list[str]: 权限名称列表
"""
read_actions = {"read", "view"}
read_prefixes = {
PermissionConstant.PREFIX_TODO
}
result = []
for p in PermissionConstant.get_all():
prefix = p.split(":")[0] if ":" in p else p
action = p.split(":")[1] if ":" in p else ""
# 只读取指定资源的 read/view 权限
if prefix in read_prefixes and action in read_actions:
result.append(p)
return result
# 角色-权限映射表
ROLE_PERMISSION_MAP = {
ROLE_ADMIN: _get_admin_permissions,
ROLE_MANAGER: _get_manager_permissions,
ROLE_OPERATOR: _get_operator_permissions,
ROLE_VIEWER: _get_viewer_permissions,
}
ROLE_DESCRIPTION_MAP = {
ROLE_ADMIN: "超级管理员,拥有系统所有权限",
ROLE_MANAGER: "业务经理,拥有所有业务模块的完整操作权限",
ROLE_OPERATOR: "操作员,拥有业务模块的基本操作权限",
ROLE_VIEWER: "访客,仅有只读访问权限",
}
async def init_default_roles(session):
"""
初始化默认角色及权限关联
创建系统预定义的四个默认角色(admin、manager、operator、viewer),
并为每个角色分配对应的权限集。如果角色已存在则跳过创建,但会补充缺失的权限关联。
Args: Args:
session: 数据库会话对象,用于执行数据库操作 session: 数据库会话对象
Returns: Returns:
None None
Note:
- 权限格式为"resource:action",例如"user:create"
- 使用冒号分隔资源和操作,如果缺少操作部分则默认为空字符串
- 已存在的权限会被跳过,避免重复创建
- 每个新创建的权限都会记录到日志中
""" """
for permission in PermissionConstant.get_all(): for role_name, get_permissions_fn in ROLE_PERMISSION_MAP.items():
parts = permission.split(":", 1) # 检查角色是否已存在(主动加载 permissions 关系,避免异步懒加载问题)
resource = parts[0] result = await session.execute(
action = parts[1] if len(parts) > 1 else "" select(Role)
.where(Role.name == role_name)
existing_permission = await PermissionService.get_permission_by_name( .options(selectinload(Role.permissions)) # type: ignore[arg-type]
session, permission
) )
if existing_permission: existing_role = result.scalar_one_or_none()
logger.debug(f"权限已存在: {permission}")
continue
await PermissionService.create_permission(session, permission, resource, action) if existing_role:
logger.info(f"创建权限: {permission}") logger.info(f"角色已存在: {role_name}")
role = existing_role
else:
# 创建角色
description = ROLE_DESCRIPTION_MAP.get(role_name, "")
role = await RoleService.create_role(
session, role_name, description=description
)
# 创建后重新查询以加载 permissions 关系
result = await session.execute(
select(Role)
.where(Role.name == role_name)
.options(selectinload(Role.permissions)) # type: ignore[arg-type]
)
role = result.scalar_one_or_none()
logger.info(f"创建角色: {role_name}")
# 获取该角色应有的权限名称列表
expected_permission_names = get_permissions_fn()
# 获取角色当前已关联的权限名称集合
existing_permission_names = {
p.name for p in role.permissions
} if role else set()
# 分配缺失的权限
for perm_name in expected_permission_names:
if perm_name in existing_permission_names:
continue
permission = await PermissionService.get_permission_by_name(
session, perm_name
)
if permission is None:
logger.warning(f"权限不存在,跳过: {perm_name}")
continue
if role.id is not None and permission.id is not None:
await PermissionService.assign_permission_to_role(
session, role.id, permission.id
)
logger.debug(f"为角色 '{role_name}' 分配权限: {perm_name}")
# 将 admin 角色分配给超级管理员用户(主动加载 roles 关系)
result = await session.execute(
select(User).where(User.email == "admin@qq.com").options(selectinload(User.roles)) # type: ignore[arg-type]
)
admin_user = result.scalar_one_or_none()
admin_role = await RoleService.get_role_by_name(session, ROLE_ADMIN)
if admin_user and admin_role and admin_role.id is not None:
# 检查是否已关联
already_assigned = any(
r.id == admin_role.id for r in admin_user.roles
)
if not already_assigned:
await RoleService.assign_role_to_user(
session, admin_user.id, admin_role.id
)
logger.info("为 admin 用户分配 admin 角色")
async def init_db(): async def init_db():
""" """
执行数据库初始化流程 执行数据库初始化流程
协调执行所有数据库初始化任务,包括管理员账户创建和权限数据初始化。 协调执行所有数据库初始化任务,包括管理员账户创建、权限数据初始化和默认角色初始化。
该函数是系统启动时数据库初始化的入口点。 该函数是系统启动时数据库初始化的入口点。
Returns: Returns:
@@ -82,12 +327,16 @@ async def init_db():
Note: Note:
- 通过任务服务获取异步会话工厂 - 通过任务服务获取异步会话工厂
- 如果会话工厂未初始化,则直接返回不执行任何操作 - 如果会话工厂未初始化,则直接返回不执行任何操作
- 按顺序执行:先初始化管理员,再初始化权限 - 按顺序执行:先初始化权限,再初始化角色,最后初始化管理员
- 使用异步上下文管理器确保会话正确关闭 - 使用异步上下文管理器确保会话正确关闭
""" """
task_service = get_task_service_instance() task_service = get_task_service_instance()
if task_service.async_session_maker is None: if task_service.async_session_maker is None:
return return
async with task_service.async_session_maker() as session: async with task_service.async_session_maker() as session:
await dev_init_admin(session) # 先初始化权限数据(角色依赖权限存在)
await init_permission(session) await init_permission(session)
# 再初始化默认角色及权限关联
await init_default_roles(session)
# 最后初始化管理员用户
await dev_init_admin(session)
+3 -3
View File
@@ -1,11 +1,11 @@
import json import json
import threading
import hashlib
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List from typing import Optional, Dict, Any, List
from pydantic import BaseModel
from dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
import hashlib
import threading
from pydantic import BaseModel
import jwt import jwt