mirror of
https://github.com/fastapi-practices/fastapi-best-architecture.git
synced 2026-09-21 21:15:13 +00:00
Optimize routes to better align with RESTful (#673)
* Optimize routes to better align with RESTful * Add codes endpoint description * Update jinja templates * fix typo * fix sql
This commit is contained in:
@@ -8,4 +8,4 @@ from backend.app.admin.api.v1.auth.captcha import router as captcha_router
|
||||
router = APIRouter(prefix='/auth')
|
||||
|
||||
router.include_router(auth_router, tags=['授权'])
|
||||
router.include_router(captcha_router, prefix='/captcha', tags=['验证码'])
|
||||
router.include_router(captcha_router, tags=['验证码'])
|
||||
|
||||
@@ -11,14 +11,15 @@ from backend.app.admin.schema.token import GetLoginToken, GetNewToken, GetSwagge
|
||||
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()
|
||||
|
||||
@@ -14,7 +14,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
'',
|
||||
'/captcha',
|
||||
summary='获取登录验证码',
|
||||
dependencies=[Depends(RateLimiter(times=5, seconds=10))],
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Annotated
|
||||
|
||||
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 @@ router = APIRouter()
|
||||
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,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Annotated
|
||||
|
||||
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 @@ router = APIRouter()
|
||||
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,
|
||||
],
|
||||
)
|
||||
|
||||
@@ -10,4 +10,4 @@ router = APIRouter(prefix='/monitors')
|
||||
|
||||
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=['会话监控'])
|
||||
|
||||
@@ -19,7 +19,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@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 @@ async def get_online(
|
||||
|
||||
@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')],
|
||||
|
||||
@@ -5,10 +5,10 @@ from fastapi import APIRouter
|
||||
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(role_router, prefix='/roles', tags=['系统角色'])
|
||||
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=['系统插件'])
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, Path, Query
|
||||
|
||||
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()
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, Path, Query
|
||||
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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}'})
|
||||
@@ -14,7 +14,7 @@ from backend.common.security.rbac import DependsRBAC
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from fastapi.params import Query
|
||||
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,
|
||||
|
||||
@@ -6,6 +6,7 @@ from fastapi import APIRouter, Depends, Path, Query
|
||||
|
||||
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()
|
||||
|
||||
@@ -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}'})
|
||||
@@ -13,6 +13,7 @@ from backend.app.admin.schema.user import (
|
||||
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 @@ from backend.database.db import CurrentSession
|
||||
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])
|
||||
@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}', summary='更新用户信息', dependencies=[DependsJwtAuth])
|
||||
async def update_user(
|
||||
request: Request, username: Annotated[str, Path(description='用户名')], obj: UpdateUserParam
|
||||
request: Request, pk: Annotated[int, Path(description='用户 ID')], obj: UpdateUserParam
|
||||
) -> ResponseModel:
|
||||
count = await user_service.update(request=request, username=username, obj=obj)
|
||||
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}/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)
|
||||
@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}/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)
|
||||
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)
|
||||
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()
|
||||
|
||||
@@ -77,15 +77,15 @@ class CRUDDataRule(CRUDPlus[DataRule]):
|
||||
"""
|
||||
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)
|
||||
|
||||
@@ -105,15 +105,15 @@ class CRUDDataScope(CRUDPlus[DataScope]):
|
||||
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)
|
||||
|
||||
@@ -41,15 +41,15 @@ class CRUDLoginLog(CRUDPlus[LoginLog]):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
|
||||
@@ -50,18 +50,17 @@ class CRUDMenu(CRUDPlus[Menu]):
|
||||
|
||||
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)
|
||||
|
||||
@@ -41,15 +41,15 @@ class CRUDOperaLogDao(CRUDPlus[OperaLog]):
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
|
||||
@@ -134,15 +134,15 @@ class CRUDRole(CRUDPlus[Role]):
|
||||
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)
|
||||
|
||||
@@ -27,6 +27,12 @@ class UpdateDataRuleParam(DataRuleSchemaBase):
|
||||
"""更新数据规则参数"""
|
||||
|
||||
|
||||
class DeleteDataRuleParam(SchemaBase):
|
||||
"""删除数据规则参数"""
|
||||
|
||||
pks: list[int] = Field(description='规则 ID 列表')
|
||||
|
||||
|
||||
class GetDataRuleDetail(DataRuleSchemaBase):
|
||||
"""数据规则详情"""
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""数据范围详情"""
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ class UpdateLoginLogParam(LoginLogSchemaBase):
|
||||
"""更新登录日志参数"""
|
||||
|
||||
|
||||
class DeleteLoginLogParam(SchemaBase):
|
||||
"""删除登录日志参数"""
|
||||
|
||||
pks: list[int] = Field(description='登录日志 ID 列表')
|
||||
|
||||
|
||||
class GetLoginLogDetail(LoginLogSchemaBase):
|
||||
"""登录日志详情"""
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ class UpdateOperaLogParam(OperaLogSchemaBase):
|
||||
"""更新操作日志参数"""
|
||||
|
||||
|
||||
class DeleteOperaLogParam(SchemaBase):
|
||||
"""删除操作日志参数"""
|
||||
|
||||
pks: list[int] = Field(description='操作日志 ID 列表')
|
||||
|
||||
|
||||
class GetOperaLogDetail(OperaLogSchemaBase):
|
||||
"""操作日志详情"""
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ class UpdateRoleParam(RoleSchemaBase):
|
||||
"""更新角色参数"""
|
||||
|
||||
|
||||
class DeleteRoleParam(SchemaBase):
|
||||
"""删除角色参数"""
|
||||
|
||||
pks: list[int] = Field(description='角色 ID 列表')
|
||||
|
||||
|
||||
class UpdateRoleMenuParam(SchemaBase):
|
||||
"""更新角色菜单参数"""
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from fastapi.security import HTTPBasicCredentials
|
||||
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 @@ class AuthService:
|
||||
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:
|
||||
|
||||
@@ -6,7 +6,12 @@ from sqlalchemy import Select
|
||||
|
||||
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 @@ class DataRuleService:
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@ from sqlalchemy import Select
|
||||
|
||||
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 @@ class DataScopeService:
|
||||
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:
|
||||
|
||||
@@ -32,7 +32,7 @@ class DeptService:
|
||||
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]]:
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@ from sqlalchemy import Select
|
||||
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 @@ class LoginLogService:
|
||||
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
|
||||
|
||||
@@ -32,7 +32,7 @@ class MenuService:
|
||||
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 @@ class MenuService:
|
||||
: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
|
||||
|
||||
@@ -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 @@ class OperaLogService:
|
||||
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
|
||||
|
||||
@@ -11,7 +11,7 @@ from typing import Any
|
||||
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 @@ class PluginService:
|
||||
|
||||
@staticmethod
|
||||
async def changed() -> str | None:
|
||||
"""插件状态是否变更"""
|
||||
"""检查插件是否发生变更"""
|
||||
return await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:changed')
|
||||
|
||||
@staticmethod
|
||||
@@ -113,6 +113,24 @@ class PluginService:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -10,6 +10,7 @@ from backend.app.admin.crud.crud_role import role_dao
|
||||
from backend.app.admin.model import Role
|
||||
from backend.app.admin.schema.role import (
|
||||
CreateRoleParam,
|
||||
DeleteRoleParam,
|
||||
UpdateRoleMenuParam,
|
||||
UpdateRoleParam,
|
||||
UpdateRoleScopeParam,
|
||||
@@ -166,17 +167,17 @@ class RoleService:
|
||||
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}')
|
||||
|
||||
@@ -16,6 +16,7 @@ from backend.app.admin.schema.user import (
|
||||
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 @@ class UserService:
|
||||
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 @@ class UserService:
|
||||
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 @@ class UserService:
|
||||
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 @@ class UserService:
|
||||
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 @@ class UserService:
|
||||
await redis_client.delete_prefix(key_prefix)
|
||||
return count
|
||||
|
||||
@staticmethod
|
||||
async def delete(*, username: str) -> int:
|
||||
async def update_permission(self, *, request: Request, pk: int, type: UserPermissionType) -> int:
|
||||
"""
|
||||
删除用户
|
||||
更新用户权限
|
||||
|
||||
:param username: 用户名
|
||||
: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 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_by_username(db, username)
|
||||
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 pk: 用户 ID
|
||||
:return:
|
||||
"""
|
||||
async with async_db_session.begin() as db:
|
||||
user = await user_dao.get(db, pk)
|
||||
if not user:
|
||||
raise errors.NotFoundError(msg='用户不存在')
|
||||
count = await user_dao.delete(db, user.id)
|
||||
|
||||
@@ -14,12 +14,6 @@ from backend.common.security.rbac import DependsRBAC
|
||||
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,
|
||||
|
||||
@@ -11,16 +11,7 @@ from backend.common.exception import errors
|
||||
|
||||
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 @@ class TaskService:
|
||||
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:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user