diff --git a/backend/app/admin/api/v1/auth/__init__.py b/backend/app/admin/api/v1/auth/__init__.py index b13b1aa58..a6fd8c712 100644 --- a/backend/app/admin/api/v1/auth/__init__.py +++ b/backend/app/admin/api/v1/auth/__init__.py @@ -8,4 +8,4 @@ router = APIRouter(prefix='/auth') router.include_router(auth_router, tags=['授权']) -router.include_router(captcha_router, prefix='/captcha', tags=['验证码']) +router.include_router(captcha_router, tags=['验证码']) diff --git a/backend/app/admin/api/v1/auth/auth.py b/backend/app/admin/api/v1/auth/auth.py index 0321c1b1c..eb4730858 100644 --- a/backend/app/admin/api/v1/auth/auth.py +++ b/backend/app/admin/api/v1/auth/auth.py @@ -11,14 +11,15 @@ from backend.app.admin.schema.user import AuthLoginParam from backend.app.admin.service.auth_service import auth_service from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base +from backend.common.security.jwt import DependsJwtAuth router = APIRouter() @router.post('/login/swagger', summary='swagger 调试专用', description='用于快捷获取 token 进行 swagger 认证') -async def swagger_login(obj: Annotated[HTTPBasicCredentials, Depends()]) -> GetSwaggerToken: +async def login_swagger(obj: Annotated[HTTPBasicCredentials, Depends()]) -> GetSwaggerToken: token, user = await auth_service.swagger_login(obj=obj) - return GetSwaggerToken(access_token=token, user=user) # type: ignore + return GetSwaggerToken(access_token=token, user=user) @router.post( @@ -27,20 +28,26 @@ async def swagger_login(obj: Annotated[HTTPBasicCredentials, Depends()]) -> GetS description='json 格式登录, 仅支持在第三方api工具调试, 例如: postman', dependencies=[Depends(RateLimiter(times=5, minutes=1))], ) -async def user_login( +async def login( request: Request, response: Response, obj: AuthLoginParam, background_tasks: BackgroundTasks ) -> ResponseSchemaModel[GetLoginToken]: data = await auth_service.login(request=request, response=response, obj=obj, background_tasks=background_tasks) return response_base.success(data=data) -@router.post('/tokens/refresh', summary='刷新 token') +@router.get('/codes', summary='获取所有授权码', description='适配 vben admin v5', dependencies=[DependsJwtAuth]) +async def get_codes(request: Request) -> ResponseSchemaModel[list[str]]: + codes = await auth_service.get_codes(request=request) + return response_base.success(data=codes) + + +@router.post('/tokens', summary='刷新 token') async def refresh_token(request: Request) -> ResponseSchemaModel[GetNewToken]: - data = await auth_service.new_token(request=request) + data = await auth_service.refresh_token(request=request) return response_base.success(data=data) @router.post('/logout', summary='用户登出') -async def user_logout(request: Request, response: Response) -> ResponseModel: +async def logout(request: Request, response: Response) -> ResponseModel: await auth_service.logout(request=request, response=response) return response_base.success() diff --git a/backend/app/admin/api/v1/auth/captcha.py b/backend/app/admin/api/v1/auth/captcha.py index 26abc9a8f..745b25715 100644 --- a/backend/app/admin/api/v1/auth/captcha.py +++ b/backend/app/admin/api/v1/auth/captcha.py @@ -14,7 +14,7 @@ @router.get( - '', + '/captcha', summary='获取登录验证码', dependencies=[Depends(RateLimiter(times=5, seconds=10))], ) diff --git a/backend/app/admin/api/v1/log/login_log.py b/backend/app/admin/api/v1/log/login_log.py index c2c86b641..0803793e9 100644 --- a/backend/app/admin/api/v1/log/login_log.py +++ b/backend/app/admin/api/v1/log/login_log.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Query -from backend.app.admin.schema.login_log import GetLoginLogDetail +from backend.app.admin.schema.login_log import DeleteLoginLogParam, GetLoginLogDetail from backend.app.admin.service.login_log_service import login_log_service from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base @@ -24,7 +24,7 @@ DependsPagination, ], ) -async def get_pagination_login_logs( +async def get_login_logs_paged( db: CurrentSession, username: Annotated[str | None, Query(description='用户名')] = None, status: Annotated[int | None, Query(description='状态')] = None, @@ -43,8 +43,8 @@ async def get_pagination_login_logs( DependsRBAC, ], ) -async def delete_login_log(pk: Annotated[list[int], Query(description='登录日志 ID 列表')]) -> ResponseModel: - count = await login_log_service.delete(pk=pk) +async def delete_login_logs(obj: DeleteLoginLogParam) -> ResponseModel: + count = await login_log_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() @@ -54,7 +54,7 @@ async def delete_login_log(pk: Annotated[list[int], Query(description='登录日 '/all', summary='清空登录日志', dependencies=[ - Depends(RequestPermission('log:login:empty')), + Depends(RequestPermission('log:login:clear')), DependsRBAC, ], ) diff --git a/backend/app/admin/api/v1/log/opera_log.py b/backend/app/admin/api/v1/log/opera_log.py index e1d7c6ac1..35b1c5064 100644 --- a/backend/app/admin/api/v1/log/opera_log.py +++ b/backend/app/admin/api/v1/log/opera_log.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Query -from backend.app.admin.schema.opera_log import GetOperaLogDetail +from backend.app.admin.schema.opera_log import DeleteOperaLogParam, GetOperaLogDetail from backend.app.admin.service.opera_log_service import opera_log_service from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base @@ -24,7 +24,7 @@ DependsPagination, ], ) -async def get_pagination_opera_logs( +async def get_opera_logs_paged( db: CurrentSession, username: Annotated[str | None, Query(description='用户名')] = None, status: Annotated[int | None, Query(description='状态')] = None, @@ -43,8 +43,8 @@ async def get_pagination_opera_logs( DependsRBAC, ], ) -async def delete_opera_log(pk: Annotated[list[int], Query(description='操作日志 ID 列表')]) -> ResponseModel: - count = await opera_log_service.delete(pk=pk) +async def delete_opera_logs(obj: DeleteOperaLogParam) -> ResponseModel: + count = await opera_log_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() @@ -54,7 +54,7 @@ async def delete_opera_log(pk: Annotated[list[int], Query(description='操作日 '/all', summary='清空操作日志', dependencies=[ - Depends(RequestPermission('log:opera:empty')), + Depends(RequestPermission('log:opera:clear')), DependsRBAC, ], ) diff --git a/backend/app/admin/api/v1/monitor/__init__.py b/backend/app/admin/api/v1/monitor/__init__.py index 013c21879..c8960cb24 100644 --- a/backend/app/admin/api/v1/monitor/__init__.py +++ b/backend/app/admin/api/v1/monitor/__init__.py @@ -10,4 +10,4 @@ router.include_router(redis_router, prefix='/redis', tags=['redis监控']) router.include_router(server_router, prefix='/server', tags=['服务器监控']) -router.include_router(token_router, prefix='/online', tags=['在线用户']) +router.include_router(token_router, prefix='/sessions', tags=['会话监控']) diff --git a/backend/app/admin/api/v1/monitor/online.py b/backend/app/admin/api/v1/monitor/online.py index 96f457623..ed3945dea 100644 --- a/backend/app/admin/api/v1/monitor/online.py +++ b/backend/app/admin/api/v1/monitor/online.py @@ -19,7 +19,7 @@ @router.get('', summary='获取在线用户', dependencies=[DependsJwtAuth]) -async def get_online( +async def get_sessions( username: Annotated[str | None, Query(description='用户名')] = None, ) -> ResponseSchemaModel[list[GetTokenDetail]]: token_keys = await redis_client.keys(f'{settings.TOKEN_REDIS_PREFIX}:*') @@ -75,13 +75,13 @@ def append_token_detail() -> None: @router.delete( '/{pk}', - summary='踢下线', + summary='强制下线', dependencies=[ - Depends(RequestPermission('sys:token:kick')), + Depends(RequestPermission('sys:session:delete')), DependsRBAC, ], ) -async def kick_out( +async def delete_session( request: Request, pk: Annotated[int, Path(description='用户 ID')], session_uuid: Annotated[str, Query(description='会话 UUID')], diff --git a/backend/app/admin/api/v1/sys/__init__.py b/backend/app/admin/api/v1/sys/__init__.py index 3411dcf8c..a8a559b82 100644 --- a/backend/app/admin/api/v1/sys/__init__.py +++ b/backend/app/admin/api/v1/sys/__init__.py @@ -5,10 +5,10 @@ from backend.app.admin.api.v1.sys.data_rule import router as data_rule_router from backend.app.admin.api.v1.sys.data_scope import router as data_scope_router from backend.app.admin.api.v1.sys.dept import router as dept_router +from backend.app.admin.api.v1.sys.files import router as file_router from backend.app.admin.api.v1.sys.menu import router as menu_router from backend.app.admin.api.v1.sys.plugin import router as plugin_router from backend.app.admin.api.v1.sys.role import router as role_router -from backend.app.admin.api.v1.sys.upload import router as upload_router from backend.app.admin.api.v1.sys.user import router as user_router router = APIRouter(prefix='/sys') @@ -19,5 +19,5 @@ router.include_router(user_router, prefix='/users', tags=['系统用户']) router.include_router(data_rule_router, prefix='/data-rules', tags=['系统数据规则']) router.include_router(data_scope_router, prefix='/data-scopes', tags=['系统数据范围']) -router.include_router(upload_router, prefix='/upload', tags=['系统上传']) +router.include_router(file_router, prefix='/files', tags=['系统文件']) router.include_router(plugin_router, prefix='/plugins', tags=['系统插件']) diff --git a/backend/app/admin/api/v1/sys/data_rule.py b/backend/app/admin/api/v1/sys/data_rule.py index ecd9b1197..dfe18df3a 100644 --- a/backend/app/admin/api/v1/sys/data_rule.py +++ b/backend/app/admin/api/v1/sys/data_rule.py @@ -6,6 +6,7 @@ from backend.app.admin.schema.data_rule import ( CreateDataRuleParam, + DeleteDataRuleParam, GetDataRuleColumnDetail, GetDataRuleDetail, UpdateDataRuleParam, @@ -57,7 +58,7 @@ async def get_data_rule( DependsPagination, ], ) -async def get_pagination_data_rules( +async def get_data_rules_paged( db: CurrentSession, name: Annotated[str | None, Query(description='规则名称')] = None ) -> ResponseSchemaModel[PageData[GetDataRuleDetail]]: data_rule_select = await data_rule_service.get_select(name=name) @@ -103,8 +104,8 @@ async def update_data_rule( DependsRBAC, ], ) -async def delete_data_rule(pk: Annotated[list[int], Query(description='数据规则 ID 列表')]) -> ResponseModel: - count = await data_rule_service.delete(pk=pk) +async def delete_data_rules(obj: DeleteDataRuleParam) -> ResponseModel: + count = await data_rule_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/app/admin/api/v1/sys/data_scope.py b/backend/app/admin/api/v1/sys/data_scope.py index 4a639dd91..fdf8d9720 100644 --- a/backend/app/admin/api/v1/sys/data_scope.py +++ b/backend/app/admin/api/v1/sys/data_scope.py @@ -6,6 +6,7 @@ from backend.app.admin.schema.data_scope import ( CreateDataScopeParam, + DeleteDataScopeParam, GetDataScopeDetail, GetDataScopeWithRelationDetail, UpdateDataScopeParam, @@ -52,7 +53,7 @@ async def get_data_scope_rules( DependsPagination, ], ) -async def get_pagination_data_scopes( +async def get_data_scopes_paged( db: CurrentSession, name: Annotated[str | None, Query(description='范围名称')] = None, status: Annotated[int | None, Query(description='状态')] = None, @@ -117,8 +118,8 @@ async def update_data_scope_rules( DependsRBAC, ], ) -async def delete_data_scope(pk: Annotated[list[int], Query(description='数据范围 ID 列表')]) -> ResponseModel: - count = await data_scope_service.delete(pk=pk) +async def delete_data_scopes(obj: DeleteDataScopeParam) -> ResponseModel: + count = await data_scope_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/app/admin/api/v1/sys/dept.py b/backend/app/admin/api/v1/sys/dept.py index 0bce3a44c..ac6d291b1 100644 --- a/backend/app/admin/api/v1/sys/dept.py +++ b/backend/app/admin/api/v1/sys/dept.py @@ -20,15 +20,15 @@ async def get_dept(pk: Annotated[int, Path(description='部门 ID')]) -> Respons return response_base.success(data=data) -@router.get('', summary='获取所有部门展示树', dependencies=[DependsJwtAuth]) -async def get_all_depts( +@router.get('', summary='获取部门树', dependencies=[DependsJwtAuth]) +async def get_dept_tree( request: Request, name: Annotated[str | None, Query(description='部门名称')] = None, leader: Annotated[str | None, Query(description='部门负责人')] = None, phone: Annotated[str | None, Query(description='联系电话')] = None, status: Annotated[int | None, Query(description='状态')] = None, ) -> ResponseSchemaModel[list[dict[str, Any]]]: - dept = await dept_service.get_dept_tree(request=request, name=name, leader=leader, phone=phone, status=status) + dept = await dept_service.get_tree(request=request, name=name, leader=leader, phone=phone, status=status) return response_base.success(data=dept) diff --git a/backend/app/admin/api/v1/sys/files.py b/backend/app/admin/api/v1/sys/files.py new file mode 100644 index 000000000..ff0fa400c --- /dev/null +++ b/backend/app/admin/api/v1/sys/files.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from typing import Annotated + +from fastapi import APIRouter, Depends, File, UploadFile + +from backend.common.dataclasses import UploadUrl +from backend.common.response.response_schema import ResponseSchemaModel, response_base +from backend.common.security.permission import RequestPermission +from backend.common.security.rbac import DependsRBAC +from backend.utils.file_ops import file_verify, upload_file + +router = APIRouter() + + +@router.post( + '/upload', + summary='文件上传', + dependencies=[ + Depends(RequestPermission('sys:file:upload')), + DependsRBAC, + ], +) +async def upload_files(file: Annotated[UploadFile, File()]) -> ResponseSchemaModel[UploadUrl]: + file_verify(file) + filename = await upload_file(file) + return response_base.success(data={'url': f'/static/upload/{filename}'}) diff --git a/backend/app/admin/api/v1/sys/menu.py b/backend/app/admin/api/v1/sys/menu.py index 02bf0e0a5..3b3d799da 100644 --- a/backend/app/admin/api/v1/sys/menu.py +++ b/backend/app/admin/api/v1/sys/menu.py @@ -14,7 +14,7 @@ router = APIRouter() -@router.get('/sidebar', summary='获取用户菜单侧边栏', description='适配 vben5', dependencies=[DependsJwtAuth]) +@router.get('/sidebar', summary='获取用户菜单侧边栏', description='已适配 vben admin v5', dependencies=[DependsJwtAuth]) async def get_user_sidebar(request: Request) -> ResponseSchemaModel[list[dict[str, Any] | None]]: menu = await menu_service.get_sidebar(request=request) return response_base.success(data=menu) @@ -26,12 +26,12 @@ async def get_menu(pk: Annotated[int, Path(description='菜单 ID')]) -> Respons return response_base.success(data=data) -@router.get('', summary='获取所有菜单展示树', dependencies=[DependsJwtAuth]) -async def get_all_menus( +@router.get('', summary='获取菜单树', dependencies=[DependsJwtAuth]) +async def get_menu_tree( title: Annotated[str | None, Query(description='菜单标题')] = None, status: Annotated[int | None, Query(description='状体')] = None, ) -> ResponseSchemaModel[list[dict[str, Any]]]: - menu = await menu_service.get_menu_tree(title=title, status=status) + menu = await menu_service.get_tree(title=title, status=status) return response_base.success(data=menu) diff --git a/backend/app/admin/api/v1/sys/plugin.py b/backend/app/admin/api/v1/sys/plugin.py index c7ab2fafa..9589fbb8f 100644 --- a/backend/app/admin/api/v1/sys/plugin.py +++ b/backend/app/admin/api/v1/sys/plugin.py @@ -7,6 +7,7 @@ from starlette.responses import StreamingResponse from backend.app.admin.service.plugin_service import plugin_service +from backend.common.enums import PluginType from backend.common.response.response_code import CustomResponseCode from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base from backend.common.security.jwt import DependsJwtAuth @@ -22,37 +23,27 @@ async def get_all_plugins() -> ResponseSchemaModel[list[dict[str, Any]]]: return response_base.success(data=plugins) -@router.get('/changes', summary='插件状态是否变更', dependencies=[DependsJwtAuth]) +@router.get('/changed', summary='是否存在插件变更', dependencies=[DependsJwtAuth]) async def plugin_changed() -> ResponseSchemaModel[bool]: plugins = await plugin_service.changed() return response_base.success(data=bool(plugins)) @router.post( - '/zip', - summary='安装 zip 插件', - description='使用插件 zip 压缩包进行安装', + '', + summary='安装插件', + description='使用插件 zip 压缩包或 git 仓库地址进行安装', dependencies=[ - Depends(RequestPermission('sys:plugin:zip')), + Depends(RequestPermission('sys:plugin:install')), DependsRBAC, ], ) -async def install_zip_plugin(file: Annotated[UploadFile, File()]) -> ResponseModel: - await plugin_service.install_zip(file=file) - return response_base.success(res=CustomResponseCode.PLUGIN_INSTALL_SUCCESS) - - -@router.post( - '/git', - summary='安装 git 插件', - description='使用插件 git 仓库地址进行安装,不限制平台;如果需要凭证,需在 git 仓库地址中添加凭证信息', - dependencies=[ - Depends(RequestPermission('sys:plugin:git')), - DependsRBAC, - ], -) -async def install_git_plugin(repo_url: Annotated[str, Query(description='插件 git 仓库地址')]) -> ResponseModel: - await plugin_service.install_git(repo_url=repo_url) +async def install_plugin( + type: Annotated[PluginType, Query(description='插件类型')], + file: Annotated[UploadFile | None, File()] = None, + repo_url: Annotated[str | None, Query(description='插件 git 仓库地址')] = None, +) -> ResponseModel: + await plugin_service.install(type=type, file=file, repo_url=repo_url) return response_base.success(res=CustomResponseCode.PLUGIN_INSTALL_SUCCESS) @@ -61,7 +52,7 @@ async def install_git_plugin(repo_url: Annotated[str, Query(description='插件 summary='卸载插件', description='此操作会直接删除插件依赖,但不会直接删除插件,而是将插件移动到备份目录', dependencies=[ - Depends(RequestPermission('sys:plugin:del')), + Depends(RequestPermission('sys:plugin:uninstall')), DependsRBAC, ], ) @@ -70,11 +61,11 @@ async def uninstall_plugin(plugin: Annotated[str, Path(description='插件名称 return response_base.success(res=CustomResponseCode.PLUGIN_UNINSTALL_SUCCESS) -@router.post( +@router.put( '/{plugin}/status', summary='更新插件状态', dependencies=[ - Depends(RequestPermission('sys:plugin:status')), + Depends(RequestPermission('sys:plugin:edit')), DependsRBAC, ], ) @@ -83,8 +74,8 @@ async def update_plugin_status(plugin: Annotated[str, Path(description='插件 return response_base.success() -@router.get('/{plugin}', summary='打包并下载插件', dependencies=[DependsJwtAuth]) -async def build_plugin(plugin: Annotated[str, Path(description='插件名称')]) -> StreamingResponse: +@router.get('/{plugin}', summary='下载插件', dependencies=[DependsJwtAuth]) +async def download_plugin(plugin: Annotated[str, Path(description='插件名称')]) -> StreamingResponse: bio = await plugin_service.build(plugin=plugin) return StreamingResponse( bio, diff --git a/backend/app/admin/api/v1/sys/role.py b/backend/app/admin/api/v1/sys/role.py index 5a4c5b2dc..3f3f28026 100644 --- a/backend/app/admin/api/v1/sys/role.py +++ b/backend/app/admin/api/v1/sys/role.py @@ -6,6 +6,7 @@ from backend.app.admin.schema.role import ( CreateRoleParam, + DeleteRoleParam, GetRoleDetail, GetRoleWithRelationDetail, UpdateRoleMenuParam, @@ -29,8 +30,8 @@ async def get_all_roles() -> ResponseSchemaModel[list[GetRoleDetail]]: return response_base.success(data=data) -@router.get('/{pk}/menus', summary='获取角色所有菜单', dependencies=[DependsJwtAuth]) -async def get_role_all_menus( +@router.get('/{pk}/menus', summary='获取角色菜单树', dependencies=[DependsJwtAuth]) +async def get_role_menu_tree( pk: Annotated[int, Path(description='角色 ID')], ) -> ResponseSchemaModel[list[dict[str, Any] | None]]: menu = await role_service.get_menu_tree(pk=pk) @@ -38,15 +39,13 @@ async def get_role_all_menus( @router.get('/{pk}/scopes', summary='获取角色所有数据范围', dependencies=[DependsJwtAuth]) -async def get_role_all_scopes(pk: Annotated[int, Path(description='角色 ID')]) -> ResponseSchemaModel[list[int]]: +async def get_role_scopes(pk: Annotated[int, Path(description='角色 ID')]) -> ResponseSchemaModel[list[int]]: rule = await role_service.get_scopes(pk=pk) return response_base.success(data=rule) @router.get('/{pk}', summary='获取角色详情', dependencies=[DependsJwtAuth]) -async def get_role( - pk: Annotated[int, Path(description='角色 ID')], -) -> ResponseSchemaModel[GetRoleWithRelationDetail]: +async def get_role(pk: Annotated[int, Path(description='角色 ID')]) -> ResponseSchemaModel[GetRoleWithRelationDetail]: data = await role_service.get(pk=pk) return response_base.success(data=data) @@ -59,7 +58,7 @@ async def get_role( DependsPagination, ], ) -async def get_pagination_roles( +async def get_roles_paged( db: CurrentSession, name: Annotated[str | None, Query(description='角色名称')] = None, status: Annotated[int | None, Query(description='状态')] = None, @@ -139,8 +138,8 @@ async def update_role_scopes( DependsRBAC, ], ) -async def delete_role(pk: Annotated[list[int], Query(description='角色 ID 列表')]) -> ResponseModel: - count = await role_service.delete(pk=pk) +async def delete_roles(obj: DeleteRoleParam) -> ResponseModel: + count = await role_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/app/admin/api/v1/sys/upload.py b/backend/app/admin/api/v1/sys/upload.py deleted file mode 100644 index f81d2450d..000000000 --- a/backend/app/admin/api/v1/sys/upload.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -from typing import Annotated - -from fastapi import APIRouter, File, UploadFile - -from backend.common.dataclasses import UploadUrl -from backend.common.enums import FileType -from backend.common.response.response_schema import ResponseSchemaModel, response_base -from backend.common.security.jwt import DependsJwtAuth -from backend.utils.file_ops import file_verify, upload_file - -router = APIRouter() - - -@router.post('/image', summary='上传图片', dependencies=[DependsJwtAuth]) -async def upload_image(file: Annotated[UploadFile, File()]) -> ResponseSchemaModel[UploadUrl]: - file_verify(file, FileType.image) - filename = await upload_file(file) - return response_base.success(data={'url': f'/static/upload/{filename}'}) - - -@router.post('/video', summary='上传视频', dependencies=[DependsJwtAuth]) -async def upload_video(file: Annotated[UploadFile, File()]) -> ResponseSchemaModel[UploadUrl]: - file_verify(file, FileType.video) - filename = await upload_file(file) - return response_base.success(data={'url': f'/static/upload/{filename}'}) diff --git a/backend/app/admin/api/v1/sys/user.py b/backend/app/admin/api/v1/sys/user.py index 9f0fd71f0..edab3556c 100644 --- a/backend/app/admin/api/v1/sys/user.py +++ b/backend/app/admin/api/v1/sys/user.py @@ -13,6 +13,7 @@ UpdateUserParam, ) from backend.app.admin.service.user_service import user_service +from backend.common.enums import UserPermissionType from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base from backend.common.security.jwt import DependsJwtAuth @@ -23,42 +24,23 @@ router = APIRouter() -@router.post('/add', summary='添加用户', dependencies=[DependsRBAC]) -async def add_user(request: Request, obj: AddUserParam) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]: - await user_service.add(request=request, obj=obj) - data = await user_service.get_userinfo(username=obj.username) - return response_base.success(data=data) - - -@router.post('/{username}/password', summary='密码重置', dependencies=[DependsJwtAuth]) -async def password_reset( - username: Annotated[str, Path(description='用户名')], obj: ResetPasswordParam -) -> ResponseModel: - count = await user_service.pwd_reset(username=username, obj=obj) - if count > 0: - return response_base.success() - return response_base.fail() - - @router.get('/me', summary='获取当前用户信息', dependencies=[DependsJwtAuth]) async def get_current_user(request: Request) -> ResponseSchemaModel[GetCurrentUserInfoWithRelationDetail]: data = request.user.model_dump() return response_base.success(data=data) -@router.get('/{username}', summary='查看用户信息', dependencies=[DependsJwtAuth]) -async def get_user( - username: Annotated[str, Path(description='用户名')], +@router.get('/{pk}', summary='获取用户信息', dependencies=[DependsJwtAuth]) +async def get_userinfo( + pk: Annotated[int, Path(description='用户 ID')], ) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]: - data = await user_service.get_userinfo(username=username) + data = await user_service.get_userinfo(pk=pk) return response_base.success(data=data) -@router.get('/{username}/roles', summary='获取用户所有角色', dependencies=[DependsJwtAuth]) -async def get_user_all_roles( - username: Annotated[str, Path(description='用户名')], -) -> ResponseSchemaModel[list[GetRoleDetail]]: - data = await user_service.get_roles(username=username) +@router.get('/{pk}/roles', summary='获取用户所有角色', dependencies=[DependsJwtAuth]) +async def get_user_roles(pk: Annotated[int, Path(description='用户 ID')]) -> ResponseSchemaModel[list[GetRoleDetail]]: + data = await user_service.get_roles(pk=pk) return response_base.success(data=data) @@ -70,7 +52,7 @@ async def get_user_all_roles( DependsPagination, ], ) -async def get_pagination_users( +async def get_users_paged( db: CurrentSession, dept: Annotated[int | None, Query(description='部门 ID')] = None, username: Annotated[str | None, Query(description='用户名')] = None, @@ -82,58 +64,55 @@ async def get_pagination_users( return response_base.success(data=page_data) -@router.put('/{username}', summary='更新用户信息', dependencies=[DependsJwtAuth]) -async def update_user( - request: Request, username: Annotated[str, Path(description='用户名')], obj: UpdateUserParam -) -> ResponseModel: - count = await user_service.update(request=request, username=username, obj=obj) - if count > 0: - return response_base.success() - return response_base.fail() - - -@router.put('/{pk}/super', summary='修改用户超级权限', dependencies=[DependsRBAC]) -async def super_set(request: Request, pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel: - count = await user_service.update_permission(request=request, pk=pk) - if count > 0: - return response_base.success() - return response_base.fail() +@router.post('', summary='创建用户', dependencies=[DependsRBAC]) +async def create_user(request: Request, obj: AddUserParam) -> ResponseSchemaModel[GetUserInfoWithRelationDetail]: + await user_service.create(request=request, obj=obj) + data = await user_service.get_userinfo(username=obj.username) + return response_base.success(data=data) -@router.put('/{pk}/staff', summary='修改用户后台登录权限', dependencies=[DependsRBAC]) -async def staff_set(request: Request, pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel: - count = await user_service.update_staff(request=request, pk=pk) +@router.put('/{pk}', summary='更新用户信息', dependencies=[DependsJwtAuth]) +async def update_user( + request: Request, pk: Annotated[int, Path(description='用户 ID')], obj: UpdateUserParam +) -> ResponseModel: + count = await user_service.update(request=request, pk=pk, obj=obj) if count > 0: return response_base.success() return response_base.fail() -@router.put('/{pk}/status', summary='修改用户状态', dependencies=[DependsRBAC]) -async def status_set(request: Request, pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel: - count = await user_service.update_status(request=request, pk=pk) +@router.put('/{pk}/permissions', summary='更新用户权限', dependencies=[DependsRBAC]) +async def update_user_permission( + request: Request, + pk: Annotated[int, Path(description='用户 ID')], + type: Annotated[UserPermissionType, Query(description='权限类型')], +) -> ResponseModel: + count = await user_service.update_permission(request=request, pk=pk, type=type) if count > 0: return response_base.success() return response_base.fail() -@router.put('/{pk}/multi', summary='修改用户多端登录状态', dependencies=[DependsRBAC]) -async def multi_set(request: Request, pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel: - count = await user_service.update_multi_login(request=request, pk=pk) +@router.put('/{pk}/password', summary='重置用户密码', dependencies=[DependsJwtAuth]) +async def reset_user_password( + pk: Annotated[int, Path(description='用户 ID')], obj: ResetPasswordParam +) -> ResponseModel: + count = await user_service.reset_pwd(pk=pk, obj=obj) if count > 0: return response_base.success() return response_base.fail() @router.delete( - path='/{username}', + path='/{pk}', summary='删除用户', dependencies=[ Depends(RequestPermission('sys:user:del')), DependsRBAC, ], ) -async def delete_user(username: Annotated[str, Path(description='用户名')]) -> ResponseModel: - count = await user_service.delete(username=username) +async def delete_user(pk: Annotated[int, Path(description='用户 ID')]) -> ResponseModel: + count = await user_service.delete(pk=pk) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/app/admin/crud/crud_data_rule.py b/backend/app/admin/crud/crud_data_rule.py index f71f06987..5eaec5127 100644 --- a/backend/app/admin/crud/crud_data_rule.py +++ b/backend/app/admin/crud/crud_data_rule.py @@ -77,15 +77,15 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateDataRuleParam) -> i """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除规则 + 批量删除规则 :param db: 数据库会话 - :param pk: 规则 ID 列表 + :param pks: 规则 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) data_rule_dao: CRUDDataRule = CRUDDataRule(DataRule) diff --git a/backend/app/admin/crud/crud_data_scope.py b/backend/app/admin/crud/crud_data_scope.py index 9e7be3620..1239fe156 100644 --- a/backend/app/admin/crud/crud_data_scope.py +++ b/backend/app/admin/crud/crud_data_scope.py @@ -105,15 +105,15 @@ async def update_rules(self, db: AsyncSession, pk: int, rule_ids: UpdateDataScop current_data_scope.rules = rules.scalars().all() return len(current_data_scope.rules) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除数据范围 + 批量删除数据范围 :param db: 数据库会话 - :param pk: 范围 ID 列表 + :param pks: 范围 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) data_scope_dao: CRUDDataScope = CRUDDataScope(DataScope) diff --git a/backend/app/admin/crud/crud_login_log.py b/backend/app/admin/crud/crud_login_log.py index 6c726e16a..e4ba13a1b 100644 --- a/backend/app/admin/crud/crud_login_log.py +++ b/backend/app/admin/crud/crud_login_log.py @@ -41,15 +41,15 @@ async def create(self, db: AsyncSession, obj: CreateLoginLogParam) -> None: """ await self.create_model(db, obj, commit=True) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除登录日志 + 批量删除登录日志 :param db: 数据库会话 - :param pk: 登录日志 ID 列表 + :param pks: 登录日志 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) async def delete_all(self, db: AsyncSession) -> int: """ diff --git a/backend/app/admin/crud/crud_menu.py b/backend/app/admin/crud/crud_menu.py index 199fb8fda..ad2df3500 100644 --- a/backend/app/admin/crud/crud_menu.py +++ b/backend/app/admin/crud/crud_menu.py @@ -50,18 +50,17 @@ async def get_all(self, db: AsyncSession, title: str | None, status: int | None) return await self.select_models_order(db, 'sort', **filters) - async def get_sidebar(self, db: AsyncSession, superuser: bool, menu_ids: list[int | None]) -> Sequence[Menu]: + async def get_sidebar(self, db: AsyncSession, menu_ids: list[int] | None) -> Sequence[Menu]: """ - 获取角色菜单列表 + 获取用户的菜单侧边栏 :param db: 数据库会话 - :param superuser: 是否超级管理员 :param menu_ids: 菜单 ID 列表 :return: """ filters = {'type__in': [0, 1, 3, 4]} - if not superuser: + if menu_ids: filters['id__in'] = menu_ids return await self.select_models_order(db, 'sort', 'asc', **filters) diff --git a/backend/app/admin/crud/crud_opera_log.py b/backend/app/admin/crud/crud_opera_log.py index c21a625ac..33b65b386 100644 --- a/backend/app/admin/crud/crud_opera_log.py +++ b/backend/app/admin/crud/crud_opera_log.py @@ -41,15 +41,15 @@ async def create(self, db: AsyncSession, obj: CreateOperaLogParam) -> None: """ await self.create_model(db, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除操作日志 + 批量删除操作日志 :param db: 数据库会话 - :param pk: 操作日志 ID 列表 + :param pks: 操作日志 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) async def delete_all(self, db: AsyncSession) -> int: """ diff --git a/backend/app/admin/crud/crud_role.py b/backend/app/admin/crud/crud_role.py index 7721a4a31..e5b64a08d 100644 --- a/backend/app/admin/crud/crud_role.py +++ b/backend/app/admin/crud/crud_role.py @@ -134,15 +134,15 @@ async def update_scopes(self, db: AsyncSession, role_id: int, scope_ids: UpdateR current_role.scopes = scopes.scalars().all() return len(current_role.scopes) - async def delete(self, db: AsyncSession, role_id: list[int]) -> int: + async def delete(self, db: AsyncSession, role_ids: list[int]) -> int: """ - 删除角色 + 批量删除角色 :param db: 数据库会话 - :param role_id: 角色 ID 列表 + :param role_ids: 角色 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=role_id) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=role_ids) role_dao: CRUDRole = CRUDRole(Role) diff --git a/backend/app/admin/schema/data_rule.py b/backend/app/admin/schema/data_rule.py index ff0456025..bd676f110 100644 --- a/backend/app/admin/schema/data_rule.py +++ b/backend/app/admin/schema/data_rule.py @@ -27,6 +27,12 @@ class UpdateDataRuleParam(DataRuleSchemaBase): """更新数据规则参数""" +class DeleteDataRuleParam(SchemaBase): + """删除数据规则参数""" + + pks: list[int] = Field(description='规则 ID 列表') + + class GetDataRuleDetail(DataRuleSchemaBase): """数据规则详情""" diff --git a/backend/app/admin/schema/data_scope.py b/backend/app/admin/schema/data_scope.py index abe8b96b6..e771fa10b 100644 --- a/backend/app/admin/schema/data_scope.py +++ b/backend/app/admin/schema/data_scope.py @@ -30,6 +30,12 @@ class UpdateDataScopeRuleParam(SchemaBase): rules: list[int] = Field(description='数据规则 ID 列表') +class DeleteDataScopeParam(SchemaBase): + """删除数据范围参数""" + + pks: list[int] = Field(description='数据范围 ID 列表') + + class GetDataScopeDetail(DataScopeBase): """数据范围详情""" diff --git a/backend/app/admin/schema/login_log.py b/backend/app/admin/schema/login_log.py index 3d122c43d..6a545393f 100644 --- a/backend/app/admin/schema/login_log.py +++ b/backend/app/admin/schema/login_log.py @@ -33,6 +33,12 @@ class UpdateLoginLogParam(LoginLogSchemaBase): """更新登录日志参数""" +class DeleteLoginLogParam(SchemaBase): + """删除登录日志参数""" + + pks: list[int] = Field(description='登录日志 ID 列表') + + class GetLoginLogDetail(LoginLogSchemaBase): """登录日志详情""" diff --git a/backend/app/admin/schema/opera_log.py b/backend/app/admin/schema/opera_log.py index 6d93d2691..f14415ef2 100644 --- a/backend/app/admin/schema/opera_log.py +++ b/backend/app/admin/schema/opera_log.py @@ -41,6 +41,12 @@ class UpdateOperaLogParam(OperaLogSchemaBase): """更新操作日志参数""" +class DeleteOperaLogParam(SchemaBase): + """删除操作日志参数""" + + pks: list[int] = Field(description='操作日志 ID 列表') + + class GetOperaLogDetail(OperaLogSchemaBase): """操作日志详情""" diff --git a/backend/app/admin/schema/role.py b/backend/app/admin/schema/role.py index 353c5ac49..209512a12 100644 --- a/backend/app/admin/schema/role.py +++ b/backend/app/admin/schema/role.py @@ -27,6 +27,12 @@ class UpdateRoleParam(RoleSchemaBase): """更新角色参数""" +class DeleteRoleParam(SchemaBase): + """删除角色参数""" + + pks: list[int] = Field(description='角色 ID 列表') + + class UpdateRoleMenuParam(SchemaBase): """更新角色菜单参数""" diff --git a/backend/app/admin/service/auth_service.py b/backend/app/admin/service/auth_service.py index e00c692bb..3395a6fb9 100644 --- a/backend/app/admin/service/auth_service.py +++ b/backend/app/admin/service/auth_service.py @@ -5,6 +5,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from starlette.background import BackgroundTask, BackgroundTasks +from backend.app.admin.crud.crud_menu import menu_dao from backend.app.admin.crud.crud_user import user_dao from backend.app.admin.model import User from backend.app.admin.schema.token import GetLoginToken, GetNewToken @@ -162,9 +163,34 @@ async def login( return data @staticmethod - async def new_token(*, request: Request) -> GetNewToken: + async def get_codes(*, request: Request) -> list[str]: """ - 获取新的访问令牌 + 获取用户权限码 + + :param request: FastAPI 请求对象 + :return: + """ + codes = set() + if request.user.is_superuser: + async with async_db_session.begin() as db: + menus = await menu_dao.get_all(db, None, None) + for menu in menus: + if menu.perms: + codes.add(*menu.perms.split(',')) + else: + roles = request.user.roles + if roles: + for role in roles: + for menu in role.menus: + if menu.perms: + codes.add(*menu.perms.split(',')) + + return list(codes) + + @staticmethod + async def refresh_token(*, request: Request) -> GetNewToken: + """ + 刷新令牌 :param request: FastAPI 请求对象 :return: diff --git a/backend/app/admin/service/data_rule_service.py b/backend/app/admin/service/data_rule_service.py index ec5eb4adb..ec0ac23b5 100644 --- a/backend/app/admin/service/data_rule_service.py +++ b/backend/app/admin/service/data_rule_service.py @@ -6,7 +6,12 @@ from backend.app.admin.crud.crud_data_rule import data_rule_dao from backend.app.admin.model import DataRule -from backend.app.admin.schema.data_rule import CreateDataRuleParam, GetDataRuleColumnDetail, UpdateDataRuleParam +from backend.app.admin.schema.data_rule import ( + CreateDataRuleParam, + DeleteDataRuleParam, + GetDataRuleColumnDetail, + UpdateDataRuleParam, +) from backend.common.exception import errors from backend.core.conf import settings from backend.database.db import async_db_session @@ -105,15 +110,15 @@ async def update(*, pk: int, obj: UpdateDataRuleParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteDataRuleParam) -> int: """ - 删除数据规则 + 批量删除数据规则 - :param pk: 规则 ID 列表 + :param obj: 规则 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await data_rule_dao.delete(db, pk) + count = await data_rule_dao.delete(db, obj.pks) return count diff --git a/backend/app/admin/service/data_scope_service.py b/backend/app/admin/service/data_scope_service.py index 66187d3b3..eb56901dd 100644 --- a/backend/app/admin/service/data_scope_service.py +++ b/backend/app/admin/service/data_scope_service.py @@ -6,7 +6,12 @@ from backend.app.admin.crud.crud_data_scope import data_scope_dao from backend.app.admin.model import DataScope -from backend.app.admin.schema.data_scope import CreateDataScopeParam, UpdateDataScopeParam, UpdateDataScopeRuleParam +from backend.app.admin.schema.data_scope import ( + CreateDataScopeParam, + DeleteDataScopeParam, + UpdateDataScopeParam, + UpdateDataScopeRuleParam, +) from backend.common.exception import errors from backend.core.conf import settings from backend.database.db import async_db_session @@ -112,17 +117,17 @@ async def update_data_scope_rule(*, pk: int, rule_ids: UpdateDataScopeRuleParam) return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteDataScopeParam) -> int: """ - 删除数据范围 + 批量删除数据范围 - :param pk: 范围 ID 列表 + :param obj: 范围 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await data_scope_dao.delete(db, pk) - for _pk in pk: - data_rule = await data_scope_dao.get(db, _pk) + count = await data_scope_dao.delete(db, obj.pks) + for pk in obj.pks: + data_rule = await data_scope_dao.get(db, pk) if data_rule: for role in await data_rule.awaitable_attrs.roles: for user in await role.awaitable_attrs.users: diff --git a/backend/app/admin/service/dept_service.py b/backend/app/admin/service/dept_service.py index 996ac1ea7..ab0a0e9a9 100644 --- a/backend/app/admin/service/dept_service.py +++ b/backend/app/admin/service/dept_service.py @@ -32,7 +32,7 @@ async def get(*, pk: int) -> Dept: return dept @staticmethod - async def get_dept_tree( + async def get_tree( *, request: Request, name: str | None, leader: str | None, phone: str | None, status: int | None ) -> list[dict[str, Any]]: """ diff --git a/backend/app/admin/service/login_log_service.py b/backend/app/admin/service/login_log_service.py index 8ae6a6413..6a333a1b4 100644 --- a/backend/app/admin/service/login_log_service.py +++ b/backend/app/admin/service/login_log_service.py @@ -7,7 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from backend.app.admin.crud.crud_login_log import login_log_dao -from backend.app.admin.schema.login_log import CreateLoginLogParam +from backend.app.admin.schema.login_log import CreateLoginLogParam, DeleteLoginLogParam from backend.common.log import log from backend.database.db import async_db_session @@ -71,15 +71,15 @@ async def create( log.error(f'登录日志创建失败: {e}') @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteLoginLogParam) -> int: """ - 删除登录日志 + 批量删除登录日志 - :param pk: 日志 ID 列表 + :param obj: 日志 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await login_log_dao.delete(db, pk) + count = await login_log_dao.delete(db, obj.pks) return count @staticmethod diff --git a/backend/app/admin/service/menu_service.py b/backend/app/admin/service/menu_service.py index 8267e2d6e..f9a3c0b78 100644 --- a/backend/app/admin/service/menu_service.py +++ b/backend/app/admin/service/menu_service.py @@ -32,7 +32,7 @@ async def get(*, pk: int) -> Menu: return menu @staticmethod - async def get_menu_tree(*, title: str | None, status: int | None) -> list[dict[str, Any]]: + async def get_tree(*, title: str | None, status: int | None) -> list[dict[str, Any]]: """ 获取菜单树形结构 @@ -54,21 +54,17 @@ async def get_sidebar(*, request: Request) -> list[dict[str, Any] | None]: :return: """ async with async_db_session() as db: - roles = request.user.roles - menu_tree = [] - if roles: - unique_menus = {} - for role in roles: - for menu in role.menus: - unique_menus[menu.id] = menu - all_ids = set(unique_menus.keys()) - valid_menu_ids = [ - menu_id - for menu_id, menu in unique_menus.items() - if menu.parent_id is None or menu.parent_id in all_ids - ] - menu_data = await menu_dao.get_sidebar(db, request.user.is_superuser, valid_menu_ids) - menu_tree = get_vben5_tree_data(menu_data) + if request.user.is_superuser: + menu_data = await menu_dao.get_sidebar(db, None) + else: + roles = request.user.roles + menu_ids = set() + if roles: + for role in roles: + for menu in role.menus: + menu_ids.add(menu.id) + menu_data = await menu_dao.get_sidebar(db, list(menu_ids)) + menu_tree = get_vben5_tree_data(menu_data) return menu_tree @staticmethod diff --git a/backend/app/admin/service/opera_log_service.py b/backend/app/admin/service/opera_log_service.py index bae664080..3016fef5f 100644 --- a/backend/app/admin/service/opera_log_service.py +++ b/backend/app/admin/service/opera_log_service.py @@ -3,7 +3,7 @@ from sqlalchemy import Select from backend.app.admin.crud.crud_opera_log import opera_log_dao -from backend.app.admin.schema.opera_log import CreateOperaLogParam +from backend.app.admin.schema.opera_log import CreateOperaLogParam, DeleteOperaLogParam from backend.database.db import async_db_session @@ -34,15 +34,15 @@ async def create(*, obj: CreateOperaLogParam) -> None: await opera_log_dao.create(db, obj) @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteOperaLogParam) -> int: """ - 删除操作日志 + 批量删除操作日志 - :param pk: 日志 ID 列表 + :param obj: 日志 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await opera_log_dao.delete(db, pk) + count = await opera_log_dao.delete(db, obj.pks) return count @staticmethod diff --git a/backend/app/admin/service/plugin_service.py b/backend/app/admin/service/plugin_service.py index a1fe675b5..9120dd91b 100644 --- a/backend/app/admin/service/plugin_service.py +++ b/backend/app/admin/service/plugin_service.py @@ -11,7 +11,7 @@ from dulwich import porcelain from fastapi import UploadFile -from backend.common.enums import StatusType +from backend.common.enums import PluginType, StatusType from backend.common.exception import errors from backend.common.log import log from backend.core.conf import settings @@ -41,7 +41,7 @@ async def get_all() -> list[dict[str, Any]]: @staticmethod async def changed() -> str | None: - """插件状态是否变更""" + """检查插件是否发生变更""" return await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:changed') @staticmethod @@ -113,6 +113,24 @@ async def install_git(*, repo_url: str): await install_requirements_async(repo_name) await redis_client.set(f'{settings.PLUGIN_REDIS_PREFIX}:changed', 'ture') + async def install(self, *, type: PluginType, file: UploadFile | None = None, repo_url: str | None = None): + """ + 安装插件 + + :param type: 插件类型 + :param file: 插件 zip 压缩包 + :param repo_url: git 仓库地址 + :return: + """ + if type == PluginType.zip: + if not file: + raise errors.ForbiddenError(msg='ZIP 压缩包不能为空') + await self.install_zip(file=file) + elif type == PluginType.git: + if not repo_url: + raise errors.ForbiddenError(msg='Git 仓库地址不能为空') + await self.install_git(repo_url=repo_url) + @staticmethod async def uninstall(*, plugin: str): """ diff --git a/backend/app/admin/service/role_service.py b/backend/app/admin/service/role_service.py index 9708348fd..08854bf43 100644 --- a/backend/app/admin/service/role_service.py +++ b/backend/app/admin/service/role_service.py @@ -10,6 +10,7 @@ from backend.app.admin.model import Role from backend.app.admin.schema.role import ( CreateRoleParam, + DeleteRoleParam, UpdateRoleMenuParam, UpdateRoleParam, UpdateRoleScopeParam, @@ -166,17 +167,17 @@ async def update_role_scope(*, pk: int, scope_ids: UpdateRoleScopeParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteRoleParam) -> int: """ - 删除角色 + 批量删除角色 - :param pk: 角色 ID 列表 + :param obj: 角色 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await role_dao.delete(db, pk) - for _pk in pk: - role = await role_dao.get(db, _pk) + count = await role_dao.delete(db, obj.pks) + for pk in obj.pks: + role = await role_dao.get(db, pk) if role: for user in await role.awaitable_attrs.users: await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}') diff --git a/backend/app/admin/service/user_service.py b/backend/app/admin/service/user_service.py index 4c0f58113..acdcc9307 100644 --- a/backend/app/admin/service/user_service.py +++ b/backend/app/admin/service/user_service.py @@ -16,6 +16,7 @@ ResetPasswordParam, UpdateUserParam, ) +from backend.common.enums import UserPermissionType from backend.common.exception import errors from backend.common.security.jwt import get_hash_password, get_token, jwt_decode, password_verify, superuser_verify from backend.core.conf import settings @@ -27,86 +28,30 @@ class UserService: """用户服务类""" @staticmethod - async def add(*, request: Request, obj: AddUserParam) -> None: - """ - 添加新用户 - - :param request: FastAPI 请求对象 - :param obj: 用户添加参数 - :return: - """ - async with async_db_session.begin() as db: - superuser_verify(request) - username = await user_dao.get_by_username(db, obj.username) - if username: - raise errors.ForbiddenError(msg='用户已注册') - obj.nickname = obj.nickname if obj.nickname else f'#{random.randrange(88888, 99999)}' - nickname = await user_dao.get_by_nickname(db, obj.nickname) - if nickname: - raise errors.ForbiddenError(msg='昵称已注册') - if not obj.password: - raise errors.ForbiddenError(msg='密码为空') - dept = await dept_dao.get(db, obj.dept_id) - if not dept: - raise errors.NotFoundError(msg='部门不存在') - for role_id in obj.roles: - role = await role_dao.get(db, role_id) - if not role: - raise errors.NotFoundError(msg='角色不存在') - await user_dao.add(db, obj) - - @staticmethod - async def pwd_reset(*, username: str, obj: ResetPasswordParam) -> int: - """ - 重置用户密码 - - :param username: 用户名 - :param obj: 密码重置参数 - :return: - """ - async with async_db_session.begin() as db: - user = await user_dao.get_by_username(db, username) - if not user: - raise errors.NotFoundError(msg='用户不存在') - if not password_verify(obj.old_password, user.password): - raise errors.ForbiddenError(msg='原密码错误') - if obj.new_password != obj.confirm_password: - raise errors.ForbiddenError(msg='密码输入不一致') - new_pwd = get_hash_password(obj.new_password, user.salt) - count = await user_dao.reset_password(db, user.id, new_pwd) - key_prefix = [ - f'{settings.TOKEN_REDIS_PREFIX}:{user.id}', - f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}', - f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}', - ] - for prefix in key_prefix: - await redis_client.delete_prefix(prefix) - return count - - @staticmethod - async def get_userinfo(*, username: str) -> User: + async def get_userinfo(*, pk: int | None = None, username: str | None = None) -> User: """ 获取用户信息 + :param pk: 用户 ID :param username: 用户名 :return: """ async with async_db_session() as db: - user = await user_dao.get_with_relation(db, username=username) + user = await user_dao.get_with_relation(db, user_id=pk, username=username) if not user: raise errors.NotFoundError(msg='用户不存在') return user @staticmethod - async def get_roles(*, username: str) -> Sequence[Role]: + async def get_roles(*, pk: int) -> Sequence[Role]: """ 获取用户所有角色 - :param username: 用户名 + :param pk: 用户 ID :return: """ async with async_db_session() as db: - user = await user_dao.get_with_relation(db, username=username) + user = await user_dao.get_with_relation(db, user_id=pk) if not user: raise errors.NotFoundError(msg='用户不存在') return user.roles @@ -125,41 +70,58 @@ async def get_select(*, dept: int, username: str, phone: str, status: int) -> Se return await user_dao.get_list(dept=dept, username=username, phone=phone, status=status) @staticmethod - async def update(*, request: Request, username: str, obj: UpdateUserParam) -> int: + async def create(*, request: Request, obj: AddUserParam) -> None: + """ + 创建用户 + + :param request: FastAPI 请求对象 + :param obj: 用户添加参数 + :return: + """ + async with async_db_session.begin() as db: + superuser_verify(request) + if await user_dao.get_by_username(db, obj.username): + raise errors.ForbiddenError(msg='用户名已注册') + obj.nickname = obj.nickname if obj.nickname else f'#{random.randrange(88888, 99999)}' + if not obj.password: + raise errors.ForbiddenError(msg='密码不允许为空') + if not await dept_dao.get(db, obj.dept_id): + raise errors.NotFoundError(msg='部门不存在') + for role_id in obj.roles: + if not await role_dao.get(db, role_id): + raise errors.NotFoundError(msg='角色不存在') + await user_dao.add(db, obj) + + @staticmethod + async def update(*, request: Request, pk: int, obj: UpdateUserParam) -> int: """ 更新用户信息 :param request: FastAPI 请求对象 - :param username: 用户名 + :param pk: 用户 ID :param obj: 用户更新参数 :return: """ async with async_db_session.begin() as db: - if request.user.username != username: - raise errors.ForbiddenError(msg='你只能修改自己的信息') - user = await user_dao.get_with_relation(db, username=username) + user = await user_dao.get_with_relation(db, user_id=pk) if not user: raise errors.NotFoundError(msg='用户不存在') - if user.username != obj.username: - _username = await user_dao.get_by_username(db, obj.username) - if _username: + if request.user.username != user.username: + raise errors.ForbiddenError(msg='只能修改自己的信息') + if obj.username != user.username: + if await user_dao.get_by_username(db, obj.username): raise errors.ForbiddenError(msg='用户名已注册') - if user.nickname != obj.nickname: - nickname = await user_dao.get_by_nickname(db, obj.nickname) - if nickname: - raise errors.ForbiddenError(msg='昵称已注册') for role_id in obj.roles: - role = await role_dao.get(db, role_id) - if not role: + if not await role_dao.get(db, role_id): raise errors.NotFoundError(msg='角色不存在') count = await user_dao.update(db, user, obj) await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}') return count @staticmethod - async def update_permission(*, request: Request, pk: int) -> int: + async def update_superuser(*, request: Request, pk: int) -> int: """ - 更新用户权限 + 更新用户管理员状态 :param request: FastAPI 请求对象 :param pk: 用户 ID @@ -171,7 +133,7 @@ async def update_permission(*, request: Request, pk: int) -> int: if not user: raise errors.NotFoundError(msg='用户不存在') if pk == request.user.id: - raise errors.ForbiddenError(msg='非法操作') + raise errors.ForbiddenError(msg='禁止修改自身权限') super_status = await user_dao.get_super(db, pk) count = await user_dao.set_super(db, pk, not super_status) await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}') @@ -192,7 +154,7 @@ async def update_staff(*, request: Request, pk: int) -> int: if not user: raise errors.NotFoundError(msg='用户不存在') if pk == request.user.id: - raise errors.ForbiddenError(msg='非法操作') + raise errors.ForbiddenError(msg='禁止修改自身权限') staff_status = await user_dao.get_staff(db, pk) count = await user_dao.set_staff(db, pk, not staff_status) await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}') @@ -213,7 +175,7 @@ async def update_status(*, request: Request, pk: int) -> int: if not user: raise errors.NotFoundError(msg='用户不存在') if pk == request.user.id: - raise errors.ForbiddenError(msg='非法操作') + raise errors.ForbiddenError(msg='禁止修改自身权限') status = await user_dao.get_status(db, pk) count = await user_dao.set_status(db, pk, 0 if status == 1 else 1) await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}') @@ -251,16 +213,65 @@ async def update_multi_login(*, request: Request, pk: int) -> int: await redis_client.delete_prefix(key_prefix) return count + async def update_permission(self, *, request: Request, pk: int, type: UserPermissionType) -> int: + """ + 更新用户权限 + + :param request: FastAPI 请求对象 + :param pk: 用户 ID + :param type: 权限类型 + :return: + """ + if type == UserPermissionType.superuser: + count = await self.update_superuser(request=request, pk=pk) + elif type == UserPermissionType.staff: + count = await self.update_staff(request=request, pk=pk) + elif type == UserPermissionType.status: + count = await self.update_status(request=request, pk=pk) + elif type == UserPermissionType.multi_login: + count = await self.update_multi_login(request=request, pk=pk) + else: + raise errors.ForbiddenError(msg='权限类型不存在') + return count + @staticmethod - async def delete(*, username: str) -> int: + async def reset_pwd(*, pk: int, obj: ResetPasswordParam) -> int: + """ + 重置用户密码 + + :param pk: 用户 ID + :param obj: 密码重置参数 + :return: + """ + async with async_db_session.begin() as db: + user = await user_dao.get(db, pk) + if not user: + raise errors.NotFoundError(msg='用户不存在') + if not password_verify(obj.old_password, user.password): + raise errors.ForbiddenError(msg='原密码错误') + if obj.new_password != obj.confirm_password: + raise errors.ForbiddenError(msg='密码输入不一致') + new_pwd = get_hash_password(obj.new_password, user.salt) + count = await user_dao.reset_password(db, user.id, new_pwd) + key_prefix = [ + f'{settings.TOKEN_REDIS_PREFIX}:{user.id}', + f'{settings.TOKEN_REFRESH_REDIS_PREFIX}:{user.id}', + f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}', + ] + for prefix in key_prefix: + await redis_client.delete_prefix(prefix) + return count + + @staticmethod + async def delete(*, pk: int) -> int: """ 删除用户 - :param username: 用户名 + :param pk: 用户 ID :return: """ async with async_db_session.begin() as db: - user = await user_dao.get_by_username(db, username) + user = await user_dao.get(db, pk) if not user: raise errors.NotFoundError(msg='用户不存在') count = await user_dao.delete(db, user.id) diff --git a/backend/app/task/api/v1/task.py b/backend/app/task/api/v1/task.py index 5f66c5ec0..91bd779dc 100644 --- a/backend/app/task/api/v1/task.py +++ b/backend/app/task/api/v1/task.py @@ -14,12 +14,6 @@ router = APIRouter() -@router.get('', summary='获取可执行任务', dependencies=[DependsJwtAuth]) -async def get_all_tasks() -> ResponseSchemaModel[list[str]]: - tasks = await task_service.get_list() - return response_base.success(data=tasks) - - @router.get( '/{tid}', summary='获取任务详情', @@ -27,12 +21,18 @@ async def get_all_tasks() -> ResponseSchemaModel[list[str]]: description='此接口被视为作废,建议使用 flower 查看任务详情', dependencies=[DependsJwtAuth], ) -async def get_task_detail(tid: Annotated[str, Path(description='任务 UUID')]) -> ResponseSchemaModel[TaskResult]: - status = task_service.get_detail(tid=tid) +async def get_task(tid: Annotated[str, Path(description='任务 UUID')]) -> ResponseSchemaModel[TaskResult]: + status = task_service.get(tid=tid) return response_base.success(data=status) -@router.post( +@router.get('', summary='获取所有任务', dependencies=[DependsJwtAuth]) +async def get_all_tasks() -> ResponseSchemaModel[list[str]]: + tasks = await task_service.get_all() + return response_base.success(data=tasks) + + +@router.delete( '/{tid}', summary='撤销任务', dependencies=[ @@ -46,8 +46,8 @@ async def revoke_task(tid: Annotated[str, Path(description='任务 UUID')]) -> R @router.post( - '', - summary='执行任务', + '/runs', + summary='运行任务', dependencies=[ Depends(RequestPermission('sys:task:run')), DependsRBAC, diff --git a/backend/app/task/service/task_service.py b/backend/app/task/service/task_service.py index 984cfc94a..7061e848a 100644 --- a/backend/app/task/service/task_service.py +++ b/backend/app/task/service/task_service.py @@ -11,16 +11,7 @@ class TaskService: @staticmethod - async def get_list() -> list[str]: - """获取所有已注册的 Celery 任务列表""" - registered_tasks = await run_in_threadpool(celery_app.control.inspect().registered) - if not registered_tasks: - raise errors.ForbiddenError(msg='Celery 服务未启动') - tasks = list(registered_tasks.values())[0] - return tasks - - @staticmethod - def get_detail(*, tid: str) -> TaskResult: + def get(*, tid: str) -> TaskResult: """ 获取指定任务的详细信息 @@ -43,6 +34,15 @@ def get_detail(*, tid: str) -> TaskResult: queue=result.queue, ) + @staticmethod + async def get_all() -> list[str]: + """获取所有已注册的 Celery 任务列表""" + registered_tasks = await run_in_threadpool(celery_app.control.inspect().registered) + if not registered_tasks: + raise errors.ForbiddenError(msg='Celery 服务未启动') + tasks = list(registered_tasks.values())[0] + return tasks + @staticmethod def revoke(*, tid: str) -> None: """ diff --git a/backend/common/enums.py b/backend/common/enums.py index 54b88a608..efbf7bb0d 100644 --- a/backend/common/enums.py +++ b/backend/common/enums.py @@ -121,3 +121,19 @@ class FileType(StrEnum): image = 'image' video = 'video' + + +class PluginType(StrEnum): + """插件类型""" + + zip = 'zip' + git = 'git' + + +class UserPermissionType(StrEnum): + """用户权限类型""" + + superuser = 'superuser' + staff = 'staff' + status = 'status' + multi_login = 'multi_login' diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py index 20e64e13a..ec106065c 100644 --- a/backend/common/security/jwt.py +++ b/backend/common/security/jwt.py @@ -231,7 +231,7 @@ def superuser_verify(request: Request) -> bool: """ superuser = request.user.is_superuser if not superuser or not request.user.is_staff: - raise errors.AuthorizationError + raise errors.AuthorizationError() return superuser diff --git a/backend/common/security/permission.py b/backend/common/security/permission.py index 66f4d39b7..afa182f83 100644 --- a/backend/common/security/permission.py +++ b/backend/common/security/permission.py @@ -64,23 +64,20 @@ async def filter_data_permission(db: AsyncSession, request: Request) -> ColumnEl return or_(1 == 1) # 获取数据范围 - unique_data_scopes = {} + data_scope_ids = set() for role in request.user.roles: for scope in role.scopes: if scope.status: - unique_data_scopes[scope.id] = scope - - # 转换为列表 - data_scopes = list(unique_data_scopes.values()) + data_scope_ids.add(scope.id) # 无规则用户不做过滤 - if not data_scopes: + if not list(data_scope_ids): return or_(1 == 1) # 获取数据范围规则 unique_data_rules = {} - for data_scope in data_scopes: - data_scope_with_relation = await data_scope_dao.get_with_relation(db, data_scope.id) + for data_scope_id in list(data_scope_ids): + data_scope_with_relation = await data_scope_dao.get_with_relation(db, data_scope_id) for rule in data_scope_with_relation.rules: unique_data_rules[rule.id] = rule diff --git a/backend/plugin/code_generator/api/router.py b/backend/plugin/code_generator/api/router.py index 61068dd98..43d6241f0 100644 --- a/backend/plugin/code_generator/api/router.py +++ b/backend/plugin/code_generator/api/router.py @@ -9,6 +9,6 @@ v1 = APIRouter(prefix=f'{settings.FASTAPI_API_V1_PATH}/gen', tags=['代码生成']) -v1.include_router(gen_router, prefix='/tables') v1.include_router(business_router, prefix='/businesses') v1.include_router(model_router, prefix='/models') +v1.include_router(gen_router, prefix='/codes') diff --git a/backend/plugin/code_generator/api/v1/business.py b/backend/plugin/code_generator/api/v1/business.py index f95dbeec8..276421558 100644 --- a/backend/plugin/code_generator/api/v1/business.py +++ b/backend/plugin/code_generator/api/v1/business.py @@ -20,12 +20,6 @@ router = APIRouter() -@router.get('/all', summary='获取所有代码生成业务', dependencies=[DependsJwtAuth]) -async def get_all_businesses() -> ResponseSchemaModel[list[GetGenBusinessDetail]]: - data = await gen_business_service.get_all() - return response_base.success(data=data) - - @router.get('/{pk}', summary='获取代码生成业务详情', dependencies=[DependsJwtAuth]) async def get_business( pk: Annotated[int, Path(description='业务 ID')], @@ -34,11 +28,17 @@ async def get_business( return response_base.success(data=data) +@router.get('', summary='获取所有代码生成业务', dependencies=[DependsJwtAuth]) +async def get_all_businesses() -> ResponseSchemaModel[list[GetGenBusinessDetail]]: + data = await gen_business_service.get_all() + return response_base.success(data=data) + + @router.get('/{pk}/models', summary='获取代码生成业务所有模型', dependencies=[DependsJwtAuth]) async def get_business_all_models( pk: Annotated[int, Path(description='业务 ID')], ) -> ResponseSchemaModel[list[GetGenModelDetail]]: - data = await gen_model_service.get_by_business(business_id=pk) + data = await gen_model_service.get_models(business_id=pk) return response_base.success(data=data) @@ -47,7 +47,7 @@ async def get_business_all_models( summary='创建代码生成业务', deprecated=True, dependencies=[ - Depends(RequestPermission('gen:code:business:add')), + Depends(RequestPermission('codegen:business:add')), DependsRBAC, ], ) @@ -60,7 +60,7 @@ async def create_business(obj: CreateGenBusinessParam) -> ResponseModel: '/{pk}', summary='更新代码生成业务', dependencies=[ - Depends(RequestPermission('gen:code:business:edit')), + Depends(RequestPermission('codegen:business:edit')), DependsRBAC, ], ) @@ -77,7 +77,7 @@ async def update_business( '/{pk}', summary='删除代码生成业务', dependencies=[ - Depends(RequestPermission('gen:code:business:del')), + Depends(RequestPermission('codegen:business:del')), DependsRBAC, ], ) diff --git a/backend/plugin/code_generator/api/v1/column.py b/backend/plugin/code_generator/api/v1/column.py index 8e75ea716..1e5b96965 100644 --- a/backend/plugin/code_generator/api/v1/column.py +++ b/backend/plugin/code_generator/api/v1/column.py @@ -30,7 +30,7 @@ async def get_model(pk: Annotated[int, Path(description='模型 ID')]) -> Respon '', summary='创建代码生成模型', dependencies=[ - Depends(RequestPermission('gen:code:model:add')), + Depends(RequestPermission('codegen:model:add')), DependsRBAC, ], ) @@ -43,7 +43,7 @@ async def create_model(obj: CreateGenModelParam) -> ResponseModel: '/{pk}', summary='更新代码生成模型', dependencies=[ - Depends(RequestPermission('gen:code:model:edit')), + Depends(RequestPermission('codegen:model:edit')), DependsRBAC, ], ) @@ -58,7 +58,7 @@ async def update_model(pk: Annotated[int, Path(description='模型 ID')], obj: U '/{pk}', summary='删除代码生成模型', dependencies=[ - Depends(RequestPermission('gen:code:model:del')), + Depends(RequestPermission('codegen:model:del')), DependsRBAC, ], ) diff --git a/backend/plugin/code_generator/api/v1/gen.py b/backend/plugin/code_generator/api/v1/gen.py index dac89af88..cf11f835a 100644 --- a/backend/plugin/code_generator/api/v1/gen.py +++ b/backend/plugin/code_generator/api/v1/gen.py @@ -16,7 +16,7 @@ router = APIRouter() -@router.get('', summary='获取数据库表') +@router.get('/tables', summary='获取数据库表') async def get_all_tables( table_schema: Annotated[str, Query(description='数据库名')] = 'fba', ) -> ResponseSchemaModel[list[str]]: @@ -25,10 +25,10 @@ async def get_all_tables( @router.post( - '/import', + '/imports', summary='导入代码生成业务和模型列', dependencies=[ - Depends(RequestPermission('gen:code:import')), + Depends(RequestPermission('codegen:table:import')), DependsRBAC, ], ) @@ -37,24 +37,24 @@ async def import_table(obj: ImportParam) -> ResponseModel: return response_base.success() -@router.get('/{pk}/preview', summary='生成代码预览', dependencies=[DependsJwtAuth]) +@router.get('/{pk}/previews', summary='代码生成预览', dependencies=[DependsJwtAuth]) async def preview_code(pk: Annotated[int, Path(description='业务 ID')]) -> ResponseSchemaModel[dict[str, bytes]]: data = await gen_service.preview(pk=pk) return response_base.success(data=data) -@router.get('/{pk}/code/path', summary='获取代码生成路径', dependencies=[DependsJwtAuth]) -async def generate_path(pk: Annotated[int, Path(description='业务 ID')]) -> ResponseSchemaModel[list[str]]: +@router.get('/{pk}/paths', summary='获取代码生成路径', dependencies=[DependsJwtAuth]) +async def get_generate_paths(pk: Annotated[int, Path(description='业务 ID')]) -> ResponseSchemaModel[list[str]]: data = await gen_service.get_generate_path(pk=pk) return response_base.success(data=data) @router.post( - '/{pk}/code', + '/{pk}/generation', summary='代码生成', description='文件磁盘写入,请谨慎操作', dependencies=[ - Depends(RequestPermission('gen:code:write')), + Depends(RequestPermission('codegen:local:write')), DependsRBAC, ], ) diff --git a/backend/plugin/code_generator/service/column_service.py b/backend/plugin/code_generator/service/column_service.py index 781f4d782..efcfa7cb9 100644 --- a/backend/plugin/code_generator/service/column_service.py +++ b/backend/plugin/code_generator/service/column_service.py @@ -36,7 +36,7 @@ async def get_types() -> list[str]: return types @staticmethod - async def get_by_business(*, business_id: int) -> Sequence[GenColumn]: + async def get_models(*, business_id: int) -> Sequence[GenColumn]: """ 获取指定业务的所有模型 diff --git a/backend/plugin/code_generator/service/gen_service.py b/backend/plugin/code_generator/service/gen_service.py index 71c1d6036..ab28743a6 100644 --- a/backend/plugin/code_generator/service/gen_service.py +++ b/backend/plugin/code_generator/service/gen_service.py @@ -99,7 +99,7 @@ async def render_tpl_code(*, business: GenBusiness) -> dict[str, str]: :param business: 业务对象 :return: """ - gen_models = await gen_model_service.get_by_business(business_id=business.id) + gen_models = await gen_model_service.get_models(business_id=business.id) if not gen_models: raise errors.NotFoundError(msg='代码生成模型表为空') diff --git a/backend/plugin/code_generator/templates/python/api.jinja b/backend/plugin/code_generator/templates/python/api.jinja index 68587136c..5b0687447 100644 --- a/backend/plugin/code_generator/templates/python/api.jinja +++ b/backend/plugin/code_generator/templates/python/api.jinja @@ -4,7 +4,12 @@ from typing import Annotated from fastapi import APIRouter, Depends, Path, Query -from backend.app.{{ app_name }}.schema.{{ table_name }} import Create{{ schema_name }}Param, Get{{ schema_name }}Detail, Update{{ schema_name }}Param +from backend.app.{{ app_name }}.schema.{{ table_name }} import ( + Create{{ schema_name }}Param, + Delete{{ schema_name }}Param, + Get{{ schema_name }}Detail, + Update{{ schema_name }}Param, +) from backend.app.{{ app_name }}.service.{{ table_name }}_service import {{ table_name }}_service from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base @@ -30,7 +35,7 @@ async def get_{{ table_name }}(pk: Annotated[int, Path(description='{{ doc_comme DependsPagination, ], ) -async def get_pagination_{{ table_name }}s(db: CurrentSession) -> ResponseSchemaModel[PageData[Get{{ schema_name }}Detail]]: +async def get_{{ table_name }}s_paged(db: CurrentSession) -> ResponseSchemaModel[PageData[Get{{ schema_name }}Detail]]: {{ table_name }}_select = await {{ table_name }}_service.get_select() page_data = await paging_data(db, {{ table_name }}_select) return response_base.success(data=page_data) @@ -72,8 +77,8 @@ async def update_{{ table_name }}(pk: Annotated[int, Path(description='{{ doc_co DependsRBAC, ], ) -async def delete_{{ table_name }}(pk: Annotated[list[int], Query(description='{{ doc_comment }} ID 列表')]) -> ResponseModel: - count = await {{ table_name }}_service.delete(pk=pk) +async def delete_{{ table_name }}s(obj: Delete{{ schema_name }}Param) -> ResponseModel: + count = await {{ table_name }}_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/plugin/code_generator/templates/python/crud.jinja b/backend/plugin/code_generator/templates/python/crud.jinja index 6673204ca..2d30bdd87 100644 --- a/backend/plugin/code_generator/templates/python/crud.jinja +++ b/backend/plugin/code_generator/templates/python/crud.jinja @@ -55,15 +55,15 @@ class CRUD{{ class_name }}(CRUDPlus[{{ schema_name }}]): """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除{{ doc_comment }} + 批量删除{{ doc_comment }} :param db: 数据库会话 - :param pk: {{ doc_comment }} ID + :param pks: {{ doc_comment }} ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) {{ instance_name }}_dao: CRUD{{ class_name }} = CRUD{{ class_name }}({{ class_name }}) diff --git a/backend/plugin/code_generator/templates/python/schema.jinja b/backend/plugin/code_generator/templates/python/schema.jinja index 230e82910..ae6309a5e 100644 --- a/backend/plugin/code_generator/templates/python/schema.jinja +++ b/backend/plugin/code_generator/templates/python/schema.jinja @@ -23,6 +23,10 @@ class Update{{ schema_name }}Param({{ schema_name }}SchemaBase): """更新{{ doc_comment }}参数""" +class Delete{{ schema_name }}Param({{ schema_name }}SchemaBase): + """删除{{ doc_comment }}参数""" + + class Get{{ schema_name }}Detail({{ schema_name }}SchemaBase): """{{ doc_comment }}详情""" diff --git a/backend/plugin/code_generator/templates/python/service.jinja b/backend/plugin/code_generator/templates/python/service.jinja index a621461dc..c00888790 100644 --- a/backend/plugin/code_generator/templates/python/service.jinja +++ b/backend/plugin/code_generator/templates/python/service.jinja @@ -6,7 +6,7 @@ from sqlalchemy import Select from backend.app.{{ app_name }}.crud.crud_{{ table_name }} import {{ table_name }}_dao from backend.app.{{ app_name }}.model import {{ class_name }} -from backend.app.{{ app_name }}.schema.{{ table_name }} import Create{{ schema_name }}Param, Update{{ schema_name }}Param +from backend.app.{{ app_name }}.schema.{{ table_name }} import Create{{ schema_name }}Param, Delete{{ schema_name }}Param, Update{{ schema_name }}Param from backend.common.exception import errors from backend.database.db import async_db_session @@ -63,15 +63,15 @@ class {{ class_name }}Service: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: Delete{{ schema_name }}Param) -> int: """ 删除{{ doc_comment }} - :param pk: {{ doc_comment }} ID 列表 + :param obj: {{ doc_comment }} ID 列表 :return: """ async with async_db_session.begin() as db: - count = await {{ table_name }}_dao.delete(db, pk) + count = await {{ table_name }}_dao.delete(db, obj.pks) return count diff --git a/backend/plugin/config/api/v1/sys/config.py b/backend/plugin/config/api/v1/sys/config.py index 60253c24e..be94cb670 100644 --- a/backend/plugin/config/api/v1/sys/config.py +++ b/backend/plugin/config/api/v1/sys/config.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Depends, Path, Query +from fastapi import APIRouter, Body, Depends, Path, Query from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base @@ -13,7 +13,6 @@ from backend.plugin.config.schema.config import ( CreateConfigParam, GetConfigDetail, - SaveBuiltInConfigParam, UpdateConfigParam, ) from backend.plugin.config.service.config_service import config_service @@ -21,66 +20,9 @@ router = APIRouter() -@router.get('/website', summary='获取网站参数配置', dependencies=[DependsJwtAuth]) -async def get_website_config() -> ResponseSchemaModel[list[GetConfigDetail]]: - config = await config_service.get_built_in_config('website') - return response_base.success(data=config) - - -@router.post( - '/website', - summary='保存网站参数配置', - dependencies=[ - Depends(RequestPermission('sys:config:website:add')), - DependsRBAC, - ], -) -async def save_website_config(objs: list[SaveBuiltInConfigParam]) -> ResponseModel: - await config_service.save_built_in_config(objs, 'website') - return response_base.success() - - -@router.get('/protocol', summary='获取用户协议', dependencies=[DependsJwtAuth]) -async def get_protocol_config() -> ResponseSchemaModel[list[GetConfigDetail]]: - config = await config_service.get_built_in_config('protocol') - return response_base.success(data=config) - - -@router.post( - '/protocol', - summary='保存用户协议', - dependencies=[ - Depends(RequestPermission('sys:config:protocol:add')), - DependsRBAC, - ], -) -async def save_protocol_config(objs: list[SaveBuiltInConfigParam]) -> ResponseModel: - await config_service.save_built_in_config(objs, 'protocol') - return response_base.success() - - -@router.get('/policy', summary='获取用户政策', dependencies=[DependsJwtAuth]) -async def get_policy_config() -> ResponseSchemaModel[list[GetConfigDetail]]: - config = await config_service.get_built_in_config('policy') - return response_base.success(data=config) - - -@router.post( - '/policy', - summary='保存用户政策', - dependencies=[ - Depends(RequestPermission('sys:config:policy:add')), - DependsRBAC, - ], -) -async def save_policy_config(objs: list[SaveBuiltInConfigParam]) -> ResponseModel: - await config_service.save_built_in_config(objs, 'policy') - return response_base.success() - - @router.get('/{pk}', summary='获取参数配置详情', dependencies=[DependsJwtAuth]) async def get_config(pk: Annotated[int, Path(description='参数配置 ID')]) -> ResponseSchemaModel[GetConfigDetail]: - config = await config_service.get(pk) + config = await config_service.get(pk=pk) return response_base.success(data=config) @@ -92,10 +34,10 @@ async def get_config(pk: Annotated[int, Path(description='参数配置 ID')]) -> DependsPagination, ], ) -async def get_pagination_configs( +async def get_configs_paged( db: CurrentSession, name: Annotated[str | None, Query(description='参数配置名称')] = None, - type: Annotated[str | None, Query()] = None, + type: Annotated[str | None, Query(description='参数配置类型')] = None, ) -> ResponseSchemaModel[PageData[GetConfigDetail]]: config_select = await config_service.get_select(name=name, type=type) page_data = await paging_data(db, config_select) @@ -138,8 +80,8 @@ async def update_config(pk: Annotated[int, Path(description='参数配置 ID')], DependsRBAC, ], ) -async def delete_config(pk: Annotated[list[int], Query(description='参数配置 ID 列表')]) -> ResponseModel: - count = await config_service.delete(pk=pk) +async def delete_configs(pks: Annotated[list[int], Body(description='参数配置 ID 列表')]) -> ResponseModel: + count = await config_service.delete(pks=pks) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/plugin/config/crud/crud_config.py b/backend/plugin/config/crud/crud_config.py index da096293a..fcffc6bcd 100644 --- a/backend/plugin/config/crud/crud_config.py +++ b/backend/plugin/config/crud/crud_config.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Sequence from sqlalchemy import Select from sqlalchemy.ext.asyncio import AsyncSession @@ -24,27 +23,6 @@ async def get(self, db: AsyncSession, pk: int) -> Config | None: """ return await self.select_model_by_column(db, id=pk, type__not_in=settings.CONFIG_BUILT_IN_TYPES) - async def get_by_type(self, db: AsyncSession, type: str) -> Sequence[Config]: - """ - 通过类型获取参数配置 - - :param db: 数据库会话 - :param type: 参数配置类型 - :return: - """ - return await self.select_models(db, type=type) - - async def get_by_key_and_type(self, db: AsyncSession, key: str, type: str) -> Config | None: - """ - 通过键名和类型获取参数配置 - - :param db: 数据库会话 - :param key: 参数配置键名 - :param type: 参数配置类型 - :return: - """ - return await self.select_model_by_column(db, key=key, type=type) - async def get_by_key(self, db: AsyncSession, key: str) -> Config | None: """ 通过键名获取参数配置 @@ -93,16 +71,16 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateConfigParam) -> int """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除参数配置 + 批量删除参数配置 :param db: 数据库会话 - :param pk: 参数配置 ID 列表 + :param pks: 参数配置 ID 列表 :return: """ return await self.delete_model_by_column( - db, allow_multiple=True, id__in=pk, type__not_in=settings.CONFIG_BUILT_IN_TYPES + db, allow_multiple=True, id__in=pks, type__not_in=settings.CONFIG_BUILT_IN_TYPES ) diff --git a/backend/plugin/config/schema/config.py b/backend/plugin/config/schema/config.py index c0216084d..4d9dc8f4a 100644 --- a/backend/plugin/config/schema/config.py +++ b/backend/plugin/config/schema/config.py @@ -7,14 +7,6 @@ from backend.common.schema import SchemaBase -class SaveBuiltInConfigParam(SchemaBase): - """保存内置参数配置参数""" - - name: str = Field(description='参数配置名称') - key: str = Field(description='参数配置键名') - value: str = Field(description='参数配置值') - - class ConfigSchemaBase(SchemaBase): """参数配置基础模型""" diff --git a/backend/plugin/config/service/config_service.py b/backend/plugin/config/service/config_service.py index eb54d3d72..bd799f747 100644 --- a/backend/plugin/config/service/config_service.py +++ b/backend/plugin/config/service/config_service.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from typing import Sequence from sqlalchemy import Select @@ -11,7 +10,6 @@ from backend.plugin.config.model import Config from backend.plugin.config.schema.config import ( CreateConfigParam, - SaveBuiltInConfigParam, UpdateConfigParam, ) @@ -20,37 +18,7 @@ class ConfigService: """参数配置服务类""" @staticmethod - async def get_built_in_config(type: str) -> Sequence[Config]: - """ - 获取内置参数配置 - - :param type: 参数配置类型 - :return: - """ - async with async_db_session() as db: - return await config_dao.get_by_type(db, type) - - @staticmethod - async def save_built_in_config(objs: list[SaveBuiltInConfigParam], type: str) -> None: - """ - 保存内置参数配置 - - :param objs: 参数配置参数列表 - :param type: 参数配置类型 - :return: - """ - async with async_db_session.begin() as db: - for obj in objs: - config = await config_dao.get_by_key_and_type(db, obj.key, type) - if config is None: - if await config_dao.get_by_key(db, obj.key): - raise errors.ForbiddenError(msg=f'参数配置 {obj.key} 已存在') - await config_dao.create_model(db, obj, flush=True, type=type) - else: - await config_dao.update_model(db, config.id, obj, type=type) - - @staticmethod - async def get(pk: int) -> Config: + async def get(*, pk: int) -> Config: """ 获取参数配置详情 @@ -111,15 +79,15 @@ async def update(*, pk: int, obj: UpdateConfigParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, pks: list[int]) -> int: """ - 删除参数配置 + 批量删除参数配置 - :param pk: 参数配置 ID 列表 + :param pks: 参数配置 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await config_dao.delete(db, pk) + count = await config_dao.delete(db, pks) return count diff --git a/backend/plugin/dict/api/v1/sys/dict_data.py b/backend/plugin/dict/api/v1/sys/dict_data.py index 938adf6e5..c258c2150 100644 --- a/backend/plugin/dict/api/v1/sys/dict_data.py +++ b/backend/plugin/dict/api/v1/sys/dict_data.py @@ -12,6 +12,7 @@ from backend.database.db import CurrentSession from backend.plugin.dict.schema.dict_data import ( CreateDictDataParam, + DeleteDictDataParam, GetDictDataDetail, GetDictDataWithRelation, UpdateDictDataParam, @@ -21,7 +22,7 @@ router = APIRouter() -@router.get('/{pk}', summary='获取字典详情', dependencies=[DependsJwtAuth]) +@router.get('/{pk}', summary='获取字典数据详情', dependencies=[DependsJwtAuth]) async def get_dict_data( pk: Annotated[int, Path(description='字典数据 ID')], ) -> ResponseSchemaModel[GetDictDataWithRelation]: @@ -31,13 +32,13 @@ async def get_dict_data( @router.get( '', - summary='分页获取所有字典', + summary='分页获取所有字典数据', dependencies=[ DependsJwtAuth, DependsPagination, ], ) -async def get_pagination_dict_datas( +async def get_dict_datas_paged( db: CurrentSession, label: Annotated[str | None, Query(description='字典数据标签')] = None, value: Annotated[str | None, Query(description='字典数据键值')] = None, @@ -50,9 +51,9 @@ async def get_pagination_dict_datas( @router.post( '', - summary='创建字典', + summary='创建字典数据', dependencies=[ - Depends(RequestPermission('sys:dict:data:add')), + Depends(RequestPermission('dict:data:add')), DependsRBAC, ], ) @@ -63,9 +64,9 @@ async def create_dict_data(obj: CreateDictDataParam) -> ResponseModel: @router.put( '/{pk}', - summary='更新字典', + summary='更新字典数据', dependencies=[ - Depends(RequestPermission('sys:dict:data:edit')), + Depends(RequestPermission('dict:data:edit')), DependsRBAC, ], ) @@ -80,14 +81,14 @@ async def update_dict_data( @router.delete( '', - summary='批量删除字典', + summary='批量删除字典数据', dependencies=[ - Depends(RequestPermission('sys:dict:data:del')), + Depends(RequestPermission('dict:data:del')), DependsRBAC, ], ) -async def delete_dict_data(pk: Annotated[list[int], Query(description='字典数据 ID 列表')]) -> ResponseModel: - count = await dict_data_service.delete(pk=pk) +async def delete_dict_datas(obj: DeleteDictDataParam) -> ResponseModel: + count = await dict_data_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/plugin/dict/api/v1/sys/dict_type.py b/backend/plugin/dict/api/v1/sys/dict_type.py index 61f68f2e3..04ec35d7f 100644 --- a/backend/plugin/dict/api/v1/sys/dict_type.py +++ b/backend/plugin/dict/api/v1/sys/dict_type.py @@ -10,7 +10,12 @@ from backend.common.security.permission import RequestPermission from backend.common.security.rbac import DependsRBAC from backend.database.db import CurrentSession -from backend.plugin.dict.schema.dict_type import CreateDictTypeParam, GetDictTypeDetail, UpdateDictTypeParam +from backend.plugin.dict.schema.dict_type import ( + CreateDictTypeParam, + DeleteDictTypeParam, + GetDictTypeDetail, + UpdateDictTypeParam, +) from backend.plugin.dict.service.dict_type_service import dict_type_service router = APIRouter() @@ -24,7 +29,7 @@ DependsPagination, ], ) -async def get_pagination_dict_types( +async def get_dict_types_paged( db: CurrentSession, name: Annotated[str | None, Query(description='字典类型名称')] = None, code: Annotated[str | None, Query(description='字典类型编码')] = None, @@ -39,7 +44,7 @@ async def get_pagination_dict_types( '', summary='创建字典类型', dependencies=[ - Depends(RequestPermission('sys:dict:type:add')), + Depends(RequestPermission('dict:type:add')), DependsRBAC, ], ) @@ -52,7 +57,7 @@ async def create_dict_type(obj: CreateDictTypeParam) -> ResponseModel: '/{pk}', summary='更新字典类型', dependencies=[ - Depends(RequestPermission('sys:dict:type:edit')), + Depends(RequestPermission('dict:type:edit')), DependsRBAC, ], ) @@ -69,12 +74,12 @@ async def update_dict_type( '', summary='批量删除字典类型', dependencies=[ - Depends(RequestPermission('sys:dict:type:del')), + Depends(RequestPermission('dict:type:del')), DependsRBAC, ], ) -async def delete_dict_type(pk: Annotated[list[int], Query(description='字典类型 ID 列表')]) -> ResponseModel: - count = await dict_type_service.delete(pk=pk) +async def delete_dict_types(obj: DeleteDictTypeParam) -> ResponseModel: + count = await dict_type_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/plugin/dict/crud/crud_dict_data.py b/backend/plugin/dict/crud/crud_dict_data.py index 53307dbe5..51c934cd6 100644 --- a/backend/plugin/dict/crud/crud_dict_data.py +++ b/backend/plugin/dict/crud/crud_dict_data.py @@ -72,15 +72,15 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateDictDataParam) -> i """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除字典数据 + 批量删除字典数据 :param db: 数据库会话 - :param pk: 字典数据 ID 列表 + :param pks: 字典数据 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) async def get_with_relation(self, db: AsyncSession, pk: int) -> DictData | None: """ diff --git a/backend/plugin/dict/crud/crud_dict_type.py b/backend/plugin/dict/crud/crud_dict_type.py index 96823d3fb..33872b3ee 100644 --- a/backend/plugin/dict/crud/crud_dict_type.py +++ b/backend/plugin/dict/crud/crud_dict_type.py @@ -72,15 +72,15 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateDictTypeParam) -> i """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除字典类型 + 批量删除字典类型 :param db: 数据库会话 - :param pk: 字典类型 ID 列表 + :param pks: 字典类型 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) dict_type_dao: CRUDDictType = CRUDDictType(DictType) diff --git a/backend/plugin/dict/schema/dict_data.py b/backend/plugin/dict/schema/dict_data.py index f0316d1e1..fbd6ecf6f 100644 --- a/backend/plugin/dict/schema/dict_data.py +++ b/backend/plugin/dict/schema/dict_data.py @@ -28,6 +28,12 @@ class UpdateDictDataParam(DictDataSchemaBase): """更新字典数据参数""" +class DeleteDictDataParam(SchemaBase): + """删除字典数据参数""" + + pks: list[int] = Field(description='字典数据 ID 列表') + + class GetDictDataDetail(DictDataSchemaBase): """字典数据详情""" diff --git a/backend/plugin/dict/schema/dict_type.py b/backend/plugin/dict/schema/dict_type.py index 92b91beba..c09dc8903 100644 --- a/backend/plugin/dict/schema/dict_type.py +++ b/backend/plugin/dict/schema/dict_type.py @@ -25,6 +25,12 @@ class UpdateDictTypeParam(DictTypeSchemaBase): """更新字典类型参数""" +class DeleteDictTypeParam(SchemaBase): + """删除字典类型参数""" + + pks: list[int] = Field(description='字典类型 ID 列表') + + class GetDictTypeDetail(DictTypeSchemaBase): """字典类型详情""" diff --git a/backend/plugin/dict/service/dict_data_service.py b/backend/plugin/dict/service/dict_data_service.py index c1b3c5529..3e3b6b978 100644 --- a/backend/plugin/dict/service/dict_data_service.py +++ b/backend/plugin/dict/service/dict_data_service.py @@ -7,7 +7,7 @@ from backend.plugin.dict.crud.crud_dict_data import dict_data_dao from backend.plugin.dict.crud.crud_dict_type import dict_type_dao from backend.plugin.dict.model import DictData -from backend.plugin.dict.schema.dict_data import CreateDictDataParam, UpdateDictDataParam +from backend.plugin.dict.schema.dict_data import CreateDictDataParam, DeleteDictDataParam, UpdateDictDataParam class DictDataService: @@ -79,15 +79,15 @@ async def update(*, pk: int, obj: UpdateDictDataParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteDictDataParam) -> int: """ - 删除字典数据 + 批量删除字典数据 - :param pk: 字典数据 ID 列表 + :param obj: 字典数据 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await dict_data_dao.delete(db, pk) + count = await dict_data_dao.delete(db, obj.pks) return count diff --git a/backend/plugin/dict/service/dict_type_service.py b/backend/plugin/dict/service/dict_type_service.py index 3d0650d55..3ed1c3ff9 100644 --- a/backend/plugin/dict/service/dict_type_service.py +++ b/backend/plugin/dict/service/dict_type_service.py @@ -5,7 +5,7 @@ from backend.common.exception import errors from backend.database.db import async_db_session from backend.plugin.dict.crud.crud_dict_type import dict_type_dao -from backend.plugin.dict.schema.dict_type import CreateDictTypeParam, UpdateDictTypeParam +from backend.plugin.dict.schema.dict_type import CreateDictTypeParam, DeleteDictTypeParam, UpdateDictTypeParam class DictTypeService: @@ -57,15 +57,15 @@ async def update(*, pk: int, obj: UpdateDictTypeParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteDictTypeParam) -> int: """ - 删除字典类型 + 批量删除字典类型 - :param pk: 字典类型 ID 列表 + :param obj: 字典类型 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await dict_type_dao.delete(db, pk) + count = await dict_type_dao.delete(db, obj.pks) return count diff --git a/backend/plugin/notice/api/v1/sys/notice.py b/backend/plugin/notice/api/v1/sys/notice.py index 8ec4d3440..436061a48 100644 --- a/backend/plugin/notice/api/v1/sys/notice.py +++ b/backend/plugin/notice/api/v1/sys/notice.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from typing import Annotated -from fastapi import APIRouter, Depends, Path, Query, Request +from fastapi import APIRouter, Depends, Path from backend.common.pagination import DependsPagination, PageData, paging_data from backend.common.response.response_schema import ResponseModel, ResponseSchemaModel, response_base @@ -10,16 +10,14 @@ from backend.common.security.permission import RequestPermission from backend.common.security.rbac import DependsRBAC from backend.database.db import CurrentSession -from backend.plugin.notice.schema.notice import CreateNoticeParam, GetNoticeDetail, UpdateNoticeParam +from backend.plugin.notice.schema.notice import CreateNoticeParam, DeleteNoticeParam, GetNoticeDetail, UpdateNoticeParam from backend.plugin.notice.service.notice_service import notice_service router = APIRouter() @router.get('/{pk}', summary='获取通知公告详情', dependencies=[DependsJwtAuth]) -async def get_notice( - request: Request, pk: Annotated[int, Path(description='通知公告 ID')] -) -> ResponseSchemaModel[GetNoticeDetail]: +async def get_notice(pk: Annotated[int, Path(description='通知公告 ID')]) -> ResponseSchemaModel[GetNoticeDetail]: notice = await notice_service.get(pk=pk) return response_base.success(data=notice) @@ -32,7 +30,7 @@ async def get_notice( DependsPagination, ], ) -async def get_pagination_notices(db: CurrentSession) -> ResponseSchemaModel[PageData[GetNoticeDetail]]: +async def get_notices_paged(db: CurrentSession) -> ResponseSchemaModel[PageData[GetNoticeDetail]]: notice_select = await notice_service.get_select() page_data = await paging_data(db, notice_select) return response_base.success(data=page_data) @@ -74,8 +72,8 @@ async def update_notice(pk: Annotated[int, Path(description='通知公告 ID')], DependsRBAC, ], ) -async def delete_notice(pk: Annotated[list[int], Query(description='通知公告 ID 列表')]) -> ResponseModel: - count = await notice_service.delete(pk=pk) +async def delete_notices(obj: DeleteNoticeParam) -> ResponseModel: + count = await notice_service.delete(obj=obj) if count > 0: return response_base.success() return response_base.fail() diff --git a/backend/plugin/notice/crud/crud_notice.py b/backend/plugin/notice/crud/crud_notice.py index 756ec9aa7..9ef91ec10 100644 --- a/backend/plugin/notice/crud/crud_notice.py +++ b/backend/plugin/notice/crud/crud_notice.py @@ -57,15 +57,15 @@ async def update(self, db: AsyncSession, pk: int, obj: UpdateNoticeParam) -> int """ return await self.update_model(db, pk, obj) - async def delete(self, db: AsyncSession, pk: list[int]) -> int: + async def delete(self, db: AsyncSession, pks: list[int]) -> int: """ - 删除通知公告 + 批量删除通知公告 :param db: 数据库会话 - :param pk: 通知公告 ID 列表 + :param pks: 通知公告 ID 列表 :return: """ - return await self.delete_model_by_column(db, allow_multiple=True, id__in=pk) + return await self.delete_model_by_column(db, allow_multiple=True, id__in=pks) notice_dao: CRUDNotice = CRUDNotice(Notice) diff --git a/backend/plugin/notice/schema/notice.py b/backend/plugin/notice/schema/notice.py index eb68192a3..c8545e2f3 100644 --- a/backend/plugin/notice/schema/notice.py +++ b/backend/plugin/notice/schema/notice.py @@ -27,6 +27,12 @@ class UpdateNoticeParam(NoticeSchemaBase): """更新通知公告参数""" +class DeleteNoticeParam(SchemaBase): + """删除通知公告参数""" + + pks: list[int] = Field(description='通知公告 ID 列表') + + class GetNoticeDetail(NoticeSchemaBase): """通知公告详情""" diff --git a/backend/plugin/notice/service/notice_service.py b/backend/plugin/notice/service/notice_service.py index 55f4c1181..fa6c88263 100644 --- a/backend/plugin/notice/service/notice_service.py +++ b/backend/plugin/notice/service/notice_service.py @@ -8,7 +8,7 @@ from backend.database.db import async_db_session from backend.plugin.notice.crud.crud_notice import notice_dao from backend.plugin.notice.model import Notice -from backend.plugin.notice.schema.notice import CreateNoticeParam, UpdateNoticeParam +from backend.plugin.notice.schema.notice import CreateNoticeParam, DeleteNoticeParam, UpdateNoticeParam class NoticeService: @@ -68,15 +68,15 @@ async def update(*, pk: int, obj: UpdateNoticeParam) -> int: return count @staticmethod - async def delete(*, pk: list[int]) -> int: + async def delete(*, obj: DeleteNoticeParam) -> int: """ - 删除通知公告 + 批量删除通知公告 - :param pk: 通知公告 ID 列表 + :param obj: 通知公告 ID 列表 :return: """ async with async_db_session.begin() as db: - count = await notice_dao.delete(db, pk) + count = await notice_dao.delete(db, obj.pks) return count diff --git a/backend/plugin/oauth2/api/v1/github.py b/backend/plugin/oauth2/api/v1/github.py index 058294242..6a60f74a1 100644 --- a/backend/plugin/oauth2/api/v1/github.py +++ b/backend/plugin/oauth2/api/v1/github.py @@ -17,7 +17,7 @@ @router.get('', summary='获取 Github 授权链接') -async def github_oauth2(request: Request) -> ResponseSchemaModel[str]: +async def get_github_oauth2_url(request: Request) -> ResponseSchemaModel[str]: auth_url = await _github_client.get_authorization_url(redirect_uri=f'{request.url}/callback') return response_base.success(data=auth_url) @@ -28,7 +28,7 @@ async def github_oauth2(request: Request) -> ResponseSchemaModel[str]: description='Github 授权后,自动重定向到当前地址并获取用户信息,通过用户信息自动创建系统用户', dependencies=[Depends(RateLimiter(times=5, minutes=1))], ) -async def github_login( +async def github_oauth2_callback( request: Request, response: Response, background_tasks: BackgroundTasks, diff --git a/backend/plugin/oauth2/api/v1/linux_do.py b/backend/plugin/oauth2/api/v1/linux_do.py index 7d9e47751..a8d3e0a29 100644 --- a/backend/plugin/oauth2/api/v1/linux_do.py +++ b/backend/plugin/oauth2/api/v1/linux_do.py @@ -20,7 +20,7 @@ @router.get('', summary='获取 LinuxDo 授权链接') -async def linux_do_oauth2(request: Request) -> ResponseSchemaModel[str]: +async def get_linux_do_oauth2_url(request: Request) -> ResponseSchemaModel[str]: auth_url = await _linux_do_client.get_authorization_url(redirect_uri=f'{request.url}/callback') return response_base.success(data=auth_url) @@ -31,7 +31,7 @@ async def linux_do_oauth2(request: Request) -> ResponseSchemaModel[str]: description='LinuxDo 授权后,自动重定向到当前地址并获取用户信息,通过用户信息自动创建系统用户', dependencies=[Depends(RateLimiter(times=5, minutes=1))], ) -async def linux_do_login( +async def linux_do_oauth2_callback( request: Request, response: Response, background_tasks: BackgroundTasks, diff --git a/backend/sql/mysql/init_test_data.sql b/backend/sql/mysql/init_test_data.sql index b70580067..45700b83a 100644 --- a/backend/sql/mysql/init_test_data.sql +++ b/backend/sql/mysql/init_test_data.sql @@ -12,7 +12,7 @@ values (1, '概览', 'Dashboard', 'dashboard', 0, 'ant-design:dashboard-outline (8, '工作台', 'Workspace', 'workspace', 1, 'carbon:workspace', 1, '/dashboard/workspace/index', null, 1, 1, 1, '', null, 1, '2025-06-09 17:57:09', null), (9, '文档', 'Document', 'document', 1, 'lucide:book-open-text', 4, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://fastapi-practices.github.io/fastapi_best_architecture_docs', null, 6, '2025-06-09 17:59:44', null), (10, 'Github', 'Github', 'github', 2, 'ant-design:github-filled', 4, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://github.com/fastapi-practices/fastapi_best_architecture', null, 6, '2025-06-09 18:00:50', null), - (11, 'Apifox', 'Apifox', 'apifox', 3, 'simple-icons:apifox', 3, null, '/_core/fallback/iframe.vue', 1, 1, 1, 'https://apifox.com/apidoc/shared-28a93f02-730b-4f33-bb5e-4dad92058cc0', null, 6, '2025-06-09 18:01:39', null), + (11, 'Apifox', 'Apifox', 'apifox', 3, 'simple-icons:apifox', 3, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://apifox.com/apidoc/shared-28a93f02-730b-4f33-bb5e-4dad92058cc0', null, 6, '2025-06-09 18:01:39', null), (12, '部门管理', 'SysDept', 'sys-dept', 1, 'mingcute:department-line', 1, '/system/dept/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:03:17', null), (13, '用户管理', 'SysUser', 'sys-user', 2, 'ant-design:user-outlined', 1, '/system/user/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:03:54', null), (14, '角色管理', 'SysRole', 'sys-role', 3, 'carbon:user-role', 1, '/system/role/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:04:47', null), @@ -51,38 +51,34 @@ values (1, '概览', 'Dashboard', 'dashboard', 0, 'ant-design:dashboard-outline (47, '新增', 'AddSysDataRule', null, 0, null, 2, null, 'data:rule:add', 1, 0, 1, '', null, 18, '2025-06-09 18:35:54', null), (48, '修改', 'EditSysDataRule', null, 0, null, 2, null, 'data:rule:edit', 1, 0, 1, '', null, 18, '2025-06-09 18:36:19', null), (49, '删除', 'DeleteSysDataRule', null, 0, null, 2, null, 'data:rule:del', 1, 0, 1, '', null, 18, '2025-06-09 18:36:44', null), - (50, '安装zip插件', 'InstallZipSysPlugin', null, 0, null, 2, null, 'sys:plugin:zip', 1, 0, 1, '', null, 19, '2025-06-09 18:38:14', null), - (51, '安装git插件', 'InstallGitSysPlugin', null, 0, null, 2, null, 'sys:plugin:git', 1, 0, 1, '', null, 19, '2025-06-09 18:38:43', null), - (52, '卸载', 'UninstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:del', 1, 0, 1, '', null, 19, '2025-06-09 18:39:08', null), - (53, '修改', 'EditSysPlugin', null, 0, null, 2, null, 'sys:plugin:status', 1, 0, 1, '', null, 19, '2025-06-09 18:39:47', null), - (54, '新增网站参数', 'AddWebsiteSysConfig', null, 0, null, 2, null, 'sys:config:website:add', 1, 0, 1, '', null, 20, '2025-06-09 18:43:30', null), - (55, '新增用户协议', 'AddProtocolSysConfig', null, 0, null, 2, null, 'sys:config:protocol:add', 1, 0, 1, '', null, 20, '2025-06-09 18:44:13', null), - (56, '新增用户政策', 'AddPolicySysConfig', null, 0, null, 2, null, 'sys:config:policy:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:28', null), - (57, '新增', 'AddSysConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:52', null), - (58, '修改', 'EditSysConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 20, '2025-06-09 18:46:13', null), - (59, '删除', 'DeleteSysConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 20, '2025-06-09 18:46:36', null), - (60, '新增类型', 'AddSysDictType', null, 0, null, 2, null, 'sys:dict:type:add', 1, 0, 1, '', null, 21, '2025-06-09 18:48:17', null), - (61, '修改类型', 'EditSysDictType', null, 0, null, 2, null, 'sys:dict:type:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:48:49', null), - (62, '删除类型', 'DeleteSysDictType', null, 0, null, 2, null, 'sys:dict:type:del', 1, 0, 1, '', null, 21, '2025-06-09 18:49:23', null), - (63, '新增', 'AddSysDictData', null, 0, null, 2, null, 'sys:dict:data:add', 1, 0, 1, '', null, 21, '2025-06-09 18:50:01', null), - (64, '修改', 'EditSysDictData', null, 0, null, 2, null, 'sys:dict:data:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:50:26', null), - (65, '删除', 'DeleteSysDictData', null, 0, null, 2, null, 'sys:dict:data:del', 1, 0, 1, '', null, 21, '2025-06-09 18:50:48', null), - (66, '新增', 'AddSysNotice', null, 0, null, 2, null, 'sys:notice:add', 1, 0, 1, '', null, 22, '2025-06-09 18:51:22', null), - (67, '修改', 'EditSysNotice', null, 0, null, 2, null, 'sys:notice:edit', 1, 0, 1, '', null, 22, '2025-06-09 18:51:45', null), - (68, '删除', 'DeleteSysNotice', null, 0, null, 2, null, 'sys:notice:del', 1, 0, 1, '', null, 22, '2025-06-09 18:52:10', null), - (69, '新增业务', 'AddSysGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:add', 1, 0, 1, '', null, 23, '2025-06-09 18:53:07', null), - (70, '修改业务', 'EditGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:53:45', null), - (71, '删除业务', 'DeleteGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:del', 1, 0, 1, '', null, 23, '2025-06-09 18:54:11', null), - (72, '新增模型', 'AddGenCodeModel', null, 0, null, 2, null, 'gen:code:model:add', 1, 0, 1, '', null, 23, '2025-06-09 18:54:45', null), - (73, '修改模型', 'EditGenCodeModel', null, 0, null, 2, null, 'gen:code:model:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:55:08', null), - (74, '删除模型', 'DeleteGenCodeModel', null, 0, null, 2, null, 'gen:code:model:del', 1, 0, 1, '', null, 23, '2025-06-09 18:55:35', null), - (75, '导入', 'ImportGenCode', null, 0, null, 2, null, 'gen:code:import', 1, 0, 1, '', null, 23, '2025-06-09 18:58:16', null), - (76, '写入', 'WriteGenCode', null, 0, null, 2, null, 'gen:code:write', 1, 0, 1, '', null, 23, '2025-06-09 19:01:22', null), - (77, '删除', 'DeleteSysLoginLog', null, 0, null, 2, null, 'log:login:del', 1, 0, 1, '', null, 25, '2025-06-09 19:02:21', null), - (78, '清空', 'EmptyLoginLog', null, 0, null, 2, null, 'log:login:empty', 1, 0, 1, '', null, 25, '2025-06-09 19:02:50', null), - (79, '删除', 'DeleteOperaLog', null, 0, null, 2, null, 'log:opera:del', 1, 0, 1, '', null, 26, '2025-06-09 19:03:13', null), - (80, '清空', 'EmptyOperaLog', null, 0, null, 2, null, 'log:opera:empty', 1, 0, 1, '', null, 26, '2025-06-09 19:03:40', null), - (81, '下线', 'KickSysToken', null, 0, null, 2, null, 'sys:token:kick', 1, 0, 1, '', null, 27, '2025-06-09 19:04:52', null); + (50, '安装插件', 'InstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:install', 1, 0, 1, '', null, 19, '2025-06-09 18:38:14', null), + (51, '卸载', 'UninstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:uninstall', 1, 0, 1, '', null, 19, '2025-06-09 18:39:08', null), + (52, '修改', 'EditSysPlugin', null, 0, null, 2, null, 'sys:plugin:edit', 1, 0, 1, '', null, 19, '2025-06-09 18:39:47', null), + (53, '新增', 'AddSysConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:52', null), + (54, '修改', 'EditSysConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 20, '2025-06-09 18:46:13', null), + (55, '删除', 'DeleteSysConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 20, '2025-06-09 18:46:36', null), + (56, '新增类型', 'AddSysDictType', null, 0, null, 2, null, 'dict:type:add', 1, 0, 1, '', null, 21, '2025-06-09 18:48:17', null), + (57, '修改类型', 'EditSysDictType', null, 0, null, 2, null, 'dict:type:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:48:49', null), + (58, '删除类型', 'DeleteSysDictType', null, 0, null, 2, null, 'dict:type:del', 1, 0, 1, '', null, 21, '2025-06-09 18:49:23', null), + (59, '新增', 'AddSysDictData', null, 0, null, 2, null, 'dict:data:add', 1, 0, 1, '', null, 21, '2025-06-09 18:50:01', null), + (60, '修改', 'EditSysDictData', null, 0, null, 2, null, 'dict:data:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:50:26', null), + (61, '删除', 'DeleteSysDictData', null, 0, null, 2, null, 'dict:data:del', 1, 0, 1, '', null, 21, '2025-06-09 18:50:48', null), + (62, '新增', 'AddSysNotice', null, 0, null, 2, null, 'sys:notice:add', 1, 0, 1, '', null, 22, '2025-06-09 18:51:22', null), + (63, '修改', 'EditSysNotice', null, 0, null, 2, null, 'sys:notice:edit', 1, 0, 1, '', null, 22, '2025-06-09 18:51:45', null), + (64, '删除', 'DeleteSysNotice', null, 0, null, 2, null, 'sys:notice:del', 1, 0, 1, '', null, 22, '2025-06-09 18:52:10', null), + (65, '新增业务', 'AddSysGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:add', 1, 0, 1, '', null, 23, '2025-06-09 18:53:07', null), + (66, '修改业务', 'EditGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:53:45', null), + (67, '删除业务', 'DeleteGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:del', 1, 0, 1, '', null, 23, '2025-06-09 18:54:11', null), + (68, '新增模型', 'AddGenCodeModel', null, 0, null, 2, null, 'codegen:model:add', 1, 0, 1, '', null, 23, '2025-06-09 18:54:45', null), + (69, '修改模型', 'EditGenCodeModel', null, 0, null, 2, null, 'codegen:model:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:55:08', null), + (70, '删除模型', 'DeleteGenCodeModel', null, 0, null, 2, null, 'codegen:model:del', 1, 0, 1, '', null, 23, '2025-06-09 18:55:35', null), + (71, '导入', 'ImportGenCode', null, 0, null, 2, null, 'codegen:table:import', 1, 0, 1, '', null, 23, '2025-06-09 18:58:16', null), + (72, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 23, '2025-06-09 19:01:22', null), + (73, '删除', 'DeleteSysLoginLog', null, 0, null, 2, null, 'log:login:del', 1, 0, 1, '', null, 25, '2025-06-09 19:02:21', null), + (74, '清空', 'EmptyLoginLog', null, 0, null, 2, null, 'log:login:clear', 1, 0, 1, '', null, 25, '2025-06-09 19:02:50', null), + (75, '删除', 'DeleteOperaLog', null, 0, null, 2, null, 'log:opera:del', 1, 0, 1, '', null, 26, '2025-06-09 19:03:13', null), + (76, '清空', 'EmptyOperaLog', null, 0, null, 2, null, 'log:opera:clear', 1, 0, 1, '', null, 26, '2025-06-09 19:03:40', null), + (77, '下线', 'KickSysToken', null, 0, null, 2, null, 'sys:session:delete', 1, 0, 1, '', null, 27, '2025-06-09 19:04:52', null); insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) values (1, '测试', 1, 1, null, '2025-05-26 17:13:45', null); diff --git a/backend/sql/postgresql/init_test_data.sql b/backend/sql/postgresql/init_test_data.sql index 107709bb7..8850b172b 100644 --- a/backend/sql/postgresql/init_test_data.sql +++ b/backend/sql/postgresql/init_test_data.sql @@ -12,7 +12,7 @@ values (1, '概览', 'Dashboard', 'dashboard', 0, 'ant-design:dashboard-outline (8, '工作台', 'Workspace', 'workspace', 1, 'carbon:workspace', 1, '/dashboard/workspace/index', null, 1, 1, 1, '', null, 1, '2025-06-09 17:57:09', null), (9, '文档', 'Document', 'document', 1, 'lucide:book-open-text', 4, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://fastapi-practices.github.io/fastapi_best_architecture_docs', null, 6, '2025-06-09 17:59:44', null), (10, 'Github', 'Github', 'github', 2, 'ant-design:github-filled', 4, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://github.com/fastapi-practices/fastapi_best_architecture', null, 6, '2025-06-09 18:00:50', null), - (11, 'Apifox', 'Apifox', 'apifox', 3, 'simple-icons:apifox', 3, null, '/_core/fallback/iframe.vue', 1, 1, 1, 'https://apifox.com/apidoc/shared-28a93f02-730b-4f33-bb5e-4dad92058cc0', null, 6, '2025-06-09 18:01:39', null), + (11, 'Apifox', 'Apifox', 'apifox', 3, 'simple-icons:apifox', 3, '/_core/fallback/iframe.vue', null, 1, 1, 1, 'https://apifox.com/apidoc/shared-28a93f02-730b-4f33-bb5e-4dad92058cc0', null, 6, '2025-06-09 18:01:39', null), (12, '部门管理', 'SysDept', 'sys-dept', 1, 'mingcute:department-line', 1, '/system/dept/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:03:17', null), (13, '用户管理', 'SysUser', 'sys-user', 2, 'ant-design:user-outlined', 1, '/system/user/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:03:54', null), (14, '角色管理', 'SysRole', 'sys-role', 3, 'carbon:user-role', 1, '/system/role/index', null, 1, 1, 1, '', null, 2, '2025-06-09 18:04:47', null), @@ -51,38 +51,34 @@ values (1, '概览', 'Dashboard', 'dashboard', 0, 'ant-design:dashboard-outline (47, '新增', 'AddSysDataRule', null, 0, null, 2, null, 'data:rule:add', 1, 0, 1, '', null, 18, '2025-06-09 18:35:54', null), (48, '修改', 'EditSysDataRule', null, 0, null, 2, null, 'data:rule:edit', 1, 0, 1, '', null, 18, '2025-06-09 18:36:19', null), (49, '删除', 'DeleteSysDataRule', null, 0, null, 2, null, 'data:rule:del', 1, 0, 1, '', null, 18, '2025-06-09 18:36:44', null), - (50, '安装zip插件', 'InstallZipSysPlugin', null, 0, null, 2, null, 'sys:plugin:zip', 1, 0, 1, '', null, 19, '2025-06-09 18:38:14', null), - (51, '安装git插件', 'InstallGitSysPlugin', null, 0, null, 2, null, 'sys:plugin:git', 1, 0, 1, '', null, 19, '2025-06-09 18:38:43', null), - (52, '卸载', 'UninstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:del', 1, 0, 1, '', null, 19, '2025-06-09 18:39:08', null), - (53, '修改', 'EditSysPlugin', null, 0, null, 2, null, 'sys:plugin:status', 1, 0, 1, '', null, 19, '2025-06-09 18:39:47', null), - (54, '新增网站参数', 'AddWebsiteSysConfig', null, 0, null, 2, null, 'sys:config:website:add', 1, 0, 1, '', null, 20, '2025-06-09 18:43:30', null), - (55, '新增用户协议', 'AddProtocolSysConfig', null, 0, null, 2, null, 'sys:config:protocol:add', 1, 0, 1, '', null, 20, '2025-06-09 18:44:13', null), - (56, '新增用户政策', 'AddPolicySysConfig', null, 0, null, 2, null, 'sys:config:policy:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:28', null), - (57, '新增', 'AddSysConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:52', null), - (58, '修改', 'EditSysConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 20, '2025-06-09 18:46:13', null), - (59, '删除', 'DeleteSysConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 20, '2025-06-09 18:46:36', null), - (60, '新增类型', 'AddSysDictType', null, 0, null, 2, null, 'sys:dict:type:add', 1, 0, 1, '', null, 21, '2025-06-09 18:48:17', null), - (61, '修改类型', 'EditSysDictType', null, 0, null, 2, null, 'sys:dict:type:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:48:49', null), - (62, '删除类型', 'DeleteSysDictType', null, 0, null, 2, null, 'sys:dict:type:del', 1, 0, 1, '', null, 21, '2025-06-09 18:49:23', null), - (63, '新增', 'AddSysDictData', null, 0, null, 2, null, 'sys:dict:data:add', 1, 0, 1, '', null, 21, '2025-06-09 18:50:01', null), - (64, '修改', 'EditSysDictData', null, 0, null, 2, null, 'sys:dict:data:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:50:26', null), - (65, '删除', 'DeleteSysDictData', null, 0, null, 2, null, 'sys:dict:data:del', 1, 0, 1, '', null, 21, '2025-06-09 18:50:48', null), - (66, '新增', 'AddSysNotice', null, 0, null, 2, null, 'sys:notice:add', 1, 0, 1, '', null, 22, '2025-06-09 18:51:22', null), - (67, '修改', 'EditSysNotice', null, 0, null, 2, null, 'sys:notice:edit', 1, 0, 1, '', null, 22, '2025-06-09 18:51:45', null), - (68, '删除', 'DeleteSysNotice', null, 0, null, 2, null, 'sys:notice:del', 1, 0, 1, '', null, 22, '2025-06-09 18:52:10', null), - (69, '新增业务', 'AddSysGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:add', 1, 0, 1, '', null, 23, '2025-06-09 18:53:07', null), - (70, '修改业务', 'EditGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:53:45', null), - (71, '删除业务', 'DeleteGenCodeBusiness', null, 0, null, 2, null, 'gen:code:business:del', 1, 0, 1, '', null, 23, '2025-06-09 18:54:11', null), - (72, '新增模型', 'AddGenCodeModel', null, 0, null, 2, null, 'gen:code:model:add', 1, 0, 1, '', null, 23, '2025-06-09 18:54:45', null), - (73, '修改模型', 'EditGenCodeModel', null, 0, null, 2, null, 'gen:code:model:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:55:08', null), - (74, '删除模型', 'DeleteGenCodeModel', null, 0, null, 2, null, 'gen:code:model:del', 1, 0, 1, '', null, 23, '2025-06-09 18:55:35', null), - (75, '导入', 'ImportGenCode', null, 0, null, 2, null, 'gen:code:import', 1, 0, 1, '', null, 23, '2025-06-09 18:58:16', null), - (76, '写入', 'WriteGenCode', null, 0, null, 2, null, 'gen:code:write', 1, 0, 1, '', null, 23, '2025-06-09 19:01:22', null), - (77, '删除', 'DeleteSysLoginLog', null, 0, null, 2, null, 'log:login:del', 1, 0, 1, '', null, 25, '2025-06-09 19:02:21', null), - (78, '清空', 'EmptyLoginLog', null, 0, null, 2, null, 'log:login:empty', 1, 0, 1, '', null, 25, '2025-06-09 19:02:50', null), - (79, '删除', 'DeleteOperaLog', null, 0, null, 2, null, 'log:opera:del', 1, 0, 1, '', null, 26, '2025-06-09 19:03:13', null), - (80, '清空', 'EmptyOperaLog', null, 0, null, 2, null, 'log:opera:empty', 1, 0, 1, '', null, 26, '2025-06-09 19:03:40', null), - (81, '下线', 'KickSysToken', null, 0, null, 2, null, 'sys:token:kick', 1, 0, 1, '', null, 27, '2025-06-09 19:04:52', null); + (50, '安装插件', 'InstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:install', 1, 0, 1, '', null, 19, '2025-06-09 18:38:14', null), + (51, '卸载', 'UninstallSysPlugin', null, 0, null, 2, null, 'sys:plugin:uninstall', 1, 0, 1, '', null, 19, '2025-06-09 18:39:08', null), + (52, '修改', 'EditSysPlugin', null, 0, null, 2, null, 'sys:plugin:edit', 1, 0, 1, '', null, 19, '2025-06-09 18:39:47', null), + (53, '新增', 'AddSysConfig', null, 0, null, 2, null, 'sys:config:add', 1, 0, 1, '', null, 20, '2025-06-09 18:45:52', null), + (54, '修改', 'EditSysConfig', null, 0, null, 2, null, 'sys:config:edit', 1, 0, 1, '', null, 20, '2025-06-09 18:46:13', null), + (55, '删除', 'DeleteSysConfig', null, 0, null, 2, null, 'sys:config:del', 1, 0, 1, '', null, 20, '2025-06-09 18:46:36', null), + (56, '新增类型', 'AddSysDictType', null, 0, null, 2, null, 'dict:type:add', 1, 0, 1, '', null, 21, '2025-06-09 18:48:17', null), + (57, '修改类型', 'EditSysDictType', null, 0, null, 2, null, 'dict:type:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:48:49', null), + (58, '删除类型', 'DeleteSysDictType', null, 0, null, 2, null, 'dict:type:del', 1, 0, 1, '', null, 21, '2025-06-09 18:49:23', null), + (59, '新增', 'AddSysDictData', null, 0, null, 2, null, 'dict:data:add', 1, 0, 1, '', null, 21, '2025-06-09 18:50:01', null), + (60, '修改', 'EditSysDictData', null, 0, null, 2, null, 'dict:data:edit', 1, 0, 1, '', null, 21, '2025-06-09 18:50:26', null), + (61, '删除', 'DeleteSysDictData', null, 0, null, 2, null, 'dict:data:del', 1, 0, 1, '', null, 21, '2025-06-09 18:50:48', null), + (62, '新增', 'AddSysNotice', null, 0, null, 2, null, 'sys:notice:add', 1, 0, 1, '', null, 22, '2025-06-09 18:51:22', null), + (63, '修改', 'EditSysNotice', null, 0, null, 2, null, 'sys:notice:edit', 1, 0, 1, '', null, 22, '2025-06-09 18:51:45', null), + (64, '删除', 'DeleteSysNotice', null, 0, null, 2, null, 'sys:notice:del', 1, 0, 1, '', null, 22, '2025-06-09 18:52:10', null), + (65, '新增业务', 'AddSysGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:add', 1, 0, 1, '', null, 23, '2025-06-09 18:53:07', null), + (66, '修改业务', 'EditGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:53:45', null), + (67, '删除业务', 'DeleteGenCodeBusiness', null, 0, null, 2, null, 'codegen:business:del', 1, 0, 1, '', null, 23, '2025-06-09 18:54:11', null), + (68, '新增模型', 'AddGenCodeModel', null, 0, null, 2, null, 'codegen:model:add', 1, 0, 1, '', null, 23, '2025-06-09 18:54:45', null), + (69, '修改模型', 'EditGenCodeModel', null, 0, null, 2, null, 'codegen:model:edit', 1, 0, 1, '', null, 23, '2025-06-09 18:55:08', null), + (70, '删除模型', 'DeleteGenCodeModel', null, 0, null, 2, null, 'codegen:model:del', 1, 0, 1, '', null, 23, '2025-06-09 18:55:35', null), + (71, '导入', 'ImportGenCode', null, 0, null, 2, null, 'codegen:table:import', 1, 0, 1, '', null, 23, '2025-06-09 18:58:16', null), + (72, '写入', 'WriteGenCode', null, 0, null, 2, null, 'codegen:local:write', 1, 0, 1, '', null, 23, '2025-06-09 19:01:22', null), + (73, '删除', 'DeleteSysLoginLog', null, 0, null, 2, null, 'log:login:del', 1, 0, 1, '', null, 25, '2025-06-09 19:02:21', null), + (74, '清空', 'EmptyLoginLog', null, 0, null, 2, null, 'log:login:clear', 1, 0, 1, '', null, 25, '2025-06-09 19:02:50', null), + (75, '删除', 'DeleteOperaLog', null, 0, null, 2, null, 'log:opera:del', 1, 0, 1, '', null, 26, '2025-06-09 19:03:13', null), + (76, '清空', 'EmptyOperaLog', null, 0, null, 2, null, 'log:opera:clear', 1, 0, 1, '', null, 26, '2025-06-09 19:03:40', null), + (77, '下线', 'KickSysToken', null, 0, null, 2, null, 'sys:session:delete', 1, 0, 1, '', null, 27, '2025-06-09 19:04:52', null); insert into sys_role (id, name, status, is_filter_scopes, remark, created_time, updated_time) values (1, '测试', 1, 1, null, '2025-05-26 17:13:45', null); diff --git a/backend/utils/file_ops.py b/backend/utils/file_ops.py index 04cd5aa81..fbca97398 100644 --- a/backend/utils/file_ops.py +++ b/backend/utils/file_ops.py @@ -28,12 +28,11 @@ def build_filename(file: UploadFile) -> str: return new_filename -def file_verify(file: UploadFile, file_type: FileType) -> None: +def file_verify(file: UploadFile) -> None: """ 文件验证 :param file: FastAPI 上传文件对象 - :param file_type: 文件类型枚举 :return: """ filename = file.filename @@ -41,12 +40,12 @@ def file_verify(file: UploadFile, file_type: FileType) -> None: if not file_ext: raise errors.ForbiddenError(msg='未知的文件类型') - if file_type == FileType.image: + if file_ext == FileType.image: if file_ext not in settings.UPLOAD_IMAGE_EXT_INCLUDE: raise errors.ForbiddenError(msg='此图片格式暂不支持') if file.size > settings.UPLOAD_IMAGE_SIZE_MAX: raise errors.ForbiddenError(msg='图片超出最大限制,请重新选择') - elif file_type == FileType.video: + elif file_ext == FileType.video: if file_ext not in settings.UPLOAD_VIDEO_EXT_INCLUDE: raise errors.ForbiddenError(msg='此视频格式暂不支持') if file.size > settings.UPLOAD_VIDEO_SIZE_MAX: