diff --git a/Dockerfile b/Dockerfile index bbab988..141cb78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ - 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/* COPY build/chongming /tmp/chongming diff --git a/src/chongming-web/src/api/generated/index.ts b/src/chongming-web/src/api/generated/index.ts index b2e5c2a..13ce1c8 100644 --- a/src/chongming-web/src/api/generated/index.ts +++ b/src/chongming-web/src/api/generated/index.ts @@ -1,4 +1,4 @@ // This file is auto-generated by @hey-api/openapi-ts -export { createTodoApiV1TodosPost, deleteTodoApiV1TodosTodoIdDelete, getCurrentUserInfoApiV1AuthMeGet, getSessionsApiV1AuthSessionsGet, loginApiV1AuthLoginPost, logoutAllApiV1AuthLogoutAllPost, logoutApiV1AuthLogoutPost, type Options, readTodoApiV1TodosTodoIdGet, readTodosApiV1TodosGet, refreshTokenApiV1AuthRefreshPost, registerApiV1AuthRegisterPost, rootGet, updateTodoApiV1TodosTodoIdPatch, uploadImageApiV1UploadImagePost } from './sdk.gen'; -export type { BodyUploadImageApiV1UploadImagePost, ClientOptions, CreateTodoApiV1TodosPostData, CreateTodoApiV1TodosPostError, CreateTodoApiV1TodosPostErrors, CreateTodoApiV1TodosPostResponse, CreateTodoApiV1TodosPostResponses, DeleteTodoApiV1TodosTodoIdDeleteData, DeleteTodoApiV1TodosTodoIdDeleteError, DeleteTodoApiV1TodosTodoIdDeleteErrors, DeleteTodoApiV1TodosTodoIdDeleteResponses, Detail, GetCurrentUserInfoApiV1AuthMeGetData, GetCurrentUserInfoApiV1AuthMeGetError, GetCurrentUserInfoApiV1AuthMeGetErrors, GetCurrentUserInfoApiV1AuthMeGetResponse, GetCurrentUserInfoApiV1AuthMeGetResponses, GetSessionsApiV1AuthSessionsGetData, GetSessionsApiV1AuthSessionsGetResponses, HttpError, HttpValidationError, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponse, LoginApiV1AuthLoginPostResponses, LogoutAllApiV1AuthLogoutAllPostData, LogoutAllApiV1AuthLogoutAllPostResponse, LogoutAllApiV1AuthLogoutAllPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponse, LogoutApiV1AuthLogoutPostResponses, ReadTodoApiV1TodosTodoIdGetData, ReadTodoApiV1TodosTodoIdGetError, ReadTodoApiV1TodosTodoIdGetErrors, ReadTodoApiV1TodosTodoIdGetResponse, ReadTodoApiV1TodosTodoIdGetResponses, ReadTodosApiV1TodosGetData, ReadTodosApiV1TodosGetError, ReadTodosApiV1TodosGetErrors, ReadTodosApiV1TodosGetResponse, ReadTodosApiV1TodosGetResponses, RefreshTokenApiV1AuthRefreshPostData, RefreshTokenApiV1AuthRefreshPostError, RefreshTokenApiV1AuthRefreshPostErrors, RefreshTokenApiV1AuthRefreshPostResponse, RefreshTokenApiV1AuthRefreshPostResponses, RefreshTokenResponse, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostError, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponse, RegisterApiV1AuthRegisterPostResponses, RootGetData, RootGetResponses, TodoCreate, TodoRead, TodoUpdate, TokenResponse, UpdateTodoApiV1TodosTodoIdPatchData, UpdateTodoApiV1TodosTodoIdPatchError, UpdateTodoApiV1TodosTodoIdPatchErrors, UpdateTodoApiV1TodosTodoIdPatchResponse, UpdateTodoApiV1TodosTodoIdPatchResponses, UploadImageApiV1UploadImagePostData, UploadImageApiV1UploadImagePostError, UploadImageApiV1UploadImagePostErrors, UploadImageApiV1UploadImagePostResponse, UploadImageApiV1UploadImagePostResponses, UploadResponse, UserCreate, UserLogin, Userlogout, UserRead, ValidationError } from './types.gen'; +export { assignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPost, assignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPost, assignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPost, assignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPost, createPermissionApiV1PermissionsPost, createRoleApiV1RolesPost, createTodoApiV1TodosPost, deletePermissionApiV1PermissionsPermissionIdDelete, deleteRoleApiV1RolesRoleIdDelete, deleteTodoApiV1TodosTodoIdDelete, getCurrentUserInfoApiV1AuthMeGet, getPermissionApiV1PermissionsPermissionIdGet, getRoleApiV1RolesRoleIdGet, getRolePermissionsApiV1RolesRoleIdPermissionsGet, getSessionsApiV1AuthSessionsGet, listPermissionsApiV1PermissionsGet, listRolesApiV1RolesGet, loginApiV1AuthLoginPost, logoutAllApiV1AuthLogoutAllPost, logoutApiV1AuthLogoutPost, type Options, readTodoApiV1TodosTodoIdGet, readTodosApiV1TodosGet, refreshTokenApiV1AuthRefreshPost, registerApiV1AuthRegisterPost, rootGet, unbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPost, unbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPost, unbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPost, unbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPost, updatePermissionApiV1PermissionsPermissionIdPut, updateRoleApiV1RolesRoleIdPut, updateTodoApiV1TodosTodoIdPatch, uploadImageApiV1UploadImagePost } from './sdk.gen'; +export type { AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostData, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostError, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors, AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostResponses, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostData, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostError, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors, AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostResponses, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostData, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostError, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors, AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostResponses, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostData, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostError, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors, AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostResponses, BodyUploadImageApiV1UploadImagePost, ClientOptions, CreatePermissionApiV1PermissionsPostData, CreatePermissionApiV1PermissionsPostError, CreatePermissionApiV1PermissionsPostErrors, CreatePermissionApiV1PermissionsPostResponse, CreatePermissionApiV1PermissionsPostResponses, CreateRoleApiV1RolesPostData, CreateRoleApiV1RolesPostError, CreateRoleApiV1RolesPostErrors, CreateRoleApiV1RolesPostResponse, CreateRoleApiV1RolesPostResponses, CreateTodoApiV1TodosPostData, CreateTodoApiV1TodosPostError, CreateTodoApiV1TodosPostErrors, CreateTodoApiV1TodosPostResponse, CreateTodoApiV1TodosPostResponses, DeletePermissionApiV1PermissionsPermissionIdDeleteData, DeletePermissionApiV1PermissionsPermissionIdDeleteError, DeletePermissionApiV1PermissionsPermissionIdDeleteErrors, DeletePermissionApiV1PermissionsPermissionIdDeleteResponses, DeleteRoleApiV1RolesRoleIdDeleteData, DeleteRoleApiV1RolesRoleIdDeleteError, DeleteRoleApiV1RolesRoleIdDeleteErrors, DeleteRoleApiV1RolesRoleIdDeleteResponses, DeleteTodoApiV1TodosTodoIdDeleteData, DeleteTodoApiV1TodosTodoIdDeleteError, DeleteTodoApiV1TodosTodoIdDeleteErrors, DeleteTodoApiV1TodosTodoIdDeleteResponses, Detail, GetCurrentUserInfoApiV1AuthMeGetData, GetCurrentUserInfoApiV1AuthMeGetError, GetCurrentUserInfoApiV1AuthMeGetErrors, GetCurrentUserInfoApiV1AuthMeGetResponse, GetCurrentUserInfoApiV1AuthMeGetResponses, GetPermissionApiV1PermissionsPermissionIdGetData, GetPermissionApiV1PermissionsPermissionIdGetError, GetPermissionApiV1PermissionsPermissionIdGetErrors, GetPermissionApiV1PermissionsPermissionIdGetResponse, GetPermissionApiV1PermissionsPermissionIdGetResponses, GetRoleApiV1RolesRoleIdGetData, GetRoleApiV1RolesRoleIdGetError, GetRoleApiV1RolesRoleIdGetErrors, GetRoleApiV1RolesRoleIdGetResponse, GetRoleApiV1RolesRoleIdGetResponses, GetRolePermissionsApiV1RolesRoleIdPermissionsGetData, GetRolePermissionsApiV1RolesRoleIdPermissionsGetError, GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors, GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponse, GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses, GetSessionsApiV1AuthSessionsGetData, GetSessionsApiV1AuthSessionsGetResponses, HttpError, HttpValidationError, ListPermissionsApiV1PermissionsGetData, ListPermissionsApiV1PermissionsGetError, ListPermissionsApiV1PermissionsGetErrors, ListPermissionsApiV1PermissionsGetResponse, ListPermissionsApiV1PermissionsGetResponses, ListRolesApiV1RolesGetData, ListRolesApiV1RolesGetError, ListRolesApiV1RolesGetErrors, ListRolesApiV1RolesGetResponse, ListRolesApiV1RolesGetResponses, LoginApiV1AuthLoginPostData, LoginApiV1AuthLoginPostError, LoginApiV1AuthLoginPostErrors, LoginApiV1AuthLoginPostResponse, LoginApiV1AuthLoginPostResponses, LogoutAllApiV1AuthLogoutAllPostData, LogoutAllApiV1AuthLogoutAllPostResponse, LogoutAllApiV1AuthLogoutAllPostResponses, LogoutApiV1AuthLogoutPostData, LogoutApiV1AuthLogoutPostResponse, LogoutApiV1AuthLogoutPostResponses, Permission, PermissionCreate, PermissionUpdate, ReadTodoApiV1TodosTodoIdGetData, ReadTodoApiV1TodosTodoIdGetError, ReadTodoApiV1TodosTodoIdGetErrors, ReadTodoApiV1TodosTodoIdGetResponse, ReadTodoApiV1TodosTodoIdGetResponses, ReadTodosApiV1TodosGetData, ReadTodosApiV1TodosGetError, ReadTodosApiV1TodosGetErrors, ReadTodosApiV1TodosGetResponse, ReadTodosApiV1TodosGetResponses, RefreshTokenApiV1AuthRefreshPostData, RefreshTokenApiV1AuthRefreshPostError, RefreshTokenApiV1AuthRefreshPostErrors, RefreshTokenApiV1AuthRefreshPostResponse, RefreshTokenApiV1AuthRefreshPostResponses, RefreshTokenResponse, RegisterApiV1AuthRegisterPostData, RegisterApiV1AuthRegisterPostError, RegisterApiV1AuthRegisterPostErrors, RegisterApiV1AuthRegisterPostResponse, RegisterApiV1AuthRegisterPostResponses, Role, RoleUpdate, RootGetData, RootGetResponses, TodoCreate, TodoRead, TodoUpdate, TokenResponse, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostData, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostError, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors, UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostResponses, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostData, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostError, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors, UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostResponses, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostData, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostError, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors, UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostResponses, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostData, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostError, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors, UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostResponses, UpdatePermissionApiV1PermissionsPermissionIdPutData, UpdatePermissionApiV1PermissionsPermissionIdPutError, UpdatePermissionApiV1PermissionsPermissionIdPutErrors, UpdatePermissionApiV1PermissionsPermissionIdPutResponse, UpdatePermissionApiV1PermissionsPermissionIdPutResponses, UpdateRoleApiV1RolesRoleIdPutData, UpdateRoleApiV1RolesRoleIdPutError, UpdateRoleApiV1RolesRoleIdPutErrors, UpdateRoleApiV1RolesRoleIdPutResponse, UpdateRoleApiV1RolesRoleIdPutResponses, UpdateTodoApiV1TodosTodoIdPatchData, UpdateTodoApiV1TodosTodoIdPatchError, UpdateTodoApiV1TodosTodoIdPatchErrors, UpdateTodoApiV1TodosTodoIdPatchResponse, UpdateTodoApiV1TodosTodoIdPatchResponses, UploadImageApiV1UploadImagePostData, UploadImageApiV1UploadImagePostError, UploadImageApiV1UploadImagePostErrors, UploadImageApiV1UploadImagePostResponse, UploadImageApiV1UploadImagePostResponses, UploadResponse, UserCreate, UserLogin, Userlogout, UserRead, ValidationError } from './types.gen'; diff --git a/src/chongming-web/src/api/generated/sdk.gen.ts b/src/chongming-web/src/api/generated/sdk.gen.ts index 863f1ed..4f54ea1 100644 --- a/src/chongming-web/src/api/generated/sdk.gen.ts +++ b/src/chongming-web/src/api/generated/sdk.gen.ts @@ -2,7 +2,7 @@ import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client'; 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 = Options2 & { /** @@ -20,6 +20,16 @@ export type Options(options?: Options) => (options?.client ?? client).get({ security: [{ scheme: 'bearer', type: 'http' }], @@ -29,6 +39,15 @@ export const readTodosApiV1TodosGet = (opt /** * Create Todo + * + * 创建新的待办事项 + * + * Args: + * todo: 待办事项创建请求体,包含待办事项的详细信息 + * session: 数据库会话对象,用于执行数据库操作 + * + * Returns: + * TodoRead: 创建成功的待办事项对象,包含完整的待办事项信息 */ export const createTodoApiV1TodosPost = (options: Options) => (options.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], @@ -42,6 +61,18 @@ export const createTodoApiV1TodosPost = (o /** * Delete Todo + * + * 删除待办事项 + * + * Args: + * todo_id: 待办事项的唯一标识符 + * session: 数据库会话对象,用于执行数据库操作 + * + * Returns: + * dict: 包含操作结果的字典,{"ok": True} 表示删除成功 + * + * Raises: + * HTTPException: 当待办事项不存在时抛出404错误 */ export const deleteTodoApiV1TodosTodoIdDelete = (options: Options) => (options.client ?? client).delete({ security: [{ scheme: 'bearer', type: 'http' }], @@ -51,6 +82,18 @@ export const deleteTodoApiV1TodosTodoIdDelete = (options: Options) => (options.client ?? client).get({ security: [{ scheme: 'bearer', type: 'http' }], @@ -60,6 +103,16 @@ export const readTodoApiV1TodosTodoIdGet = (options: Options) => (options.client ?? client).patch({ security: [{ scheme: 'bearer', type: 'http' }], @@ -73,6 +126,20 @@ export const updateTodoApiV1TodosTodoIdPatch = (options: Options) => (options.client ?? client).post({ url: '/api/v1/auth/register', @@ -85,6 +152,24 @@ export const registerApiV1AuthRegisterPost = (options: Options) => (options.client ?? client).post({ url: '/api/v1/auth/login', @@ -97,6 +182,21 @@ export const loginApiV1AuthLoginPost = (op /** * 刷新访问令牌 + * + * 刷新访问令牌接口 + * + * 使用有效的刷新令牌获取新的访问令牌。验证刷新令牌的有效性,如果有效则生成新的访问令牌。 + * + * Args: + * request: FastAPI请求对象,用于获取客户端信息 + * credentials: HTTP认证凭证,从请求头中提取的刷新令牌 + * session: 数据库会话对象,用于验证令牌状态 + * + * Returns: + * RefreshTokenResponse: 包含新的访问令牌(access_token)和令牌类型(token_type)的响应对象 + * + * Raises: + * HTTPException: 当刷新令牌无效或已过期时抛出401错误 */ export const refreshTokenApiV1AuthRefreshPost = (options?: Options) => (options?.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], @@ -106,6 +206,16 @@ export const refreshTokenApiV1AuthRefreshPost = (options?: Options) => (options?.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], @@ -115,6 +225,16 @@ export const logoutApiV1AuthLogoutPost = ( /** * 登出所有设备 + * + * 登出所有设备接口 + * + * 使指定用户的所有活跃会话令牌失效,实现多设备同时登出功能。 + * + * Args: + * current_user: 当前登录用户的令牌数据,包含用户ID等信息 + * + * Returns: + * Userlogout: 登出成功响应,包含成功消息和状态码200 */ export const logoutAllApiV1AuthLogoutAllPost = (options?: Options) => (options?.client ?? client).post({ security: [{ scheme: 'bearer', type: 'http' }], @@ -125,7 +245,15 @@ export const logoutAllApiV1AuthLogoutAllPost = (options?: Options) => (options?.client ?? client).get({ security: [{ scheme: 'bearer', type: 'http' }], @@ -136,7 +264,19 @@ export const getSessionsApiV1AuthSessionsGet = (options?: Options) => (options?.client ?? client).get({ security: [{ scheme: 'bearer', type: 'http' }], @@ -146,6 +286,30 @@ export const getCurrentUserInfoApiV1AuthMeGet = (options: Options) => (options.client ?? client).post({ ...formDataBodySerializer, @@ -158,9 +322,484 @@ export const uploadImageApiV1UploadImagePost = (options?: Options) => (options?.client ?? client).get({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).delete({ + 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 = (options: Options) => (options.client ?? client).get({ + 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 = (options: Options) => (options.client ?? client).put({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options?: Options) => (options?.client ?? client).get({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).delete({ + 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 = (options: Options) => (options.client ?? client).get({ + 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 = (options: Options) => (options.client ?? client).put({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).get({ + 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 = (options: Options) => (options.client ?? client).post({ + 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 = (options: Options) => (options.client ?? client).post({ + security: [{ scheme: 'bearer', type: 'http' }], + url: '/api/v1/roles/{role_id}/unbind-permission/{permission_id}', + ...options +}); + /** * 根路径 * - * 根路径,返回应用信息 + * 根路径端点,返回应用的基本信息。 + * + * 返回: + * dict: 包含应用名称、版本、文档地址和健康检查地址的字典 */ export const rootGet = (options?: Options) => (options?.client ?? client).get({ url: '/', ...options }); diff --git a/src/chongming-web/src/api/generated/types.gen.ts b/src/chongming-web/src/api/generated/types.gen.ts index 209a857..e63891a 100644 --- a/src/chongming-web/src/api/generated/types.gen.ts +++ b/src/chongming-web/src/api/generated/types.gen.ts @@ -16,6 +16,14 @@ export type BodyUploadImageApiV1UploadImagePost = { /** * Detail + * + * HTTP错误详情模型 + * + * 用于封装HTTP错误的详细信息,包括错误消息和错误代码。 + * + * Attributes: + * message (str): 错误描述信息 + * code (int): 错误状态码或业务错误码 */ export type Detail = { /** @@ -30,6 +38,14 @@ export type Detail = { /** * HTTPError + * + * HTTP错误响应模型 + * + * 用于标准化HTTP错误响应的数据结构,符合RESTful API规范。 + * + * Attributes: + * detail (Optional[Detail]): 错误详情对象,包含具体的错误信息和错误码。 + * 如果为None,表示没有详细的错误信息。 */ export type HttpError = { detail?: Detail | null; @@ -45,10 +61,114 @@ export type HttpValidationError = { detail?: Array; }; +/** + * Permission + * + * 权限模型类,用于定义系统中的权限项。 + * + * 权限采用资源:操作的格式进行标识,例如: + * - user:create 表示创建用户的权限 + * - order:delete 表示删除订单的权限 + * + * Attributes: + * id: 权限的唯一标识符,主键,自增 + * name: 权限标识符,唯一且建立索引,格式为"资源:操作" + * description: 权限的描述信息,可选字段 + * resource: 资源名称,建立索引,如 user、order、product 等 + * action: 操作类型,如 create、read、update、delete 等 + * roles: 关联的角色列表,通过 RolePermission 中间表实现多对多关系 + */ +export type Permission = { + /** + * Id + */ + id?: number | null; + /** + * Name + */ + name: string; + /** + * Description + */ + description?: string | null; + /** + * Resource + */ + resource: string; + /** + * Action + */ + action: string; +}; + +/** + * PermissionCreate + * + * 权限创建请求模型类,用于接收权限创建时的参数。 + * + * Attributes: + * name: 权限名称,格式为"资源:操作" + * description: 权限描述信息,可选字段 + * resource: 资源名称,如 user、order、product 等 + * action: 操作类型,如 create、read、update、delete 等 + */ +export type PermissionCreate = { + /** + * Name + */ + name: string; + /** + * Description + */ + description?: string | null; + /** + * Resource + */ + resource: string; + /** + * Action + */ + action: string; +}; + +/** + * PermissionUpdate + * + * 权限更新请求模型类,用于接收权限更新时的参数。 + * + * 所有字段均为可选,允许部分更新权限信息。 + * + * Attributes: + * name: 更新后的权限名称,可选字段 + * description: 更新后的权限描述信息,可选字段 + * resource: 更新后的资源名称,可选字段 + * action: 更新后的操作类型,可选字段 + */ +export type PermissionUpdate = { + /** + * Name + */ + name?: string | null; + /** + * Description + */ + description?: string | null; + /** + * Resource + */ + resource?: string | null; + /** + * Action + */ + action?: string | null; +}; + /** * RefreshTokenResponse * * 刷新 Token 响应模型 + * + * 用于刷新 token 接口,仅返回新的 access token */ export type RefreshTokenResponse = { /** @@ -61,8 +181,70 @@ export type RefreshTokenResponse = { token_type?: string; }; +/** + * Role + * + * 角色模型类,用于定义系统中的用户角色。 + * + * 该类继承自SQLModel,映射到数据库中的角色表。每个角色可以有多个用户和多个权限, + * 通过中间表UserRole和RolePermission实现多对多关系。 + * + * Attributes: + * id: 角色的唯一标识符,主键,自增 + * name: 角色名称,具有唯一性约束和索引,例如:admin, manager, operator, viewer + * description: 角色的描述信息,可选字段 + * is_only_user: 标识该角色是否仅限单个用户使用,默认为False + * users: 与该角色关联的用户列表,通过UserRole中间表建立多对多关系 + * permissions: 与该角色关联的权限列表,通过RolePermission中间表建立多对多关系 + */ +export type Role = { + /** + * Id + */ + id?: number | null; + /** + * Name + */ + name: string; + /** + * Description + */ + description?: string | null; + /** + * Is Only User + */ + is_only_user?: boolean; +}; + +/** + * RoleUpdate + * + * 角色更新请求模型类,用于接收角色更新时的参数。 + * + * 该类继承自Pydantic的BaseModel,所有字段均为可选,允许部分更新角色信息。 + * + * Attributes: + * name: 更新后的角色名称,可选字段 + * description: 更新后的角色描述信息,可选字段 + */ +export type RoleUpdate = { + /** + * Name + */ + name?: string | null; + /** + * Description + */ + description?: string | null; +}; + /** * TodoCreate + * + * 创建Todo任务时使用的请求模型类。 + * + * 继承自TodoBase,用于接收前端传入的新建任务数据。 + * 不包含id字段,因为id由数据库自动生成。 */ export type TodoCreate = { /** @@ -81,6 +263,14 @@ export type TodoCreate = { /** * TodoRead + * + * 读取Todo任务时使用的响应模型类。 + * + * 继承自TodoBase,用于向客户端返回任务数据。 + * 包含完整的任务信息,包括自动生成的id字段。 + * + * Attributes: + * id: 任务唯一标识符,整数类型,必填字段 */ export type TodoRead = { /** @@ -103,6 +293,16 @@ export type TodoRead = { /** * TodoUpdate + * + * 更新Todo任务时使用的请求模型类。 + * + * 所有字段均为可选,允许部分更新任务信息。 + * 只有提供的字段会被更新,未提供的字段保持原值。 + * + * Attributes: + * title: 任务标题,可选字段 + * description: 任务描述,可选字段 + * completed: 任务完成状态,可选字段 */ export type TodoUpdate = { /** @@ -123,6 +323,8 @@ export type TodoUpdate = { * TokenResponse * * Token 响应模型 + * + * 用于登录接口返回 access token 和 refresh token */ export type TokenResponse = { /** @@ -149,6 +351,8 @@ export type TokenResponse = { /** * UploadResponse + * + * 图片上传响应数据模型 */ export type UploadResponse = { /** @@ -168,7 +372,13 @@ export type UploadResponse = { /** * UserCreate * - * 用户创建模型 + * 用户创建请求模型 + * + * 用于创建新用户时的数据验证和传输。 + * 在UserBase基础上增加了密码字段。 + * + * Attributes: + * password: 用户明文密码,用于创建时传入 */ export type UserCreate = { /** @@ -203,6 +413,14 @@ export type UserCreate = { /** * UserLogin + * + * 用户登录请求模型 + * + * 用于处理用户登录时的数据验证。 + * + * Attributes: + * email: 用户邮箱地址 + * password: 用户密码 */ export type UserLogin = { /** @@ -218,7 +436,13 @@ export type UserLogin = { /** * UserRead * - * 用户读取模型 + * 用户读取响应模型 + * + * 用于向客户端返回用户信息。 + * 在UserBase基础上增加了用户ID字段。 + * + * Attributes: + * id: 用户唯一标识符,UUID类型 */ export type UserRead = { /** @@ -253,6 +477,14 @@ export type UserRead = { /** * Userlogout + * + * 用户登出响应模型 + * + * 用于返回登出操作的结果。 + * + * Attributes: + * code: 响应状态码,默认为200表示成功 + * message: 响应消息,默认为"logout success" */ export type Userlogout = { /** @@ -629,6 +861,689 @@ export type UploadImageApiV1UploadImagePostResponses = { export type UploadImageApiV1UploadImagePostResponse = UploadImageApiV1UploadImagePostResponses[keyof UploadImageApiV1UploadImagePostResponses]; +export type ListPermissionsApiV1PermissionsGetData = { + body?: never; + path?: never; + query?: { + /** + * Skip + */ + skip?: number; + /** + * Limit + */ + limit?: number; + /** + * Resource + */ + resource?: string | null; + }; + url: '/api/v1/permissions'; +}; + +export type ListPermissionsApiV1PermissionsGetErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type ListPermissionsApiV1PermissionsGetError = ListPermissionsApiV1PermissionsGetErrors[keyof ListPermissionsApiV1PermissionsGetErrors]; + +export type ListPermissionsApiV1PermissionsGetResponses = { + /** + * Response List Permissions Api V1 Permissions Get + * + * Successful Response + */ + 200: Array; +}; + +export type ListPermissionsApiV1PermissionsGetResponse = ListPermissionsApiV1PermissionsGetResponses[keyof ListPermissionsApiV1PermissionsGetResponses]; + +export type CreatePermissionApiV1PermissionsPostData = { + body: PermissionCreate; + path?: never; + query?: never; + url: '/api/v1/permissions'; +}; + +export type CreatePermissionApiV1PermissionsPostErrors = { + /** + * 权限名称已存在 + */ + 400: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type CreatePermissionApiV1PermissionsPostError = CreatePermissionApiV1PermissionsPostErrors[keyof CreatePermissionApiV1PermissionsPostErrors]; + +export type CreatePermissionApiV1PermissionsPostResponses = { + /** + * Successful Response + */ + 200: Permission; +}; + +export type CreatePermissionApiV1PermissionsPostResponse = CreatePermissionApiV1PermissionsPostResponses[keyof CreatePermissionApiV1PermissionsPostResponses]; + +export type DeletePermissionApiV1PermissionsPermissionIdDeleteData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}'; +}; + +export type DeletePermissionApiV1PermissionsPermissionIdDeleteErrors = { + /** + * 权限已被角色使用,无法删除 + */ + 400: HttpError; + /** + * 权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type DeletePermissionApiV1PermissionsPermissionIdDeleteError = DeletePermissionApiV1PermissionsPermissionIdDeleteErrors[keyof DeletePermissionApiV1PermissionsPermissionIdDeleteErrors]; + +export type DeletePermissionApiV1PermissionsPermissionIdDeleteResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type GetPermissionApiV1PermissionsPermissionIdGetData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}'; +}; + +export type GetPermissionApiV1PermissionsPermissionIdGetErrors = { + /** + * 权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetPermissionApiV1PermissionsPermissionIdGetError = GetPermissionApiV1PermissionsPermissionIdGetErrors[keyof GetPermissionApiV1PermissionsPermissionIdGetErrors]; + +export type GetPermissionApiV1PermissionsPermissionIdGetResponses = { + /** + * Successful Response + */ + 200: Permission; +}; + +export type GetPermissionApiV1PermissionsPermissionIdGetResponse = GetPermissionApiV1PermissionsPermissionIdGetResponses[keyof GetPermissionApiV1PermissionsPermissionIdGetResponses]; + +export type UpdatePermissionApiV1PermissionsPermissionIdPutData = { + body: PermissionUpdate; + path: { + /** + * Permission Id + */ + permission_id: number; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}'; +}; + +export type UpdatePermissionApiV1PermissionsPermissionIdPutErrors = { + /** + * 权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdatePermissionApiV1PermissionsPermissionIdPutError = UpdatePermissionApiV1PermissionsPermissionIdPutErrors[keyof UpdatePermissionApiV1PermissionsPermissionIdPutErrors]; + +export type UpdatePermissionApiV1PermissionsPermissionIdPutResponses = { + /** + * Successful Response + */ + 200: Permission; +}; + +export type UpdatePermissionApiV1PermissionsPermissionIdPutResponse = UpdatePermissionApiV1PermissionsPermissionIdPutResponses[keyof UpdatePermissionApiV1PermissionsPermissionIdPutResponses]; + +export type AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}/assign-to-role/{role_id}'; +}; + +export type AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors = { + /** + * 权限已分配给角色 + */ + 400: HttpError; + /** + * 角色或权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostError = AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors[keyof AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostErrors]; + +export type AssignPermissionToRoleApiV1PermissionsPermissionIdAssignToRoleRoleIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}/unbind-from-role/{role_id}'; +}; + +export type UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors = { + /** + * 角色不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostError = UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors[keyof UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostErrors]; + +export type UnbindPermissionFromRoleApiV1PermissionsPermissionIdUnbindFromRoleRoleIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + /** + * User Id + */ + user_id: string; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}/assign-to-user/{user_id}'; +}; + +export type AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors = { + /** + * 用户或权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostError = AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors[keyof AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostErrors]; + +export type AssignPermissionToUserApiV1PermissionsPermissionIdAssignToUserUserIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostData = { + body?: never; + path: { + /** + * Permission Id + */ + permission_id: number; + /** + * User Id + */ + user_id: string; + }; + query?: never; + url: '/api/v1/permissions/{permission_id}/unbind-from-user/{user_id}'; +}; + +export type UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors = { + /** + * 用户或权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostError = UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors[keyof UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostErrors]; + +export type UnbindPermissionFromUserApiV1PermissionsPermissionIdUnbindFromUserUserIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type ListRolesApiV1RolesGetData = { + body?: never; + path?: never; + query?: { + /** + * Skip + */ + skip?: number; + /** + * Limit + */ + limit?: number; + }; + url: '/api/v1/roles'; +}; + +export type ListRolesApiV1RolesGetErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type ListRolesApiV1RolesGetError = ListRolesApiV1RolesGetErrors[keyof ListRolesApiV1RolesGetErrors]; + +export type ListRolesApiV1RolesGetResponses = { + /** + * Response List Roles Api V1 Roles Get + * + * Successful Response + */ + 200: Array; +}; + +export type ListRolesApiV1RolesGetResponse = ListRolesApiV1RolesGetResponses[keyof ListRolesApiV1RolesGetResponses]; + +export type CreateRoleApiV1RolesPostData = { + body?: never; + path?: never; + query: { + /** + * Name + */ + name: string; + /** + * Description + */ + description?: string | null; + /** + * Is Only User + */ + is_only_user?: boolean; + }; + url: '/api/v1/roles'; +}; + +export type CreateRoleApiV1RolesPostErrors = { + /** + * 角色名称已存在 + */ + 400: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type CreateRoleApiV1RolesPostError = CreateRoleApiV1RolesPostErrors[keyof CreateRoleApiV1RolesPostErrors]; + +export type CreateRoleApiV1RolesPostResponses = { + /** + * Successful Response + */ + 200: Role; +}; + +export type CreateRoleApiV1RolesPostResponse = CreateRoleApiV1RolesPostResponses[keyof CreateRoleApiV1RolesPostResponses]; + +export type DeleteRoleApiV1RolesRoleIdDeleteData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}'; +}; + +export type DeleteRoleApiV1RolesRoleIdDeleteErrors = { + /** + * 角色不为空,无法删除 + */ + 400: HttpError; + /** + * 角色不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type DeleteRoleApiV1RolesRoleIdDeleteError = DeleteRoleApiV1RolesRoleIdDeleteErrors[keyof DeleteRoleApiV1RolesRoleIdDeleteErrors]; + +export type DeleteRoleApiV1RolesRoleIdDeleteResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type GetRoleApiV1RolesRoleIdGetData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}'; +}; + +export type GetRoleApiV1RolesRoleIdGetErrors = { + /** + * 角色不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetRoleApiV1RolesRoleIdGetError = GetRoleApiV1RolesRoleIdGetErrors[keyof GetRoleApiV1RolesRoleIdGetErrors]; + +export type GetRoleApiV1RolesRoleIdGetResponses = { + /** + * Successful Response + */ + 200: Role; +}; + +export type GetRoleApiV1RolesRoleIdGetResponse = GetRoleApiV1RolesRoleIdGetResponses[keyof GetRoleApiV1RolesRoleIdGetResponses]; + +export type UpdateRoleApiV1RolesRoleIdPutData = { + body: RoleUpdate; + path: { + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}'; +}; + +export type UpdateRoleApiV1RolesRoleIdPutErrors = { + /** + * 角色不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UpdateRoleApiV1RolesRoleIdPutError = UpdateRoleApiV1RolesRoleIdPutErrors[keyof UpdateRoleApiV1RolesRoleIdPutErrors]; + +export type UpdateRoleApiV1RolesRoleIdPutResponses = { + /** + * Successful Response + */ + 200: Role; +}; + +export type UpdateRoleApiV1RolesRoleIdPutResponse = UpdateRoleApiV1RolesRoleIdPutResponses[keyof UpdateRoleApiV1RolesRoleIdPutResponses]; + +export type AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + /** + * User Id + */ + user_id: string; + }; + query?: never; + url: '/api/v1/roles/{role_id}/assign-to-user/{user_id}'; +}; + +export type AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors = { + /** + * 角色或用户不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostError = AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors[keyof AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostErrors]; + +export type AssignRoleToUserApiV1RolesRoleIdAssignToUserUserIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + /** + * User Id + */ + user_id: string; + }; + query?: never; + url: '/api/v1/roles/{role_id}/unbind-from-user/{user_id}'; +}; + +export type UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors = { + /** + * 用户不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostError = UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors[keyof UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostErrors]; + +export type UnbindRoleFromUserApiV1RolesRoleIdUnbindFromUserUserIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type GetRolePermissionsApiV1RolesRoleIdPermissionsGetData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}/permissions'; +}; + +export type GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors = { + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type GetRolePermissionsApiV1RolesRoleIdPermissionsGetError = GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors[keyof GetRolePermissionsApiV1RolesRoleIdPermissionsGetErrors]; + +export type GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses = { + /** + * Response Get Role Permissions Api V1 Roles Role Id Permissions Get + * + * Successful Response + */ + 200: Array; +}; + +export type GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponse = GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses[keyof GetRolePermissionsApiV1RolesRoleIdPermissionsGetResponses]; + +export type AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + /** + * Permission Id + */ + permission_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}/assign-permission/{permission_id}'; +}; + +export type AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors = { + /** + * 权限已分配给角色 + */ + 400: HttpError; + /** + * 角色或权限不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostError = AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors[keyof AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostErrors]; + +export type AssignPermissionToRoleApiV1RolesRoleIdAssignPermissionPermissionIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + +export type UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostData = { + body?: never; + path: { + /** + * Role Id + */ + role_id: number; + /** + * Permission Id + */ + permission_id: number; + }; + query?: never; + url: '/api/v1/roles/{role_id}/unbind-permission/{permission_id}'; +}; + +export type UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors = { + /** + * 角色不存在 + */ + 404: HttpError; + /** + * Validation Error + */ + 422: HttpValidationError; +}; + +export type UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostError = UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors[keyof UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostErrors]; + +export type UnbindPermissionFromRoleApiV1RolesRoleIdUnbindPermissionPermissionIdPostResponses = { + /** + * Successful Response + */ + 200: unknown; +}; + export type RootGetData = { body?: never; path?: never; diff --git a/src/chongming/app/api/__init__.py b/src/chongming/app/api/__init__.py index 1d0c335..4ea1bf1 100644 --- a/src/chongming/app/api/__init__.py +++ b/src/chongming/app/api/__init__.py @@ -4,7 +4,8 @@ API 路由 from fastapi import APIRouter -from .routers import todo, auth, upload +from .routers import todo, auth, upload, permission, role + # 创建主路由 api_router = APIRouter() @@ -13,6 +14,8 @@ api_router = APIRouter() api_router.include_router(todo.router, prefix="/todos", tags=["todo"]) api_router.include_router(auth.router, prefix="/auth", tags=["authentication"]) 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"]) # 导出路由 diff --git a/src/chongming/app/api/deps.py b/src/chongming/app/api/deps.py index 28732be..642e82d 100644 --- a/src/chongming/app/api/deps.py +++ b/src/chongming/app/api/deps.py @@ -1,7 +1,14 @@ +from typing import List, Set, Union + from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials +from sqlalchemy.ext.asyncio import AsyncSession 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 @@ -40,3 +47,177 @@ async def get_current_user( ) 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 diff --git a/src/chongming/app/api/routers/permission.py b/src/chongming/app/api/routers/permission.py new file mode 100644 index 0000000..747f08e --- /dev/null +++ b/src/chongming/app/api/routers/permission.py @@ -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} diff --git a/src/chongming/app/api/routers/role.py b/src/chongming/app/api/routers/role.py new file mode 100644 index 0000000..3f9e0e1 --- /dev/null +++ b/src/chongming/app/api/routers/role.py @@ -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) + diff --git a/src/chongming/app/api/routers/todo.py b/src/chongming/app/api/routers/todo.py index 0c70749..2f3aeef 100644 --- a/src/chongming/app/api/routers/todo.py +++ b/src/chongming/app/api/routers/todo.py @@ -5,8 +5,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from ...model.todo import TodoCreate, TodoUpdate, TodoRead from ...core.database import get_session +from ...constant.permission import TodoConstant 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)]) @@ -17,6 +18,7 @@ router = APIRouter(dependencies=[Depends(get_current_user)]) async def create_todo( todo: TodoCreate, 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]) 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) -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获取单个待办事项 @@ -72,7 +81,10 @@ async def read_todo(todo_id: int, session: AsyncSession = Depends(get_session)): @router.patch("/{todo_id}", response_model=TodoRead) 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}") -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)), +): """ 删除待办事项 diff --git a/src/chongming/app/constant/__init__.py b/src/chongming/app/constant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/chongming/app/constant/permission/__init__.py b/src/chongming/app/constant/permission/__init__.py new file mode 100644 index 0000000..86c0b82 --- /dev/null +++ b/src/chongming/app/constant/permission/__init__.py @@ -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 diff --git a/src/chongming/app/constant/permission/permission_mgt.py b/src/chongming/app/constant/permission/permission_mgt.py new file mode 100644 index 0000000..f235ac9 --- /dev/null +++ b/src/chongming/app/constant/permission/permission_mgt.py @@ -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: "解绑权限", + } diff --git a/src/chongming/app/constant/permission/personnel.py b/src/chongming/app/constant/permission/personnel.py new file mode 100644 index 0000000..24a9c35 --- /dev/null +++ b/src/chongming/app/constant/permission/personnel.py @@ -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: "删除人员", + } diff --git a/src/chongming/app/constant/permission/role.py b/src/chongming/app/constant/permission/role.py new file mode 100644 index 0000000..f946558 --- /dev/null +++ b/src/chongming/app/constant/permission/role.py @@ -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: "解绑角色", + } diff --git a/src/chongming/app/constant/permission/system_config.py b/src/chongming/app/constant/permission/system_config.py new file mode 100644 index 0000000..0d64fa2 --- /dev/null +++ b/src/chongming/app/constant/permission/system_config.py @@ -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: "恢复备份", + } diff --git a/src/chongming/app/constant/permission/todo.py b/src/chongming/app/constant/permission/todo.py new file mode 100644 index 0000000..99c780f --- /dev/null +++ b/src/chongming/app/constant/permission/todo.py @@ -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: "删除待办事项", + } diff --git a/src/chongming/app/core/cache.py b/src/chongming/app/core/cache.py index acfcc7b..d76a8b6 100644 --- a/src/chongming/app/core/cache.py +++ b/src/chongming/app/core/cache.py @@ -6,7 +6,7 @@ """ import time -from typing import Any, Optional +from typing import Any, Callable, Optional import diskcache @@ -347,7 +347,7 @@ def get_cache() -> Cache: # 装饰器:缓存函数结果 def cached(ttl: Optional[int] = None): """ - 函数结果缓存装饰器 + 函数结果缓存装饰器(同步版本) 自动缓存函数的返回值,相同参数的调用直接从缓存返回, 避免重复计算。适用于纯函数或副作用小的函数。 @@ -363,30 +363,76 @@ def cached(ttl: Optional[int] = None): ... def get_user(user_id: int): ... return {"id": user_id, "name": "test"} >>> - >>> # 第一次调用执行函数并缓存 >>> get_user(1) - >>> # 60秒内再次调用直接返回缓存结果 - >>> get_user(1) - - Note: - - 缓存键由函数名 + 位置参数 + 关键字参数组成 - - 参数必须是可哈希的,否则会导致错误 - - 不适合缓存大型对象或频繁变化的数据 - - 不同参数组合会生成不同的缓存键 """ def decorator(func): def wrapper(*args, **kwargs): - # 生成缓存key key = f"{func.__name__}:{str(args)}:{str(kwargs)}" cache = get_cache() - # 尝试从缓存获取 result = cache.get(key) if result is not None: return result - # 执行函数 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) diff --git a/src/chongming/app/core/constant.py b/src/chongming/app/core/constant.py deleted file mode 100644 index fc40c05..0000000 --- a/src/chongming/app/core/constant.py +++ /dev/null @@ -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 diff --git a/src/chongming/app/model/__init__.py b/src/chongming/app/model/__init__.py index 0fd63b2..eca2fc1 100644 --- a/src/chongming/app/model/__init__.py +++ b/src/chongming/app/model/__init__.py @@ -1,6 +1,6 @@ from .user import User from .role import Role, RoleUpdate -from .permission import Permission +from .permission import Permission, PermissionUpdate, PermissionCreate from .relationship_table import UserRole, RolePermission from .todo import Todo @@ -10,6 +10,8 @@ __all__ = [ "Role", "RoleUpdate", "Permission", + "PermissionUpdate", + "PermissionCreate", "UserRole", "RolePermission", "Todo", diff --git a/src/chongming/app/model/permission.py b/src/chongming/app/model/permission.py index 7e1f600..ef40f3b 100644 --- a/src/chongming/app/model/permission.py +++ b/src/chongming/app/model/permission.py @@ -1,5 +1,6 @@ from typing import Optional, List, TYPE_CHECKING +from pydantic import BaseModel from sqlmodel import SQLModel, Field, Relationship from .relationship_table import RolePermission @@ -34,3 +35,39 @@ class Permission(SQLModel, table=True): roles: List["Role"] = Relationship( 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 diff --git a/src/chongming/app/service/permission.py b/src/chongming/app/service/permission.py index e247961..8c75343 100644 --- a/src/chongming/app/service/permission.py +++ b/src/chongming/app/service/permission.py @@ -1,15 +1,20 @@ -from typing import Optional +from typing import Optional, List, Sequence from uuid import UUID from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession 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 .role import RoleService +# 用户权限缓存的 TTL(秒),权限变更后最多 2 分钟自动刷新 +_USER_PERMS_CACHE_TTL = 120 + + class PermissionService: """权限服务类,提供权限的创建、查询、分配和解除绑定等功能""" @@ -69,6 +74,189 @@ class PermissionService: ) 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 async def assign_permission_to_role( cls, session: AsyncSession, role_id: int, permission_id: int @@ -80,7 +268,26 @@ class PermissionService: :param role_id: 角色ID :param permission_id: 权限ID :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) session.add(role_permission) await session.commit() @@ -133,6 +340,8 @@ class PermissionService: user.roles.append(role) await session.commit() await session.refresh(user) + # 清除该用户的权限缓存,下次查询时重新加载 + cls._invalidate_user_perms_cache(user_id) return user @classmethod @@ -142,6 +351,8 @@ class PermissionService: """ 从角色中解除权限绑定 + 直接通过 RolePermission 关联表删除记录,避免在异步上下文中触发懒加载。 + :param session: 数据库会话对象 :param role_id: 角色ID :param permission_id: 权限ID @@ -153,8 +364,15 @@ class PermissionService: if role is None: 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.refresh(role) return role @@ -166,7 +384,7 @@ class PermissionService: """ 从用户中解除权限绑定 - 通过找到用户的专属角色,从该角色中移除指定权限 + 通过找到用户的专属角色,从该角色中移除指定权限。 :param session: 数据库会话对象 :param user_id: 用户UUID @@ -183,11 +401,17 @@ class PermissionService: if role is None: raise HTTPException(status_code=404, detail="Role not found") - # 从角色权限列表中移除指定权限 - role.permissions = [ - permission - for permission in role.permissions - if permission.id != permission_id - ] - await session.commit() - await session.refresh(role) + if role.id is not None: + # 直接通过关联表删除记录 + 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.refresh(role) + # 清除该用户的权限缓存 + cls._invalidate_user_perms_cache(user_id) diff --git a/src/chongming/app/service/role.py b/src/chongming/app/service/role.py index 0bb6aad..71594e1 100644 --- a/src/chongming/app/service/role.py +++ b/src/chongming/app/service/role.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Sequence from uuid import UUID from fastapi import HTTPException @@ -89,6 +89,53 @@ class RoleService: result = await session.get(Role, role_id) 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 async def unbind_role_from_user( session: AsyncSession, @@ -103,9 +150,6 @@ class RoleService: user_id: 用户UUID role_id: 角色ID - Returns: - User: 更新后的用户对象 - Raises: HTTPException: 当用户不存在时抛出404错误 """ @@ -117,7 +161,6 @@ class RoleService: user.roles = [role for role in user.roles if role.id != role_id] await session.commit() await session.refresh(user) - return user @staticmethod async def delete_role( diff --git a/src/chongming/app/service/user.py b/src/chongming/app/service/user.py index b751680..302b6d1 100644 --- a/src/chongming/app/service/user.py +++ b/src/chongming/app/service/user.py @@ -1,5 +1,6 @@ from typing import Optional from uuid import UUID +import time from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -60,7 +61,7 @@ class UserService: IntegrityError: 当邮箱已存在时可能抛出完整性错误 """ 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) await session.commit() await session.refresh(user) diff --git a/src/chongming/app/task/dev_init_db.py b/src/chongming/app/task/dev_init_db.py index 09afda5..e6fcfa8 100644 --- a/src/chongming/app/task/dev_init_db.py +++ b/src/chongming/app/task/dev_init_db.py @@ -1,13 +1,69 @@ +from sqlalchemy.orm import selectinload +from sqlmodel import select + from ..core.logger import get_logger -from ..core.constant import PermissionConstant +from ..constant.permission import PermissionConstant from ..service.user import UserService +from ..service.role import RoleService from ..service.permission import PermissionService +from ..model.role import Role +from ..model.user import User from plugins.scheduler.scheduler import get_task_service_instance 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): """ @@ -27,53 +83,242 @@ async def dev_init_admin(session): - 如果管理员已存在,则跳过创建并记录日志 - 新创建的用户具有超级管理员权限(is_superuser=True) """ - admin_user = await UserService.get_user_by_email("admin", session) - if admin_user: # type: ignore - logger.info("管理员用户已存在") # type: ignore + # 先检查 admin@qq.com 邮箱是否已被使用 + admin_user = await UserService.get_user_by_email("admin@qq.com", session) + if admin_user: + logger.info("管理员用户已存在") 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: - session: 数据库会话对象,用于执行数据库操作 + session: 数据库会话对象 Returns: None - - Note: - - 权限格式为"resource:action",例如"user:create" - - 使用冒号分隔资源和操作,如果缺少操作部分则默认为空字符串 - - 已存在的权限会被跳过,避免重复创建 - - 每个新创建的权限都会记录到日志中 """ - 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 + for role_name, get_permissions_fn in ROLE_PERMISSION_MAP.items(): + # 检查角色是否已存在(主动加载 permissions 关系,避免异步懒加载问题) + result = await session.execute( + select(Role) + .where(Role.name == role_name) + .options(selectinload(Role.permissions)) # type: ignore[arg-type] ) - if existing_permission: - logger.debug(f"权限已存在: {permission}") - continue + existing_role = result.scalar_one_or_none() - await PermissionService.create_permission(session, permission, resource, action) - logger.info(f"创建权限: {permission}") + if existing_role: + 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(): """ 执行数据库初始化流程 - 协调执行所有数据库初始化任务,包括管理员账户创建和权限数据初始化。 + 协调执行所有数据库初始化任务,包括管理员账户创建、权限数据初始化和默认角色初始化。 该函数是系统启动时数据库初始化的入口点。 Returns: @@ -82,12 +327,16 @@ async def init_db(): Note: - 通过任务服务获取异步会话工厂 - 如果会话工厂未初始化,则直接返回不执行任何操作 - - 按顺序执行:先初始化管理员,再初始化权限 + - 按顺序执行:先初始化权限,再初始化角色,最后初始化管理员 - 使用异步上下文管理器确保会话正确关闭 """ task_service = get_task_service_instance() if task_service.async_session_maker is None: return async with task_service.async_session_maker() as session: - await dev_init_admin(session) + # 先初始化权限数据(角色依赖权限存在) await init_permission(session) + # 再初始化默认角色及权限关联 + await init_default_roles(session) + # 最后初始化管理员用户 + await dev_init_admin(session) diff --git a/src/plugins/jwt/jwt_cache.py b/src/plugins/jwt/jwt_cache.py index 318cd1e..5410c44 100644 --- a/src/plugins/jwt/jwt_cache.py +++ b/src/plugins/jwt/jwt_cache.py @@ -1,11 +1,11 @@ import json +import threading +import hashlib from datetime import datetime, timedelta from typing import Optional, Dict, Any, List -from pydantic import BaseModel from dataclasses import dataclass, asdict -import hashlib -import threading +from pydantic import BaseModel import jwt