diff --git a/backend/app/api/v1/module_ai/mcp/crud.py b/backend/app/api/v1/module_ai/mcp/crud.py index 3f6d3d6d..f1ca8e69 100644 --- a/backend/app/api/v1/module_ai/mcp/crud.py +++ b/backend/app/api/v1/module_ai/mcp/crud.py @@ -39,6 +39,3 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]): async def delete_crud(self, ids: List[int]) -> None: """批量删除""" return await self.delete(ids=ids) - - -mcp_crud: McpCRUD = McpCRUD(auth=AuthSchema()) \ No newline at end of file diff --git a/backend/app/api/v1/module_ai/mcp/param.py b/backend/app/api/v1/module_ai/mcp/param.py index 7794c093..49015a9c 100644 --- a/backend/app/api/v1/module_ai/mcp/param.py +++ b/backend/app/api/v1/module_ai/mcp/param.py @@ -16,7 +16,6 @@ class McpQueryParam: name: Optional[str] = Query(None, description="MCP 名称"), type: Optional[int] = Query(None, description="MCP 类型"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) if name else None diff --git a/backend/app/api/v1/module_application/myapp/controller.py b/backend/app/api/v1/module_application/myapp/controller.py index 14706ed7..a33f6295 100644 --- a/backend/app/api/v1/module_application/myapp/controller.py +++ b/backend/app/api/v1/module_application/myapp/controller.py @@ -19,7 +19,7 @@ from .schema import ( ) -MyAppRouter = APIRouter(route_class=OperationLogRoute, prefix="/application", tags=["应用管理"]) +MyAppRouter = APIRouter(route_class=OperationLogRoute, prefix="/myapp", tags=["应用管理"]) @MyAppRouter.get("/detail/{id}", summary="获取应用详情", description="获取应用详情") async def get_obj_detail_controller( diff --git a/backend/app/api/v1/module_application/myapp/crud.py b/backend/app/api/v1/module_application/myapp/crud.py index 24098bc6..3bc8e248 100644 --- a/backend/app/api/v1/module_application/myapp/crud.py +++ b/backend/app/api/v1/module_application/myapp/crud.py @@ -20,7 +20,7 @@ class ApplicationCRUD(CRUDBase[ApplicationModel, ApplicationCreateSchema, Applic """获取应用详情""" return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ApplicationModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[ApplicationModel]: """列表查询""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_application/myapp/param.py b/backend/app/api/v1/module_application/myapp/param.py index f3c83f57..651f3384 100644 --- a/backend/app/api/v1/module_application/myapp/param.py +++ b/backend/app/api/v1/module_application/myapp/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -14,10 +13,9 @@ class ApplicationQueryParam: name: Optional[str] = Query(None, description="应用名称"), status: Optional[bool] = Query(None, description="是否启用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) if name else None @@ -28,8 +26,6 @@ class ApplicationQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_example/demo/controller.py b/backend/app/api/v1/module_example/demo/controller.py index 3b6c94ef..1a9ad1db 100644 --- a/backend/app/api/v1/module_example/demo/controller.py +++ b/backend/app/api/v1/module_example/demo/controller.py @@ -3,6 +3,7 @@ from fastapi import APIRouter, Body, Depends, Path, UploadFile from fastapi.responses import JSONResponse, StreamingResponse import urllib.parse +import json from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService @@ -39,8 +40,8 @@ async def get_obj_list_controller( auth: AuthSchema = Depends(AuthPermission(permissions=["demo:example:query"])) ) -> JSONResponse: result_dict_list = await DemoService.get_demo_list_service(auth=auth, search=search, order_by=page.order_by) - result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size) - logger.info(f"查询示例列表成功") + result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size) + logger.info("查询示例列表成功") return SuccessResponse(data=result_dict, msg="查询示例列表成功") @DemoRouter.post("/create", summary="创建示例", description="创建示例") @@ -49,7 +50,7 @@ async def create_obj_controller( auth: AuthSchema = Depends(AuthPermission(permissions=["demo:example:create"])) ) -> JSONResponse: result_dict = await DemoService.create_demo_service(auth=auth, data=data) - logger.info(f"创建示例成功: {result_dict}") + logger.info(f"创建示例成功: {result_dict.get('name')}") return SuccessResponse(data=result_dict, msg="创建示例成功") @DemoRouter.put("/update/{id}", summary="修改示例", description="修改示例") @@ -59,7 +60,7 @@ async def update_obj_controller( auth: AuthSchema = Depends(AuthPermission(permissions=["demo:example:update"])) ) -> JSONResponse: result_dict = await DemoService.update_demo_service(auth=auth, id=id, data=data) - logger.info(f"修改示例成功: {result_dict}") + logger.info(f"修改示例成功: {result_dict.get('name')}") return SuccessResponse(data=result_dict, msg="修改示例成功") @DemoRouter.delete("/delete", summary="删除示例", description="删除示例") @@ -93,7 +94,7 @@ async def export_obj_list_controller( return StreamResponse( data=bytes2file_response(export_result), media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers = { + headers={ 'Content-Disposition': 'attachment; filename=example.xlsx' } ) @@ -108,14 +109,14 @@ async def import_obj_list_controller( return SuccessResponse(data=batch_import_result, msg="导入示例成功") @DemoRouter.post('/download/template', summary="获取示例导入模板", description="获取示例导入模板", dependencies=[Depends(AuthPermission(permissions=["demo:example:download"]))]) -async def export_obj_template_controller()-> StreamingResponse: +async def export_obj_template_controller() -> StreamingResponse: example_import_template_result = await DemoService.import_template_download_service() logger.info('获取示例导入模板成功') return StreamResponse( data=bytes2file_response(example_import_template_result), media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers = { + headers={ 'Content-Disposition': f'attachment; filename={urllib.parse.quote("示例导入模板.xlsx")}', 'Access-Control-Expose-Headers': 'Content-Disposition' } diff --git a/backend/app/api/v1/module_example/demo/crud.py b/backend/app/api/v1/module_example/demo/crud.py index cd77b9fa..31ed9909 100644 --- a/backend/app/api/v1/module_example/demo/crud.py +++ b/backend/app/api/v1/module_example/demo/crud.py @@ -13,14 +13,13 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]): def __init__(self, auth: AuthSchema) -> None: """初始化CRUD""" - self.auth = auth super().__init__(model=DemoModel, auth=auth) async def get_by_id_crud(self, id: int) -> Optional[DemoModel]: """详情""" return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DemoModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DemoModel]: """列表查询""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_example/demo/param.py b/backend/app/api/v1/module_example/demo/param.py index af135189..ac73a20f 100644 --- a/backend/app/api/v1/module_example/demo/param.py +++ b/backend/app/api/v1/module_example/demo/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -14,10 +13,9 @@ class DemoQueryParam: name: Optional[str] = Query(None, description="名称"), status: Optional[bool] = Query(None, description="是否启用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) @@ -28,8 +26,6 @@ class DemoQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_example/demo/service.py b/backend/app/api/v1/module_example/demo/service.py index bf17476e..2b3a49b5 100644 --- a/backend/app/api/v1/module_example/demo/service.py +++ b/backend/app/api/v1/module_example/demo/service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import io -from typing import Any, List, Dict +from typing import Any, List, Dict, Optional from fastapi import UploadFile import pandas as pd @@ -25,14 +25,15 @@ class DemoService: async def get_demo_detail_service(cls, auth: AuthSchema, id: int) -> Dict: """详情""" obj = await DemoCRUD(auth).get_by_id_crud(id=id) + if not obj: + raise CustomException(msg="该数据不存在") return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def get_demo_list_service(cls, auth: AuthSchema, search: DemoQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: + async def get_demo_list_service(cls, auth: AuthSchema, search: Optional[DemoQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: """列表查询""" - if order_by: - order_by = eval(order_by) - obj_list = await DemoCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) + search_dict = search.__dict__ if search else None + obj_list = await DemoCRUD(auth).get_list_crud(search=search_dict, order_by=order_by) return [DemoOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod @@ -45,26 +46,33 @@ class DemoService: return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def update_demo_service(cls, auth: AuthSchema, id:int, data: DemoUpdateSchema) -> Dict: + async def update_demo_service(cls, auth: AuthSchema, id: int, data: DemoUpdateSchema) -> Dict: """更新""" + # 检查数据是否存在 obj = await DemoCRUD(auth).get_by_id_crud(id=id) if not obj: raise CustomException(msg='更新失败,该数据不存在') + + # 检查名称是否重复 exist_obj = await DemoCRUD(auth).get(name=data.name) - if exist_obj and exist_obj.id != data.id: + if exist_obj and exist_obj.id != id: raise CustomException(msg='更新失败,名称重复') + obj = await DemoCRUD(auth).update_crud(id=id, data=data) return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def delete_demo_service(cls, auth: AuthSchema, ids: list[int]) -> None: + async def delete_demo_service(cls, auth: AuthSchema, ids: List[int]) -> None: """删除""" if len(ids) < 1: raise CustomException(msg='删除失败,删除对象不能为空') + + # 检查所有要删除的数据是否存在 for id in ids: obj = await DemoCRUD(auth).get_by_id_crud(id=id) if not obj: - raise CustomException(msg='删除失败,该数据不存在') + raise CustomException(msg=f'删除失败,ID为{id}的数据不存在') + await DemoCRUD(auth).delete_crud(ids=ids) @classmethod @@ -90,10 +98,14 @@ class DemoService: for item in data: # 处理状态 item['status'] = '正常' if item.get('status') else '停用' - # 处理公告类型 - item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知' + # 处理创建者 + creator_info = item.get('creator') + if isinstance(creator_info, dict): + item['creator'] = creator_info.get('name', '未知') + else: + item['creator'] = '未知' - return ExcelUtil.export_list2excel(list_data=obj_list, mapping_dict=mapping_dict) + return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict) @classmethod async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str: diff --git a/backend/app/api/v1/module_generator/gencode/controller.py b/backend/app/api/v1/module_generator/gencode/controller.py index 3d11ae43..4d909491 100644 --- a/backend/app/api/v1/module_generator/gencode/controller.py +++ b/backend/app/api/v1/module_generator/gencode/controller.py @@ -2,9 +2,9 @@ from datetime import datetime from typing import List -from fastapi import APIRouter, Depends, Query, Body +from fastapi import APIRouter, Depends, Query, Body, Path from fastapi.responses import StreamingResponse, JSONResponse -from pydantic_validation_decorator import ValidateFields + from app.common.response import SuccessResponse, ErrorResponse, StreamResponse from app.core.dependencies import AuthPermission from app.core.router_class import OperationLogRoute @@ -22,9 +22,9 @@ from app.core.logger import logger GenRouter = APIRouter(route_class=OperationLogRoute, prefix='/gencode', tags=["代码生成模块"]) -@GenRouter.get('/detail/{table_id}', summary="获取业务表详细信息", description="获取业务表详细信息") -async def query_detail_gen_table_controller( - table_id: int, +@GenRouter.get("/detail/{table_id}", summary="获取业务表详细信息", description="获取业务表详细信息") +async def get_gen_table_detail_controller( + table_id: int = Path(..., description="业务表ID"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:query"])) ) -> JSONResponse: gen_table = await GenTableService.get_gen_table_by_id_service(auth, table_id) @@ -35,7 +35,7 @@ async def query_detail_gen_table_controller( return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功") -@GenRouter.get('/list', summary="查询代码生成业务表列表", description="查询代码生成业务表列表") +@GenRouter.get("/list", summary="查询代码生成业务表列表", description="查询代码生成业务表列表") async def get_gen_table_list_controller( page: PaginationQueryParam = Depends(), search: GenTableQueryParam = Depends(), @@ -46,7 +46,8 @@ async def get_gen_table_list_controller( logger.info('获取代码生成业务表列表成功') return SuccessResponse(data=result_dict, msg="获取代码生成业务表列表成功") -@GenRouter.post('/create', summary="创建表结构", description="创建表结构") + +@GenRouter.post("/create", summary="创建表结构", description="创建表结构") async def create_table_controller( sql: str = Query(..., description="SQL语句"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:create"])), @@ -57,10 +58,10 @@ async def create_table_controller( return SuccessResponse(msg="创建表结构成功", data=result) -@GenRouter.put('/update', summary="编辑业务表信息", description="编辑业务表信息") -@ValidateFields(validate_model='edit_gen_table') +@GenRouter.put("/update/{table_id}", summary="编辑业务表信息", description="编辑业务表信息") async def update_gen_table_controller( - data: GenTableUpdateSchema, + table_id: int = Path(..., description="业务表ID"), + data: GenTableUpdateSchema = Body(..., description="业务表信息"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:update"])), current_user: UserOutSchema = Depends(lambda auth: auth.user) ) -> JSONResponse: @@ -73,14 +74,14 @@ async def update_gen_table_controller( updated_data = GenTableUpdateSchema(**update_data) await GenTableService.validate_edit(updated_data) - edit_gen_result = await GenTableService.edit_gen_table_service(auth, updated_data) + edit_gen_result = await GenTableService.update_gen_table_service(auth, updated_data, table_id) logger.info('编辑业务表信息成功') return SuccessResponse(data=edit_gen_result, msg="编辑业务表信息成功") -@GenRouter.delete('/delete', summary="删除业务表信息", description="删除业务表信息") +@GenRouter.delete("/delete", summary="删除业务表信息", description="删除业务表信息") async def delete_gen_table_controller( - table_ids: list[int] = Body(..., description="ID列表"), + table_ids: str = Body(..., description="ID列表,用逗号分隔"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:delete"])) ) -> JSONResponse: delete_gen_table = GenTableDeleteSchema(table_ids=table_ids) @@ -89,8 +90,7 @@ async def delete_gen_table_controller( return result -@GenRouter.post('/import', summary="导入表结构", description="导入表结构") -@ValidateFields(validate_model='edit_gen_table') +@GenRouter.post("/import", summary="导入表结构", description="导入表结构") async def import_gen_table_controller( tables: List[str] = Body(..., description="表名列表", embed=True), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:import"])), @@ -103,9 +103,9 @@ async def import_gen_table_controller( return result -@GenRouter.patch('/batch/out', summary="批量生成代码", description="批量生成代码") +@GenRouter.patch("/batch/out", summary="批量生成代码", description="批量生成代码") async def batch_gen_code_controller( - tables: str = Query(..., description="表名列表"), + tables: str = Query(..., description="表名列表,用逗号分隔"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:operate"])) ) -> StreamResponse: table_names = tables.split(',') if tables else [] @@ -118,9 +118,9 @@ async def batch_gen_code_controller( ) -@GenRouter.post('/out/path/{table_name}', summary="生成代码到指定路径", description="生成代码到指定路径") +@GenRouter.post("/out/path/{table_name}", summary="生成代码到指定路径", description="生成代码到指定路径") async def gen_code_local_controller( - table_name: str, + table_name: str = Path(..., description="表名"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:code"])) ) -> JSONResponse: from app.config.setting import settings @@ -132,9 +132,9 @@ async def gen_code_local_controller( return SuccessResponse(msg="生成代码到指定路径成功", data=result) -@GenRouter.get('/preview/{table_id}', summary="预览代码", description="预览代码") +@GenRouter.get("/preview/{table_id}", summary="预览代码", description="预览代码") async def preview_code_controller( - table_id: int, + table_id: int = Path(..., description="业务表ID"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:gencode:query"])) ) -> JSONResponse: preview_code_result = await GenTableService.preview_code_service(auth, table_id) @@ -142,21 +142,21 @@ async def preview_code_controller( return SuccessResponse(data=preview_code_result, msg="预览代码成功") -@GenRouter.get('/db/list', summary="查询数据库表列表", description="查询数据库表列表") +@GenRouter.get("/db/list", summary="查询数据库表列表", description="查询数据库表列表") async def get_gen_db_table_list_controller( page: PaginationQueryParam = Depends(), search: GenTableQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:dblist:query"])) ) -> JSONResponse: - result_dict_list = await GenTableService.get_gen_db_table_list_service(auth=auth, query_object=search, is_page=False) - result_dict = await PaginationService.paginate(data_list=result_dict_list["items"], page_no=page.page_no, page_size=page.page_size) + result_dict_list = await GenTableService.get_gen_db_table_list_service(auth=auth, search=search, order_by=page.order_by) + result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size) logger.info('获取数据库表列表成功') return SuccessResponse(data=result_dict, msg="获取数据库表列表成功") -@GenRouter.post('/sync/db/{table_name}', summary="同步数据库", description="同步数据库") +@GenRouter.post("/sync/db/{table_name}", summary="同步数据库", description="同步数据库") async def sync_db_controller( - table_name: str, + table_name: str = Path(..., description="表名"), auth: AuthSchema = Depends(AuthPermission(permissions=["generator:db:sync"])) ) -> JSONResponse: result = await GenTableService.sync_db_service(auth, table_name) diff --git a/backend/app/api/v1/module_generator/gencode/crud.py b/backend/app/api/v1/module_generator/gencode/crud.py index 2ffd53b3..f170f1f2 100644 --- a/backend/app/api/v1/module_generator/gencode/crud.py +++ b/backend/app/api/v1/module_generator/gencode/crud.py @@ -1,82 +1,115 @@ # -*- coding:utf-8 -*- from datetime import datetime, time +import json from sqlalchemy.engine.row import Row from sqlalchemy import delete, func, select, text, update from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from typing import List, Optional, Sequence, Any, Dict +from app.api.v1.module_system.params.schema import ParamsCreateSchema +from app.core.logger import logger + from .model import GenTableModel, GenTableColumnModel from app.config.setting import settings from app.common.request import PaginationService -from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema +from .schema import GenTableCreateSchema, GenTableUpdateSchema, GenTableOutSchema, GenTableDeleteSchema, GenTableColumnCreateSchema, GenTableColumnUpdateSchema, GenTableColumnOutSchema, GenTableColumnDeleteSchema, GenDBTableSchema from .param import GenTableQueryParam from app.core.base_crud import CRUDBase from app.api.v1.module_system.auth.schema import AuthSchema class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateSchema]): - """ - 代码生成业务表模块数据库操作层 - """ + """代码生成业务表模块数据库操作层""" def __init__(self, auth: AuthSchema) -> None: """初始化CRUD""" super().__init__(model=GenTableModel, auth=auth) - async def get_gen_table_by_id(self, db: AsyncSession, table_id: int) -> Optional[GenTableModel]: + async def get_gen_table_by_id(self, table_id: int) -> Optional[GenTableModel]: """ 根据业务表id获取需要生成的业务表信息 - :param db: orm对象 :param table_id: 业务表id :return: 需要生成的业务表信息对象 """ - gen_table_info = ( + gen_table = ( ( - await db.execute( - select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.id == table_id) + await self.db.execute( + select(GenTableModel) + .options(selectinload(GenTableModel.columns)) + .where(GenTableModel.id == table_id) ) ) .scalars() .first() ) - return gen_table_info + return gen_table - async def get_gen_table_by_name(self, db: AsyncSession, table_name: str) -> Optional[GenTableModel]: + async def get_gen_table_by_name(self, table_name: str) -> Optional[GenTableModel]: """ 根据业务表名称获取需要生成的业务表信息 - :param db: orm对象 :param table_name: 业务表名称 :return: 需要生成的业务表信息对象 """ - gen_table_info = ( + gen_table = ( ( - await db.execute( - select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.table_name == table_name) + await self.db.execute( + select(GenTableModel) + .options(selectinload(GenTableModel.columns)) + .where(GenTableModel.table_name == table_name) ) ) .scalars() .first() ) - return gen_table_info + return gen_table - async def get_gen_table_all(self, db: AsyncSession) -> Sequence[GenTableModel]: + async def get_gen_table_all(self) -> Sequence[GenTableModel]: """ 获取所有业务表信息 :param db: orm对象 :return: 所有业务表信息 """ - gen_table_all = (await db.execute(select(GenTableModel).options(selectinload(GenTableModel.columns)))).scalars().all() + gen_table_all = ( + await self.db.execute( + select(GenTableModel) + .options(selectinload(GenTableModel.columns))) + ).scalars().all() return gen_table_all + + async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None): + """ + 根据查询参数获取代码生成业务表列表信息 - async def create_table_by_sql_dao(self, db: AsyncSession, sql_statements: List) -> None: + :param query_object: 查询参数对象 + :return: 代码生成业务表列表信息对象 + """ + # 构建查询条件 + conditions = await self.__build_conditions(**search.__dict__) if search else [] + query = ( + select(GenTableModel) + .options(selectinload(GenTableModel.columns)) + .where( + *conditions + ) + .order_by(GenTableModel.created_at.desc()) + .distinct() + ) + + # 获取所有数据 + result = await self.db.execute(query) + gen_table_all = list(result.scalars().all()) + + return gen_table_all + + async def create_table_by_sql(self, sql: str) -> bool: """ 根据sql语句创建表结构 @@ -84,130 +117,119 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateS :param sql_statements: sql语句的ast列表 :return: """ - for sql_statement in sql_statements: - sql = sql_statement.sql(dialect=settings.DATABASE_TYPE) - await db.execute(text(sql)) + await self.db.execute(text(sql)) + try: + await self.db.execute(text(sql)) + # 提交事务 + await self.db.commit() + await self.db.flush() + return True + except Exception as e: + # 如果发生异常,回滚事务 + await self.db.rollback() + logger.error(f"创建表时发生错误: {e}") + return False - async def get_gen_table_list(self, db: AsyncSession, query_object: GenTableQueryParam, is_page: bool = False): + async def add_gen_table(self, add_model: GenTableCreateSchema) -> GenTableModel: """ - 根据查询参数获取代码生成业务表列表信息 - - :param db: orm对象 - :param query_object: 查询参数对象 - :param is_page: 是否开启分页 - :return: 代码生成业务表列表信息对象 + 增加 """ - # 构建查询条件 - conditions = [] - - # 访问table_name属性 - if getattr(query_object, 'table_name', None) and query_object.table_name[1]: - conditions.append(func.lower(GenTableModel.table_name).like(f'%{str(query_object.table_name[1]).lower()}%')) - - # 访问table_comment属性 - if getattr(query_object, 'table_comment', None): - conditions.append(func.lower(GenTableModel.table_comment).like(f'%{str(query_object.table_comment).lower()}%')) - - # 访问created_at属性而不是start_time和end_time - if hasattr(query_object, 'created_at') and query_object.created_at: - if isinstance(query_object.created_at, tuple) and query_object.created_at[0] == "between": - conditions.append(GenTableModel.created_at.between(*query_object.created_at[1])) + gen_table = GenTableModel(**add_model.model_dump(exclude_unset=True, exclude={'sub', 'tree', 'crud'})) + self.db.add(gen_table) + await self.db.flush() + return gen_table + + async def delete_gen_table(self, delete_model: GenTableDeleteSchema) -> None: + """ + 删除 + """ + await self.db.execute(delete(GenTableModel).where(GenTableModel.id.in_(delete_model.table_ids))) + await self.db.flush() + + async def edit_gen_table(self, table_id: int, edit_model: GenTableUpdateSchema, auto_commit: bool = True): + """ + 修改 + """ + edit_dict_data = edit_model.model_dump(exclude_unset=True) + await self.db.execute(update(GenTableModel).where(GenTableModel.id == table_id).values(**edit_dict_data)) + await self.db.flush() + if auto_commit: + await self.db.commit() + return edit_model - query = ( - select(GenTableModel) - .options(selectinload(GenTableModel.columns)) - .where(*conditions) - .order_by(GenTableModel.created_at.desc()) - .distinct() - ) - - # 获取所有数据 - result = await db.execute(query) - all_data = list(result.scalars().all()) - - # 使用PaginationService.paginate进行分页 - if is_page and query_object.page_no is not None and query_object.page_size is not None: - paginated_result = await PaginationService.paginate( - data_list=all_data, - page_no=query_object.page_no, - page_size=query_object.page_size - ) - return paginated_result - else: - return { - "items": all_data, - "total": len(all_data), - "page_no": None, - "page_size": None, - "has_next": False - } - - async def get_gen_db_table_list(self, db: AsyncSession, query_object: GenTableQueryParam, is_page: bool = False): + async def get_gen_db_table_list(self, table_name: Optional[str] = None) -> list[Any]: """ 根据查询参数获取数据库列表信息 :param db: orm对象 - :param query_object: 查询参数对象 - :param is_page: 是否开启分页 + :param search: 查询参数对象 + :param order_by: 排序字段 :return: 数据库列表信息对象 """ - query_sql = """ - SELECT table_name as table_name, - table_comment as table_comment, - create_time as create_time, - update_time as update_time - from - information_schema.tables - where - table_schema = (select database()) - and table_name not like 'apscheduler\_%' - and table_name not like 'gen\_%' - and table_name not in (select table_name from gen_table) - """ - # 根据param.py中的定义,table_name是元组形式("like", value) - if getattr(query_object, 'table_name', None) and query_object.table_name[1]: - query_sql += """and lower(table_name) like lower(concat('%', :table_name, '%'))""" + + # 使用更健壮的方式检测数据库方言 + if settings.DATABASE_TYPE == 'postgresql': + query_sql = """ + SELECT + table_catalog as database_name, + table_name as table_name, + table_type as table_type, + table_schema as table_comment + from + information_schema.tables + where + table_catalog = (select current_database()) + and is_insertable_into = 'YES' + and table_schema = 'public' + """ + elif settings.DATABASE_TYPE == 'mysql': + query_sql = """ + SELECT + table_schema as database_name, + table_name as table_name, + table_type as table_type, + table_comment as table_comment + from + information_schema.tables + where + table_schema = (select database()) + """ + else: + query_sql = f""" + SELECT + '{settings.DATABASE_NAME}' as database_name, + name as table_name, + type as table_type, + tbl_name as table_comment + from + sqlite_master + where + type = 'table' + """ - # 处理table_comment字段(如果有) - if getattr(query_object, 'table_comment', None): - query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))""" - - # 构建查询参数 - query_params = {} - - # 添加table_name查询参数 - if getattr(query_object, 'table_name', None) and query_object.table_name[1]: - query_params['table_name'] = query_object.table_name[1] - - # 添加table_comment查询参数 - if getattr(query_object, 'table_comment', None): - query_params['table_comment'] = query_object.table_comment - - query_sql += """order by create_time desc""" - query = text(query_sql).bindparams(**query_params) + # 直接执行文本SQL查询,避免SQLAlchemy自动添加额外的SELECT ) + query = text(query_sql).bindparams() # 执行查询 - result = await db.execute(query) - all_data = list(result.fetchall()) - - # 使用PaginationService.paginate进行分页 - if is_page and query_object.page_no is not None and query_object.page_size is not None: - paginated_result = await PaginationService.paginate( - data_list=all_data, - page_no=query_object.page_no, - page_size=query_object.page_size - ) - return paginated_result - else: - return { - "items": all_data, - "total": len(all_data), - "page_no": None, - "page_size": None, - "has_next": False - } + result = await self.db.execute(query) + all_data = result.fetchall() - async def get_gen_db_table_list_by_names(self, db: AsyncSession, table_names: List[str]): + # 将Row对象转换为字典列表,解决JSON序列化问题 + dict_data = [] + for row in all_data: + # 检查row是否为Row对象 + if isinstance(row, Row): + # 使用._mapping获取字典 + dict_row = GenDBTableSchema(**dict(row._mapping)).model_dump() + if table_name: + dict_row['table_name'] = table_name + dict_data.append(dict_row) + else: + dict_row = GenDBTableSchema(**dict(row)).model_dump() + dict_data.append(dict_row) + return dict_data + + async def get_gen_db_table_list_by_names(self, table_names: List[str]): """ 根据业务表名称组获取数据库列表信息 @@ -215,45 +237,99 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableCreateSchema, GenTableUpdateS :param table_names: 业务表名称组 :return: 数据库列表信息对象 """ - query_sql = """ - select - table_name as table_name, - table_comment as table_comment, - create_time as create_time, - update_time as update_time - from - information_schema.tables - where - table_schema = (select database()) - and table_name in :table_names - """ + # 使用更健壮的方式检测数据库方言 + if settings.DATABASE_TYPE == 'postgresql': + query_sql = """ + SELECT + table_catalog as database_name, + table_name as table_name, + table_type as table_type, + table_schema as table_comment + from + information_schema.tables + where + table_catalog = (select current_database()) + and is_insertable_into = 'YES' + and table_schema = 'public' + and table_name in :table_names + """ + elif settings.DATABASE_TYPE == 'mysql': + query_sql = """ + SELECT + table_schema as database_name, + table_name as table_name, + table_type as table_type, + table_comment as table_comment + from + information_schema.tables + where + table_schema = (select database()) + and table_name in :table_names + """ + else: + query_sql = f""" + SELECT + '{settings.DATABASE_NAME}' as database_name, + name as table_name, + type as table_type, + tbl_name as table_comment + from + sqlite_master + where + type = 'table' + and table_name in :table_names + """ + query = text(query_sql).bindparams(table_names=tuple(table_names)) - gen_db_table_list = (await db.execute(query)).fetchall() + gen_db_table_list = (await self.db.execute(query)).fetchall() return gen_db_table_list class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnCreateSchema, GenTableColumnUpdateSchema]): - """ - 代码生成业务表字段模块数据库操作层 - """ + """代码生成业务表字段模块数据库操作层""" def __init__(self, auth: AuthSchema) -> None: """初始化CRUD""" super().__init__(model=GenTableColumnModel, auth=auth) - async def get_gen_table_column_list_by_table_id(self, db: AsyncSession, table_id: int) -> Sequence[GenTableColumnModel]: + async def get_by_id_crud(self, column_id: int) -> Optional[GenTableColumnModel]: + """详情""" + return await self.get(id=column_id) + + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[GenTableColumnModel]: + """列表查询""" + return await self.list(search=search, order_by=order_by) + + async def create_crud(self, data: GenTableColumnCreateSchema) -> Optional[GenTableColumnModel]: + """创建""" + return await self.create(data=data) + + async def update_crud(self, id: int, data: GenTableColumnUpdateSchema) -> Optional[GenTableColumnModel]: + """更新""" + return await self.update(id=id, data=data) + + async def delete_crud(self, data: GenTableColumnDeleteSchema) -> None: + """批量删除""" + return await self.delete(ids=data.column_ids) + + async def get_gen_table_column_list_by_table_id_crud(self, table_id: int) -> Sequence[GenTableColumnModel]: + """根据业务表id获取需要生成的业务表字段列表信息""" + return await self.list(search={"table_id": table_id}) + + async def get_gen_table_column_list_by_table_id(self, table_id: int) -> Sequence[GenTableColumnModel]: """ 根据业务表id获取需要生成的业务表字段列表信息 - :param db: orm对象 :param table_id: 业务表id :return: 需要生成的业务表字段列表信息对象 """ gen_table_column_list = ( ( - await db.execute( - select(GenTableColumnModel).where(GenTableColumnModel.table_id == table_id).order_by(GenTableColumnModel.sort) + await self.db.execute( + select(GenTableColumnModel) + .where(GenTableColumnModel.table_id == table_id) + .order_by(GenTableColumnModel.sort) ) ) .scalars() @@ -270,33 +346,60 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnCreateSchem :param table_name: 业务表名称 :return: 业务表字段列表信息对象 """ - query_sql = """ - select - column_name as column_name, - case - when is_nullable = 'no' and column_key != 'PRI' then '1' - else '0' - end as is_required, - case - when column_key = 'PRI' then '1' - else '0' - end as is_pk, - ordinal_position as sort, - column_comment as column_comment, - case - when extra = 'auto_increment' then '1' - else '0' - end as is_increment, - column_type as column_type - from - information_schema.columns - where - table_schema = (select database()) - and table_name = :table_name - order by - ordinal_position - """ - query = text(query_sql).bindparams(table_name=table_name) - gen_db_table_columns = (await db.execute(query)).fetchall() + # 兼容SQLite和MySQL/PostgreSQL + if str(db.bind.dialect) == 'sqlite': + query_sql = """ + pragma table_info(:table_name) + """ + query = text(query_sql).bindparams(table_name=table_name) + gen_db_table_columns_raw = (await db.execute(query)).fetchall() + + # 转换SQLite的pragma结果为与information_schema.columns兼容的格式 + gen_db_table_columns = [] + for col in gen_db_table_columns_raw: + # col格式: (cid, name, type, notnull, dflt_value, pk) + is_required = '1' if col[3] == 1 and col[5] == 0 else '0' + is_pk = '1' if col[5] == 1 else '0' + is_increment = '0' # SQLite没有auto_increment标记,需要额外判断 + + # 构建兼容的结果行 + gen_db_table_columns.append({ + 'column_name': col[1], + 'is_required': is_required, + 'is_pk': is_pk, + 'sort': col[0], # 使用cid作为排序 + 'column_comment': '', # SQLite不存储列注释 + 'is_increment': is_increment, + 'column_type': col[2] + }) + else: + query_sql = """ + select + column_name as column_name, + case + when is_nullable = 'no' and column_key != 'PRI' then '1' + else '0' + end as is_required, + case + when column_key = 'PRI' then '1' + else '0' + end as is_pk, + ordinal_position as sort, + column_comment as column_comment, + case + when extra = 'auto_increment' then '1' + else '0' + end as is_increment, + column_type as column_type + from + information_schema.columns + where + table_schema = (select database()) + and table_name = :table_name + order by + ordinal_position + """ + query = text(query_sql).bindparams(table_name=table_name) + gen_db_table_columns = (await db.execute(query)).fetchall() return gen_db_table_columns \ No newline at end of file diff --git a/backend/app/api/v1/module_generator/gencode/model.py b/backend/app/api/v1/module_generator/gencode/model.py index 2da89f68..48f2885f 100644 --- a/backend/app/api/v1/module_generator/gencode/model.py +++ b/backend/app/api/v1/module_generator/gencode/model.py @@ -30,7 +30,13 @@ class GenTableModel(CreatorMixin): gen_path: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='/', comment='生成路径(不填默认项目路径)') options: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True, comment='其它生成选项') - columns: Mapped[List['GenTableColumnModel']] = relationship('GenTableColumnModel', order_by='GenTableColumnModel.sort', back_populates='table') + # 关系定义 + columns: Mapped[List['GenTableColumnModel']] = relationship( + 'GenTableColumnModel', + order_by='GenTableColumnModel.sort', + back_populates='tables', + cascade='all, delete-orphan' + ) class GenTableColumnModel(CreatorMixin): @@ -58,5 +64,16 @@ class GenTableColumnModel(CreatorMixin): dict_type: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='字典类型') sort: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment='排序') - table_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('gen_table.id'), nullable=True, comment='归属表编号') - table: Mapped['GenTableModel'] = relationship('GenTableModel', back_populates='columns') \ No newline at end of file + # 外键关系 + table_id: Mapped[Optional[int]] = mapped_column( + Integer, + ForeignKey('gen_table.id', ondelete='CASCADE'), + nullable=True, + comment='归属表编号' + ) + + # 关系定义 + tables: Mapped['GenTableModel'] = relationship( + 'GenTableModel', + back_populates='columns' + ) \ No newline at end of file diff --git a/backend/app/api/v1/module_generator/gencode/param.py b/backend/app/api/v1/module_generator/gencode/param.py index dfaf3cfd..e9e35f1f 100644 --- a/backend/app/api/v1/module_generator/gencode/param.py +++ b/backend/app/api/v1/module_generator/gencode/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -8,50 +7,44 @@ from app.core.validator import DateTimeStr class GenTableQueryParam: - """数据库表查询参数""" + """代码生成业务表查询参数""" def __init__( self, table_name: Optional[str] = Query(None, description="表名称"), - status: Optional[bool] = Query(None, description="是否启用"), + table_comment: Optional[str] = Query(None, description="表注释"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), - ) -> None: - # 存储查询条件,不直接赋值给父类属性 + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), + ) -> None: + # 模糊查询字段 self.table_name = ("like", table_name) + self.table_comment = ("like", table_comment) # 精确查询字段 self.creator_id = creator - self.status = status # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) class GenTableColumnQueryParam: - """数据库表字段查询参数""" + """代码生成业务表字段查询参数""" def __init__( self, column_name: Optional[str] = Query(None, description="列名称"), - status: Optional[bool] = Query(None, description="是否启用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), - ) -> None: - # 存储查询条件,不直接赋值给父类属性 + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), + ) -> None: + # 模糊查询字段 self.column_name = ("like", column_name) # 精确查询字段 self.creator_id = creator - self.status = status # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) \ No newline at end of file + self.created_at = ("between", (start_time, end_time)) \ No newline at end of file diff --git a/backend/app/api/v1/module_generator/gencode/schema.py b/backend/app/api/v1/module_generator/gencode/schema.py index 7e9125f9..bc9656c7 100644 --- a/backend/app/api/v1/module_generator/gencode/schema.py +++ b/backend/app/api/v1/module_generator/gencode/schema.py @@ -3,90 +3,65 @@ from typing import List, Literal, Optional from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic.alias_generators import to_camel -from pydantic_validation_decorator import NotBlank from app.utils.string_util import StringUtil from app.common.constant import GenConstant from app.core.base_schema import BaseSchema -class GenTableCreateSchema(BaseModel): - """ - 代码生成业务表对应pydantic模型 - """ +class GenTableOptionModel(BaseModel): model_config = ConfigDict(from_attributes=True) - table_name: str = Field(default=..., description='表名称') - table_comment: str = Field(default=..., description='表描述') + parent_menu_id: Optional[int] = Field(default=None, description='所属父级分类') + tree_code: Optional[str] = Field(default=None, description='tree_code') + tree_name: Optional[str] = Field(default=None, description='tree_name') + tree_parent_code: Optional[str] = Field(default=None, description='tree_parent_code') + +class GenDBTableSchema(BaseModel): + + model_config = ConfigDict(from_attributes=True) + + database_name: Optional[str] = Field(default=None, description='数据库名称') + table_name: Optional[str] = Field(default=None, description='表名称') + table_type: Optional[str] = Field(default=None, description='表类型') + table_comment: Optional[str] = Field(default=None, description='表描述') + + +class GenTableCreateSchema(BaseModel): + """ + 代码生成业务表创建模型 + """ + model_config = ConfigDict(from_attributes=True) + + table_name: Optional[str] = Field(default=None, description='表名称') + table_comment: Optional[str] = Field(default=None, description='表描述') sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名') - sub_table_fk_name: str = Field(default=..., description='子表关联的外键名') - class_name: str = Field(default=..., description='实体类名称') - tpl_category: Optional[str] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)') + sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名') + class_name: Optional[str] = Field(default=None, description='实体类名称') + tpl_category: Optional[Literal['crud', 'tree']] = Field(default=None, description='使用的模板(crud单表操作 tree树表操作)') tpl_web_type: Optional[str] = Field(default=None, description='前端模板类型(element-ui模版 element-plus模版)') - package_name: str = Field(default=..., description='生成包路径') - module_name: str = Field(default=..., description='生成模块名') - business_name: str = Field(default=..., description='生成业务名') - function_name: str = Field(default=..., description='生成功能名') + package_name: Optional[str] = Field(default=None, description='生成包路径') + module_name: Optional[str] = Field(default=None, description='生成模块名') + business_name: Optional[str] = Field(default=None, description='生成业务名') + function_name: Optional[str] = Field(default=None, description='生成功能名') function_author: Optional[str] = Field(default=None, description='生成功能作者') gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1自定义路径)') gen_path: Optional[str] = Field(default=None, description='生成路径(不填默认项目路径)') options: Optional[str] = Field(default=None, description='其它生成选项') - - @NotBlank(field_name='table_name', message='表名称不能为空') - def get_table_name(self): - return self.table_name - - @NotBlank(field_name='table_comment', message='表描述不能为空') - def get_table_comment(self): - return self.table_comment - - @NotBlank(field_name='class_name', message='实体类名称不能为空') - def get_class_name(self): - return self.class_name - - @NotBlank(field_name='package_name', message='生成包路径不能为空') - def get_package_name(self): - return self.package_name - - @NotBlank(field_name='module_name', message='生成模块名不能为空') - def get_module_name(self): - return self.module_name - - @NotBlank(field_name='business_name', message='生成业务名不能为空') - def get_business_name(self): - return self.business_name - - @NotBlank(field_name='function_name', message='生成功能名不能为空') - def get_function_name(self): - return self.function_name - - @NotBlank(field_name='function_author', message='生成功能作者不能为空') - def get_function_author(self): - return self.function_author - - def validate_fields(self): - self.get_table_name() - self.get_table_comment() - self.get_class_name() - self.get_package_name() - self.get_module_name() - self.get_business_name() - self.get_function_name() - self.get_function_author() + description: Optional[str] = Field(default=None, description='功能描述') class GenTableUpdateSchema(GenTableCreateSchema): """ - 代码生成业务表模型 + 代码生成业务表更新模型 """ - pk_column: Optional['GenTableColumnUpdateSchema'] = Field(default=None, description='主键信息') sub_table: Optional['GenTableUpdateSchema'] = Field(default=None, description='子表信息') - columns: List['GenTableColumnUpdateSchema'] = Field(default=..., description='表列信息') - tree_code: Optional[str] = Field(default=None, description='树编码字段') + columns: Optional[List['GenTableColumnUpdateSchema']] = Field(default=None, description='表列信息') + tree_code: Optional[str] = Field(default=None, description='树编码字段tree_code') tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段') - tree_name: Optional[str] = Field(default=None, description='树名称字段') + tree_name: Optional[str] = Field(default=None, description='树名称字段ree_name') parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段') parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段') sub: Optional[bool] = Field(default=None, description='是否为子表') @@ -102,7 +77,9 @@ class GenTableUpdateSchema(GenTableCreateSchema): class GenTableOutSchema(GenTableUpdateSchema, BaseSchema): - """响应模型""" + """ + 代码生成业务表响应模型 + """ model_config = ConfigDict(from_attributes=True) @@ -110,26 +87,23 @@ class GenTableDeleteSchema(BaseModel): """ 删除代码生成业务表模型 """ - model_config = ConfigDict(alias_generator=to_camel) - table_ids: str = Field(description='需要删除的代码生成业务表ID') + table_ids: str = Field(..., description='需要删除的代码生成业务表ID') class GenTableColumnCreateSchema(BaseModel): """ - 代码生成业务表字段对应pydantic模型 + 代码生成业务表字段创建模型 """ - model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) table_id: Optional[int] = Field(default=None, description='归属表编号') - - column_name: str = Field(default=..., description='列名称') + column_name: Optional[str] = Field(default=None, description='列名称') column_comment: Optional[str] = Field(default=None, description='列描述') - column_type: str = Field(default=..., description='列类型') + column_type: Optional[str] = Field(default=None, description='列类型') python_type: Optional[str] = Field(default=None, description='PYTHON类型') - python_field: str = Field(default=..., description='PYTHON字段名') + python_field: Optional[str] = Field(default=None, description='PYTHON字段名') is_pk: Optional[str] = Field(default=None, description='是否主键(1是)') is_increment: Optional[str] = Field(default=None, description='是否自增(1是)') is_required: Optional[str] = Field(default=None, description='是否必填(1是)') @@ -139,59 +113,23 @@ class GenTableColumnCreateSchema(BaseModel): is_list: Optional[str] = Field(default=None, description='是否列表字段(1是)') is_query: Optional[str] = Field(default=None, description='是否查询字段(1是)') query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)') - html_type: str = Field(default=..., description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)') - dict_type: str = Field(default=..., description='字典类型') + html_type: Optional[str] = Field(default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)') + dict_type: Optional[str] = Field(default=None, description='字典类型') sort: Optional[int] = Field(default=None, description='排序') - - @NotBlank(field_name='python_field', message='Python属性不能为空') - def get_python_field(self): - return self.python_field - - def validate_fields(self): - self.get_python_field() + description: Optional[str] = Field(default=None, description='功能描述') class GenTableColumnUpdateSchema(GenTableColumnCreateSchema): """ - 代码生成业务表字段模型 + 代码生成业务表字段更新模型 """ - - cap_python_field: Optional[str] = Field(default=None, description='字段大写形式') - pk: Optional[bool] = Field(default=None, description='是否主键') - increment: Optional[bool] = Field(default=None, description='是否自增') - required: Optional[bool] = Field(default=None, description='是否必填') - unique: Optional[bool] = Field(default=None, description='是否唯一') - insert: Optional[bool] = Field(default=None, description='是否为插入字段') - edit: Optional[bool] = Field(default=None, description='是否编辑字段') - list: Optional[bool] = Field(default=None, description='是否列表字段') - query: Optional[bool] = Field(default=None, description='是否查询字段') - super_column: Optional[bool] = Field(default=None, description='是否为基类字段') - usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单') - - @model_validator(mode='after') - def check_some_is(self) -> 'GenTableColumnUpdateSchema': - self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None - self.pk = True if self.is_pk and self.is_pk == '1' else False - self.increment = True if self.is_increment and self.is_increment == '1' else False - self.required = True if self.is_required and self.is_required == '1' else False - self.unique = True if self.is_unique and self.is_unique == '1' else False - self.insert = True if self.is_insert and self.is_insert == '1' else False - self.edit = True if self.is_edit and self.is_edit == '1' else False - self.list = True if self.is_list and self.is_list == '1' else False - self.query = True if self.is_query and self.is_query == '1' else False - self.super_column = ( - True - if StringUtil.equals_any_ignore_case(self.python_field, GenConstant.TREE_ENTITY + GenConstant.BASE_ENTITY) - else False - ) - self.usable_column = ( - True if StringUtil.equals_any_ignore_case(self.python_field, ['parentId', 'orderNum', 'remark']) else False - ) - return self + ... class GenTableColumnOutSchema(GenTableColumnUpdateSchema, BaseSchema): - """响应模型""" + """ + 代码生成业务表字段响应模型 + """ model_config = ConfigDict(from_attributes=True) @@ -199,7 +137,6 @@ class GenTableColumnDeleteSchema(BaseModel): """ 删除代码生成业务表字段模型 """ - model_config = ConfigDict(from_attributes=True) - column_ids: str = Field(description='需要删除的代码生成业务表字段ID') + column_ids: List[int] = Field(..., description='需要删除的代码生成业务表字段ID') \ No newline at end of file diff --git a/backend/app/api/v1/module_generator/gencode/service.py b/backend/app/api/v1/module_generator/gencode/service.py index 328d4f7c..5061b4e5 100644 --- a/backend/app/api/v1/module_generator/gencode/service.py +++ b/backend/app/api/v1/module_generator/gencode/service.py @@ -5,8 +5,8 @@ import json import os import zipfile from datetime import datetime -from sqlalchemy.ext.asyncio import AsyncSession from typing import Any, List, Dict, Optional, Sequence +from sqlalchemy.ext.asyncio import AsyncSession from app.config.setting import settings from app.core.base_model import CamelCaseUtil @@ -28,115 +28,124 @@ GEN_PATH = "generated_code" # 默认生成路径 class GenTableService: - """ - 代码生成业务表服务层 - """ + """代码生成业务表服务层""" + + @classmethod + async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> Dict: + """获取业务表详细信息""" + gen_table = await cls.get_gen_table_by_id_service(auth, table_id) + gen_tables = await cls.get_gen_table_all_service(auth) + gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_service(auth, table_id) + return dict(info=gen_table, rows=gen_columns, tables=gen_tables) @classmethod async def get_gen_table_list_service( cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False - ): - """ - 获取代码生成业务表列表信息service - - :param auth: 认证信息 - :param query_object: 查询参数对象 - :param is_page: 是否开启分页 - :return: 代码生成业务列表信息对象 - """ + ) -> Dict: + """获取代码生成业务表列表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) - gen_table_list_result = await gen_table_dao.get_gen_table_list(auth.db, query_object, is_page) - + gen_table_list_result = await gen_table_dao.get_gen_table_list(query_object, is_page) return gen_table_list_result @classmethod - async def get_gen_db_table_list_service( - cls, auth: AuthSchema, query_object: GenTableQueryParam, is_page: bool = False - ): - """ - 获取数据库列表信息service - - :param auth: 认证信息 - :param query_object: 查询参数对象 - :param is_page: 是否开启分页 - :return: 数据库列表信息对象 - """ - gen_table_dao = GenTableCRUD(auth=auth) - gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list(auth.db, query_object, is_page) - + async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam, order_by: Optional[List[Dict[str, str]]] = None) -> list[Any]: + """获取数据库列表信息""" + # 确保db是AsyncSession类型 + gen_db_table_list_result = await GenTableCRUD(auth=auth).get_gen_db_table_list(search, order_by) return gen_db_table_list_result @classmethod - async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> list[GenTableOutSchema]: - """ - 根据表名称组获取数据库列表信息service - - :param auth: 认证信息 - :param table_names: 表名称组 - :return: 数据库列表信息对象 - """ - gen_table_dao = GenTableCRUD(auth=auth) - gen_db_table_list_result = await gen_table_dao.get_gen_db_table_list_by_names(auth.db, table_names) - + async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> List[GenTableOutSchema]: + """根据表名称组获取数据库列表信息""" + gen_db_table_list_result = await GenTableCRUD(auth=auth).get_gen_db_table_list_by_names(table_names) return [GenTableOutSchema(**gen_table) for gen_table in CamelCaseUtil.transform_result(gen_db_table_list_result)] @classmethod async def import_gen_table_service( cls, auth: AuthSchema, gen_table_list: List[GenTableOutSchema], current_user: UserOutSchema - ): - """ - 导入表结构service - - :param auth: 认证信息 - :param gen_table_list: 导入表列表 - :param current_user: 当前用户信息对象 - :return: 导入结果 - """ + ) -> SuccessResponse: + """导入表结构""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') try: gen_table_dao = GenTableCRUD(auth=auth) gen_table_column_dao = GenTableColumnCRUD(auth=auth) for table in gen_table_list: table_name = table.table_name - GenUtils.init_table(table, current_user.username) # 使用username而不是user.user_name + GenUtils.init_table(table, current_user.username) add_gen_table = await gen_table_dao.create(data=table.model_dump()) if add_gen_table: - table.table_id = add_gen_table.id - gen_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name or "") + # 使用id而不是table_id + table.id = add_gen_table.id + gen_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(db, table_name or "") for column in [ GenTableColumnOutSchema(**gen_table_column) for gen_table_column in CamelCaseUtil.transform_result(gen_table_columns) ]: GenUtils.init_column_field(column, table) await gen_table_column_dao.create(data=column.model_dump()) - await auth.db.commit() + if isinstance(db, AsyncSession): + await db.commit() return SuccessResponse(msg='导入成功') except Exception as e: - try: - await auth.db.rollback() - except: - pass # 忽略回滚错误 + if isinstance(db, AsyncSession): + try: + await db.rollback() + except: + pass # 忽略回滚错误 raise CustomException(msg=f'导入失败, {str(e)}') @classmethod - async def edit_gen_table_service(cls, auth: AuthSchema, page_object: GenTableUpdateSchema) -> Dict[str, Any]: - """ - 编辑业务表信息service + async def create_table_service(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse: + """创建表结构""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') + gen_table_dao = GenTableCRUD(auth=auth) + + try: + # 执行SQL语句创建表 + await gen_table_dao.create_table_by_sql_dao(db, [sql]) + if isinstance(db, AsyncSession): + await db.commit() + return SuccessResponse(msg='创建表结构成功') + except Exception as e: + if isinstance(db, AsyncSession): + try: + await db.rollback() + except: + pass # 忽略回滚错误 + raise CustomException(msg=f'创建表结构失败: {str(e)}') - :param auth: 认证信息 - :param page_object: 编辑业务表对象 - :return: 编辑业务表校验结果 - """ + @classmethod + async def update_gen_table_service(cls, auth: AuthSchema, page_object: GenTableUpdateSchema, table_id: int) -> Dict[str, Any]: + """编辑业务表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) gen_table_column_dao = GenTableColumnCRUD(auth=auth) - # 检查必要字段是否存在 - if getattr(page_object, 'table_id', None) is None: - raise CustomException(msg='业务表ID不能为空') - edit_gen_table = page_object.model_dump(exclude_unset=True, by_alias=True) - gen_table_info = await cls.get_gen_table_by_id_service(auth, page_object.table_id) - if gen_table_info.table_id: + gen_table_info = await cls.get_gen_table_by_id_service(auth, table_id) + if gen_table_info.id: try: # 确保options字段存在且为有效JSON if 'options' not in edit_gen_table or edit_gen_table['options'] is None: @@ -148,36 +157,42 @@ class GenTableService: except json.JSONDecodeError: edit_gen_table['options'] = '{}' - await gen_table_dao.update(id=page_object.table_id, data=edit_gen_table) - if page_object.columns: + await gen_table_dao.update(id=table_id, data=edit_gen_table) + if hasattr(page_object, 'columns') and page_object.columns: for gen_table_column in page_object.columns: - gen_table_column.update_by = page_object.update_by - gen_table_column.update_time = datetime.now() - if gen_table_column.column_id is not None: + # 为列添加更新信息 + gen_table_column_dict = gen_table_column.model_dump() + gen_table_column_dict['update_by'] = getattr(page_object, 'update_by', '') + gen_table_column_dict['update_time'] = datetime.now() + # 检查是否有id属性 + column_id = getattr(gen_table_column, 'id', None) + if column_id is not None: await gen_table_column_dao.update( - id=gen_table_column.column_id, - data=gen_table_column.model_dump(by_alias=True) + id=column_id, + data=gen_table_column_dict ) - await auth.db.commit() + if isinstance(db, AsyncSession): + await db.commit() return {"is_success": True, "message": "更新成功"} except Exception as e: - try: - await auth.db.rollback() - except: - pass # 忽略回滚错误 + if isinstance(db, AsyncSession): + try: + await db.rollback() + except: + pass # 忽略回滚错误 raise CustomException(msg=f'更新失败: {str(e)}') else: raise CustomException(msg='业务表不存在') @classmethod async def delete_gen_table_service(cls, auth: AuthSchema, page_object: GenTableDeleteSchema) -> SuccessResponse: - """ - 删除业务表信息service - - :param auth: 认证信息 - :param page_object: 删除业务表对象 - :return: 删除业务表校验结果 - """ + """删除业务表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) gen_table_column_dao = GenTableColumnCRUD(auth=auth) @@ -188,32 +203,34 @@ class GenTableService: await gen_table_dao.delete(ids=[int(table_id)]) # 删除相关的字段信息 # 这里需要先查询出所有相关的column_id,然后删除 - columns = await gen_table_column_dao.get_gen_table_column_list_by_table_id(auth.db, int(table_id)) + columns = await gen_table_column_dao.get_gen_table_column_list_by_table_id(db, int(table_id)) if columns: column_ids = [column.id for column in columns] await gen_table_column_dao.delete(ids=column_ids) - await auth.db.commit() + if isinstance(db, AsyncSession): + await db.commit() return SuccessResponse(msg='删除成功') except Exception as e: - try: - await auth.db.rollback() - except: - pass # 忽略回滚错误 + if isinstance(db, AsyncSession): + try: + await db.rollback() + except: + pass # 忽略回滚错误 raise CustomException(msg=f'删除失败: {str(e)}') else: raise CustomException(msg='传入业务表id为空') @classmethod async def get_gen_table_by_id_service(cls, auth: AuthSchema, table_id: int) -> GenTableOutSchema: - """ - 获取需要生成的业务表详细信息service - - :param auth: 认证信息 - :param table_id: 需要生成的业务表id - :return: 需要生成的业务表id对应的信息 - """ + """获取需要生成的业务表详细信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) - gen_table = await gen_table_dao.get_gen_table_by_id(auth.db, table_id) + gen_table = await gen_table_dao.get_gen_table_by_id(db, table_id) if gen_table: result = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table))) return result @@ -221,15 +238,16 @@ class GenTableService: raise CustomException(msg='业务表不存在') @classmethod - async def get_gen_table_all_service(cls, auth: AuthSchema) -> list[GenTableOutSchema]: - """ - 获取所有业务表信息service - - :param auth: 认证信息 - :return: 所有业务表信息列表 - """ + async def get_gen_table_all_service(cls, auth: AuthSchema) -> List[GenTableOutSchema]: + """获取所有业务表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) - gen_tables = await gen_table_dao.get_gen_table_all(auth.db) + gen_tables = await gen_table_dao.get_gen_table_all(db) result = [] for table in gen_tables: table_info = await cls.set_table_from_options(GenTableOutSchema(**CamelCaseUtil.transform_result(table))) @@ -237,38 +255,8 @@ class GenTableService: return result @classmethod - async def create_table_service(cls, auth: AuthSchema, sql: str, current_user: UserOutSchema) -> SuccessResponse: - """ - 创建表结构service - - :param auth: 认证信息 - :param sql: 建表语句 - :param current_user: 当前用户信息对象 - :return: 创建表结构结果 - """ - gen_table_dao = GenTableCRUD(auth=auth) - - try: - # 执行SQL语句创建表 - await gen_table_dao.create_table_by_sql_dao(auth.db, [sql]) - await auth.db.commit() - return SuccessResponse(msg='创建表结构成功') - except Exception as e: - try: - await auth.db.rollback() - except: - pass # 忽略回滚错误 - raise CustomException(msg=f'创建表结构失败: {str(e)}') - - @classmethod - async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> dict[Any, Any]: - """ - 预览代码service - - :param auth: 认证信息 - :param table_id: 业务表id - :return: 预览数据列表 - """ + async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> Dict[Any, Any]: + """预览代码""" gen_table = await cls.get_gen_table_by_id_service(auth, table_id) await cls.set_sub_table(auth, gen_table) await cls._set_pk_column(gen_table) @@ -286,13 +274,7 @@ class GenTableService: @classmethod async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse: - """ - 生成代码至指定路径service - - :param auth: 认证信息 - :param table_name: 业务表名称 - :return: 生成代码结果 - """ + """生成代码至指定路径""" env = TemplateInitializer.init_jinja2() render_info = await cls.__get_gen_render_info(auth, table_name) for template in render_info[0]: @@ -310,13 +292,7 @@ class GenTableService: @classmethod async def batch_gen_code_service(cls, auth: AuthSchema, table_names: List[str]) -> bytes: - """ - 批量生成代码service - - :param auth: 认证信息 - :param table_names: 业务表名称组 - :return: 下载代码结果 - """ + """批量生成代码""" zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for table_name in table_names: @@ -330,71 +306,24 @@ class GenTableService: zip_buffer.close() return zip_data - @classmethod - async def __get_gen_render_info(cls, auth: AuthSchema, table_name: str) -> list[Any]: - """ - 获取生成代码渲染模板相关信息 - - :param auth: 认证信息 - :param table_name: 业务表名称 - :return: 生成代码渲染模板相关信息 - """ - - gen_table_dao = GenTableCRUD(auth=auth) - gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name) - if gen_table: - gen_table_schema = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table)) - await cls.set_sub_table(auth, gen_table_schema) - await cls._set_pk_column(gen_table_schema) - context = TemplateUtils.prepare_context(gen_table_schema) - template_list = TemplateUtils.get_template_list( - gen_table_schema.tpl_category or "", - gen_table_schema.tpl_web_type or "" - ) - output_files = [TemplateUtils.get_file_name([template], gen_table_schema)[0] for template in template_list] - - return [template_list, output_files, context, gen_table_schema] - else: - raise CustomException(msg=f'业务表 {table_name} 不存在') - - @classmethod - def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]: - """ - 根据GenTableModel对象和模板名称生成路径 - - :param gen_table: GenTableModel对象 - :param template: 模板名称 - :return: 生成的路径 - """ - try: - gen_path = gen_table.gen_path or "" - if gen_path == '/': - file_name = TemplateUtils.get_file_name([template], gen_table)[0] - return os.path.join(os.getcwd(), GEN_PATH, file_name) - else: - file_name = TemplateUtils.get_file_name([template], gen_table)[0] - return os.path.join(gen_path, file_name) - except Exception: - return None - @classmethod async def sync_db_service(cls, auth: AuthSchema, table_name: str) -> SuccessResponse: - """ - 同步数据库service - - :param auth: 认证信息 - :param table_name: 业务表名称 - :return: 同步数据库结果 - """ + """同步数据库""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_dao = GenTableCRUD(auth=auth) gen_table_column_dao = GenTableColumnCRUD(auth=auth) - gen_table = await gen_table_dao.get_gen_table_by_name(auth.db, table_name) + gen_table = await gen_table_dao.get_gen_table_by_name(db, table_name) if gen_table: table = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table)) table_columns = table.columns or [] # 确保不为None table_column_map = {column.column_name: column for column in table_columns} - query_db_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(auth.db, table_name) + query_db_table_columns = await gen_table_column_dao.get_gen_db_table_columns_by_name(db, table_name) db_table_columns = [ GenTableColumnOutSchema(**column) for column in CamelCaseUtil.transform_result(query_db_table_columns) ] @@ -406,7 +335,11 @@ class GenTableService: GenUtils.init_column_field(column, table) if column.column_name in table_column_map: prev_column = table_column_map[column.column_name] - column.column_id = prev_column.column_id + # 使用getattr安全访问id属性 + column_id = getattr(prev_column, 'id', None) + if column_id is not None: + # 为column设置id属性 + column.id = column_id if getattr(column, 'list', False): # 使用getattr安全访问属性 column.dict_type = prev_column.dict_type column.query_type = prev_column.query_type @@ -418,50 +351,50 @@ class GenTableService: ): column.is_required = prev_column.is_required column.html_type = prev_column.html_type - if column.column_id is not None: - await gen_table_column_dao.update(id=column.column_id, data=column.model_dump(by_alias=True)) + # 使用getattr安全访问id属性 + column_id = getattr(column, 'id', None) + if column_id is not None: + await gen_table_column_dao.update(id=column_id, data=column.model_dump(by_alias=True)) else: await gen_table_column_dao.create(data=column.model_dump(by_alias=True)) del_columns = [column for column in table_columns if column.column_name not in db_table_column_names] if del_columns: for column in del_columns: - if column.column_id is not None: - await gen_table_column_dao.delete(ids=[column.column_id]) - await auth.db.commit() + # 使用getattr安全访问id属性 + column_id = getattr(column, 'id', None) + if column_id is not None: + await gen_table_column_dao.delete(ids=[column_id]) + if isinstance(db, AsyncSession): + await db.commit() return SuccessResponse(msg='同步成功') except Exception as e: - try: - await auth.db.rollback() - except: - pass # 忽略回滚错误 + if isinstance(db, AsyncSession): + try: + await db.rollback() + except: + pass # 忽略回滚错误 raise CustomException(msg=f'同步失败: {str(e)}') else: raise CustomException('业务表不存在') @classmethod async def set_sub_table(cls, auth: AuthSchema, gen_table: GenTableOutSchema) -> None: - """ - 设置主子表信息 - - :param auth: 认证信息 - :param gen_table: 业务表信息 - :return: - """ - + """设置主子表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') if gen_table.sub_table_name: gen_table_dao = GenTableCRUD(auth=auth) - sub_table = await gen_table_dao.get_gen_table_by_name(auth.db, gen_table.sub_table_name) + sub_table = await gen_table_dao.get_gen_table_by_name(db, gen_table.sub_table_name) if sub_table: gen_table.sub_table = GenTableOutSchema(**CamelCaseUtil.transform_result(sub_table)) @classmethod async def _set_pk_column(cls, gen_table: GenTableOutSchema) -> None: - """ - 设置主键列信息 - - :param gen_table: 业务表信息 - :return: - """ + """设置主键列信息""" if gen_table.columns: for column in gen_table.columns: if column.pk: @@ -480,12 +413,7 @@ class GenTableService: @classmethod async def set_table_from_options(cls, gen_table: GenTableOutSchema) -> GenTableOutSchema: - """ - 设置代码生成其他选项值 - - :param gen_table: 生成对象 - :return: 设置后的生成对象 - """ + """设置代码生成其他选项值""" params_obj = json.loads(gen_table.options) if gen_table.options else None if params_obj: gen_table.tree_code = params_obj.get(GenConstant.TREE_CODE) @@ -497,12 +425,8 @@ class GenTableService: return gen_table @classmethod - async def validate_edit(cls, edit_gen_table: GenTableUpdateSchema): - """ - 编辑保存参数校验 - - :param edit_gen_table: 编辑业务表对象 - """ + async def validate_edit(cls, edit_gen_table: GenTableUpdateSchema) -> None: + """编辑保存参数校验""" if edit_gen_table.tpl_category == GenConstant.TPL_TREE: # 从options字段获取参数,而不是params if not edit_gen_table.options: @@ -522,24 +446,61 @@ class GenTableService: elif not edit_gen_table.sub_table_fk_name: raise CustomException(msg='子表关联的外键名不能为空') + @classmethod + async def __get_gen_render_info(cls, auth: AuthSchema, table_name: str) -> List[Any]: + """获取生成代码渲染模板相关信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') + gen_table_dao = GenTableCRUD(auth=auth) + gen_table = await gen_table_dao.get_gen_table_by_name(db, table_name) + if gen_table: + gen_table_schema = GenTableOutSchema(**CamelCaseUtil.transform_result(gen_table)) + await cls.set_sub_table(auth, gen_table_schema) + await cls._set_pk_column(gen_table_schema) + context = TemplateUtils.prepare_context(gen_table_schema) + template_list = TemplateUtils.get_template_list( + gen_table_schema.tpl_category or "", + gen_table_schema.tpl_web_type or "" + ) + output_files = [TemplateUtils.get_file_name([template], gen_table_schema)[0] for template in template_list] + + return [template_list, output_files, context, gen_table_schema] + else: + raise CustomException(msg=f'业务表 {table_name} 不存在') + + @classmethod + def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]: + """根据GenTableModel对象和模板名称生成路径""" + try: + gen_path = gen_table.gen_path or "" + if gen_path == '/': + file_name = TemplateUtils.get_file_name([template], gen_table)[0] + return os.path.join(os.getcwd(), GEN_PATH, file_name) + else: + file_name = TemplateUtils.get_file_name([template], gen_table)[0] + return os.path.join(gen_path, file_name) + except Exception: + return None + class GenTableColumnService: - """ - 代码生成业务表字段服务层 - """ + """代码生成业务表字段服务层""" @classmethod async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[GenTableColumnOutSchema]: - """ - 获取业务表字段列表信息service - - :param auth: 认证信息 - :param table_id: 业务表格id - :return: 业务表字段列表信息对象 - """ + """获取业务表字段列表信息""" + if not auth.db: + raise CustomException(msg='数据库连接不存在') + # 确保db是AsyncSession类型 + db = auth.db + if not isinstance(db, AsyncSession): + raise CustomException(msg='数据库连接类型不正确') gen_table_column_dao = GenTableColumnCRUD(auth=auth) - gen_table_column_list_result = await gen_table_column_dao.get_gen_table_column_list_by_table_id(auth.db, table_id) - + gen_table_column_list_result = await gen_table_column_dao.get_gen_table_column_list_by_table_id(db, table_id) return [ GenTableColumnOutSchema(**gen_table_column) for gen_table_column in CamelCaseUtil.transform_result(gen_table_column_list_result) diff --git a/backend/app/api/v1/module_monitor/job/controller.py b/backend/app/api/v1/module_monitor/job/controller.py index 06e4ea72..390a0aa3 100644 --- a/backend/app/api/v1/module_monitor/job/controller.py +++ b/backend/app/api/v1/module_monitor/job/controller.py @@ -147,7 +147,8 @@ async def get_job_log_list_controller( search: JobLogQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"])) ) -> JSONResponse: - result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=page.order_by) + order_by = [{"create_time": "desc"}] + result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=order_by) result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size) logger.info(f"查询定时任务日志列表成功") return SuccessResponse(data=result_dict, msg="查询定时任务日志列表成功") diff --git a/backend/app/api/v1/module_monitor/job/crud.py b/backend/app/api/v1/module_monitor/job/crud.py index 3b263c35..3f18a7d9 100644 --- a/backend/app/api/v1/module_monitor/job/crud.py +++ b/backend/app/api/v1/module_monitor/job/crud.py @@ -8,7 +8,6 @@ from .model import JobModel, JobLogModel from .schema import JobCreateSchema,JobUpdateSchema,JobLogCreateSchema,JobLogUpdateSchema - class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]): """定时任务数据层""" @@ -21,7 +20,7 @@ class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]): """获取定时任务详情""" return await self.get(id=id) - async def get_obj_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[JobModel]: + async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[JobModel]: """获取定时任务列表""" return await self.list(search=search, order_by=order_by) @@ -58,14 +57,10 @@ class JobLogCRUD(CRUDBase[JobLogModel, JobLogCreateSchema, JobLogUpdateSchema]): """获取定时任务日志详情""" return await self.get(id=id) - async def get_obj_log_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[JobLogModel]: + async def get_obj_log_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[JobLogModel]: """获取定时任务日志列表""" return await self.list(search=search, order_by=order_by) - async def create_obj_log_crud(self, data: JobLogCreateSchema) -> Optional[JobLogModel]: - """创建定时任务日志""" - return await self.create(data=data) - async def delete_obj_log_crud(self, ids: List[int]) -> None: """删除定时任务日志""" return await self.delete(ids=ids) \ No newline at end of file diff --git a/backend/app/api/v1/module_monitor/job/model.py b/backend/app/api/v1/module_monitor/job/model.py index c7d0ec6c..24999039 100644 --- a/backend/app/api/v1/module_monitor/job/model.py +++ b/backend/app/api/v1/module_monitor/job/model.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- +from datetime import datetime from typing import Optional -from sqlalchemy import Boolean, String, Integer, Text, ForeignKey +from sqlalchemy import Boolean, String, Integer, Text, ForeignKey, DateTime from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.base_model import CreatorMixin, MappedBase @@ -48,6 +49,7 @@ class JobLogModel(MappedBase): job_message: Mapped[Optional[str]] = mapped_column(String(500),nullable=True,default='',comment='日志信息') exception_info: Mapped[Optional[str]] = mapped_column(String(2000),nullable=True,default='',comment='异常信息') job_id: Mapped[Optional[int]] = mapped_column(ForeignKey('monitor_job.id'), comment='任务ID') - + status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)") + create_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, default=datetime.now, comment='创建时间') # 任务关联关系 job: Mapped[Optional["JobModel"]] = relationship(back_populates="job_logs", lazy="selectin") diff --git a/backend/app/api/v1/module_monitor/job/param.py b/backend/app/api/v1/module_monitor/job/param.py index fa5dcc15..eea6c7fa 100644 --- a/backend/app/api/v1/module_monitor/job/param.py +++ b/backend/app/api/v1/module_monitor/job/param.py @@ -2,7 +2,6 @@ from typing import Optional from fastapi import Query -from datetime import datetime from app.core.validator import DateTimeStr @@ -15,10 +14,9 @@ class JobQueryParam: name: Optional[str] = Query(None, description="任务名称"), status: Optional[bool] = Query(None, description="状态: 启动,停止"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", f"%{name}%") if name else None @@ -29,9 +27,7 @@ class JobQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) class JobLogQueryParam: @@ -39,16 +35,15 @@ class JobLogQueryParam: def __init__( self, + job_name: Optional[str] = Query(None, description="任务名称"), status: Optional[bool] = Query(None, description="状态: 正常,失败"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() + # 模糊查询字段 + self.job_name = ("like", job_name) # 精确查询字段 self.status = status - # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) \ No newline at end of file + self.create_time = ("between", (start_time, end_time)) \ No newline at end of file diff --git a/backend/app/api/v1/module_monitor/job/schema.py b/backend/app/api/v1/module_monitor/job/schema.py index 2d9fc471..28c09866 100644 --- a/backend/app/api/v1/module_monitor/job/schema.py +++ b/backend/app/api/v1/module_monitor/job/schema.py @@ -52,8 +52,8 @@ class JobLogCreateSchema(BaseModel): job_kwargs: Optional[str] = Field(default=None, description='关键字参数') job_trigger: Optional[str] = Field(default=None, description='任务触发器') job_message: Optional[str] = Field(default=None, description='日志信息') - status: Optional[bool] = Field(default=None, description='任务状态:正常,失败') exception_info: Optional[str] = Field(default=None, description='异常信息') + status: Optional[bool] = Field(default=False, description='任务状态:正常,失败') create_time: Optional[DateTimeStr] = Field(default=None, description='创建时间') diff --git a/backend/app/api/v1/module_monitor/job/service.py b/backend/app/api/v1/module_monitor/job/service.py index 812bb981..a15be98c 100644 --- a/backend/app/api/v1/module_monitor/job/service.py +++ b/backend/app/api/v1/module_monitor/job/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, List, Dict +from typing import Any, List, Dict, Optional from app.core.ap_scheduler import SchedulerUtil from app.core.exceptions import CustomException @@ -23,9 +23,7 @@ class JobService: return JobOutSchema.model_validate(obj).model_dump() @classmethod - async def get_job_list_service(cls, auth: AuthSchema, search: JobQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: - if order_by: - order_by = eval(order_by) + async def get_job_list_service(cls, auth: AuthSchema, search: Optional[JobQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: obj_list = await JobCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by) return [JobOutSchema.model_validate(obj).model_dump() for obj in obj_list] @@ -34,7 +32,7 @@ class JobService: exist_obj = await JobCRUD(auth).get(name=data.name) if exist_obj: raise CustomException(msg='创建失败,该定时任务已存在') - if data.trigger == 'cron' and not CronUtil.validate_cron_expression(data.trigger_args): + if data.trigger == 'cron' and data.trigger_args and not CronUtil.validate_cron_expression(data.trigger_args): raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确') obj = await JobCRUD(auth).create_obj_crud(data=data) @@ -46,9 +44,11 @@ class JobService: exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=id) if not exist_obj: raise CustomException(msg='更新失败,该定时任务不存在') - if data.trigger == 'cron' and not CronUtil.validate_cron_expression(data.trigger_args): + if data.trigger == 'cron' and data.trigger_args and not CronUtil.validate_cron_expression(data.trigger_args): raise CustomException(msg=f'新增定时任务{data.name}失败, Cron表达式不正确') obj = await JobCRUD(auth).update_obj_crud(id=id, data=data) + if not obj: + raise CustomException(msg='更新失败,该数据定时任务不存在') SchedulerUtil().modify_job(job_id=obj.id) return JobOutSchema.model_validate(obj).model_dump() @@ -130,12 +130,8 @@ class JobLogService: return JobLogOutSchema.model_validate(obj).model_dump() @classmethod - async def get_job_log_list_service(cls, auth: AuthSchema, search: JobLogQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: + async def get_job_log_list_service(cls, auth: AuthSchema, search: Optional[JobLogQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: """获取定时任务日志列表""" - if order_by: - order_by = eval(order_by) - else: - order_by = [{"created_at": "desc"}] obj_list = await JobLogCRUD(auth).get_obj_log_list_crud(search=search.__dict__, order_by=order_by) return [JobLogOutSchema.model_validate(obj).model_dump() for obj in obj_list] @@ -172,9 +168,9 @@ class JobLogService: 'job_kwargs': '关键字参数', 'job_trigger': '任务触发器', 'job_message': '日志信息', - 'status': '执行状态', 'exception_info': '异常信息', - 'created_at': '创建时间', + 'status': '执行状态', + 'create_time': '创建时间', } # 复制数据并转换状态 diff --git a/backend/app/api/v1/module_monitor/online/param.py b/backend/app/api/v1/module_monitor/online/param.py index 4ece0f53..e3720765 100644 --- a/backend/app/api/v1/module_monitor/online/param.py +++ b/backend/app/api/v1/module_monitor/online/param.py @@ -13,7 +13,6 @@ class OnlineQueryParam: ipaddr: Optional[str] = Query(None, description="登陆IP地址"), login_location: Optional[str] = Query(None, description="登录所属地"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", f"%{name}%") if name else None diff --git a/backend/app/api/v1/module_monitor/online/service.py b/backend/app/api/v1/module_monitor/online/service.py index bc79db53..3c8a1f5d 100644 --- a/backend/app/api/v1/module_monitor/online/service.py +++ b/backend/app/api/v1/module_monitor/online/service.py @@ -9,7 +9,7 @@ from app.core.redis_crud import RedisCURD from app.core.security import decode_access_token from app.core.logger import logger from .param import OnlineQueryParam -from .schema import OnlineOutSchema + class OnlineService: """在线用户管理模块服务层""" diff --git a/backend/app/api/v1/module_monitor/resource/param.py b/backend/app/api/v1/module_monitor/resource/param.py index 3cbea93e..84fbd8e0 100644 --- a/backend/app/api/v1/module_monitor/resource/param.py +++ b/backend/app/api/v1/module_monitor/resource/param.py @@ -11,7 +11,6 @@ class ResourceSearchQueryParam: name: Optional[str] = Query(None, description="搜索关键词"), path: Optional[str] = Query(None, description="目录路径"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) if name else None diff --git a/backend/app/api/v1/module_monitor/server/service.py b/backend/app/api/v1/module_monitor/server/service.py index e5fc253e..b5595a7f 100644 --- a/backend/app/api/v1/module_monitor/server/service.py +++ b/backend/app/api/v1/module_monitor/server/service.py @@ -25,10 +25,10 @@ class ServerService: async def get_server_monitor_info_service(cls) -> Dict: """获取服务器监控信息""" return ServerMonitorSchema( - cpu=cls._get_cpu_info().model_dump(), - mem=cls._get_memory_info().model_dump(), - sys=cls._get_system_info().model_dump(), - py=cls._get_python_info().model_dump(), + cpu=cls._get_cpu_info(), + mem=cls._get_memory_info(), + sys=cls._get_system_info(), + py=cls._get_python_info(), disks=cls._get_disk_info() ).model_dump() @@ -36,8 +36,11 @@ class ServerService: def _get_cpu_info(cls) -> CpuInfoSchema: """获取CPU信息""" cpu_times = psutil.cpu_times_percent() + cpu_num=psutil.cpu_count(logical=True) + if not cpu_num: + cpu_num = 1 return CpuInfoSchema( - cpu_num=psutil.cpu_count(logical=True), + cpu_num=cpu_num, used=cpu_times.user, sys=cpu_times.system, free=cpu_times.idle @@ -89,7 +92,7 @@ class ServerService: ) @classmethod - def _get_disk_info(cls) -> List[Dict]: + def _get_disk_info(cls) -> List[DiskInfoSchema]: """获取磁盘信息""" disk_info = [] for partition in psutil.disk_partitions(): @@ -106,7 +109,7 @@ class ServerService: used=bytes2human(usage.used), free=bytes2human(usage.free), usage=usage.percent # 直接使用数字而不是字符串 - ).model_dump() + ) ) except (PermissionError, FileNotFoundError): # 明确指定可能的异常 diff --git a/backend/app/api/v1/module_system/auth/controller.py b/backend/app/api/v1/module_system/auth/controller.py index b845015f..55bb200a 100644 --- a/backend/app/api/v1/module_system/auth/controller.py +++ b/backend/app/api/v1/module_system/auth/controller.py @@ -2,8 +2,8 @@ import time from typing import Union, Dict -from fastapi import APIRouter, Depends, Request, BackgroundTasks, WebSocket -from fastapi.responses import JSONResponse, StreamingResponse +from fastapi import APIRouter, Depends, Request +from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from redis.asyncio.client import Redis @@ -82,33 +82,3 @@ async def logout_controller( logger.info('退出成功') return SuccessResponse(msg='退出成功') return ErrorResponse(msg='退出失败') - - -# 以下接口为预留,后期会用 -@AuthRouter.post("/background_tasks", summary="模拟fastapi自带后台任务-模拟流式响应") -async def stream_response__controller( - background_tasks: BackgroundTasks, - message: str -): - def task(message): - for i in range(5): - yield f"睡眠 {message} {i}\n" - time.sleep(1) - background_tasks.add_task(task, message) - - return StreamingResponse( - data=task(message), - headers={"X-Custom-Header": "Streaming-Response"}, - media_type="text/plain", - ) - - -@AuthRouter.websocket("/ws", name="websocket") -async def websocket_endpoint_controller( - websocket: WebSocket -): - # ws://127.0.0.1:8000/ws - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Message text was: {data}") diff --git a/backend/app/api/v1/module_system/auth/schema.py b/backend/app/api/v1/module_system/auth/schema.py index ceccde0f..3483c283 100644 --- a/backend/app/api/v1/module_system/auth/schema.py +++ b/backend/app/api/v1/module_system/auth/schema.py @@ -15,7 +15,7 @@ class AuthSchema(BaseModel): user: Optional[UserOutSchema] = Field(default=None, description='用户信息') check_data_scope: bool = Field(default=True, description='是否检查数据权限') - db: AsyncSession | Session | None = Field(default=None, description='数据库会话') + db: AsyncSession = Field(description='数据库会话') class JWTPayloadSchema(BaseModel): diff --git a/backend/app/api/v1/module_system/auth/service.py b/backend/app/api/v1/module_system/auth/service.py index 185b0160..b2e72dcc 100644 --- a/backend/app/api/v1/module_system/auth/service.py +++ b/backend/app/api/v1/module_system/auth/service.py @@ -64,6 +64,8 @@ class LoginService: # 验证码校验 if settings.CAPTCHA_ENABLE and not request_from_docs: + if not login_form.captcha_key or not login_form.captcha: + raise CustomException(msg="验证码不能为空") await CaptchaService.check_captcha_service(redis=redis, key=login_form.captcha_key, captcha=login_form.captcha) # 用户认证 @@ -81,6 +83,10 @@ class LoginService: # 更新最后登录时间 user = await UserCRUD(auth).update_last_login_crud(id=user.id) + if not user: + raise CustomException(msg="用户不存在") + if not login_form.login_type: + raise CustomException(msg="登录类型不能为空") # 创建token token = await cls.create_token_service(request=request, redis=redis, user=user, login_type=login_form.login_type) @@ -111,7 +117,11 @@ class LoginService: request_ip = x_forwarded_for.split(',')[0].strip() else: # 若没有 X-Forwarded-For 头,则使用 request.client.host - request_ip = request.client.host + if request.client: + request_ip = request.client.host + else: + request_ip = "127.0.0.1" + login_location = await IpLocalUtil.get_ip_location(request_ip) request.scope["login_location"] = login_location @@ -131,7 +141,7 @@ class LoginService: login_location=login_location, os=user_agent.os.family, browser = user_agent.browser.family, - login_time=user.last_login.isoformat() if isinstance(user.last_login, datetime) else str(user.last_login), + login_time=user.last_login, login_type=login_type ).model_dump_json() @@ -162,7 +172,7 @@ class LoginService: return JWTOutSchema( access_token=access_token, refresh_token=refresh_token, - expires_in=access_expires.total_seconds(), + expires_in=int(access_expires.total_seconds()), token_type=settings.TOKEN_TYPE ) @@ -231,7 +241,7 @@ class LoginService: return JWTOutSchema( access_token=access_token, refresh_token=refresh_token_new, - expires_in=access_expires.total_seconds(), + expires_in=int(access_expires.total_seconds()), token_type=settings.TOKEN_TYPE ) diff --git a/backend/app/api/v1/module_system/dept/controller.py b/backend/app/api/v1/module_system/dept/controller.py index b89cd592..420ba8e3 100644 --- a/backend/app/api/v1/module_system/dept/controller.py +++ b/backend/app/api/v1/module_system/dept/controller.py @@ -1,15 +1,13 @@ # -*- coding: utf-8 -*- -from fastapi import APIRouter, Body, Depends, Path, Query +from fastapi import APIRouter, Body, Depends, Path from fastapi.responses import JSONResponse from app.common.response import SuccessResponse -from app.common.request import PaginationService from app.core.router_class import OperationLogRoute from app.core.dependencies import AuthPermission from app.core.base_schema import BatchSetAvailable from app.core.logger import logger -from app.core.base_params import PaginationQueryParam from ..auth.schema import AuthSchema from .param import DeptQueryParam from .service import DeptService @@ -27,7 +25,8 @@ async def get_dept_tree_controller( search: DeptQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["system:dept:query"])) ) -> JSONResponse: - result_dict_list = await DeptService.get_dept_tree_service(search=search, auth=auth) + order_by = [{"order": "asc"}] + result_dict_list = await DeptService.get_dept_tree_service(search=search, auth=auth, order_by=order_by) logger.info(f"查询部门树成功") return SuccessResponse(data=result_dict_list, msg="查询部门树成功") diff --git a/backend/app/api/v1/module_system/dept/crud.py b/backend/app/api/v1/module_system/dept/crud.py index dde282fd..720ae73f 100644 --- a/backend/app/api/v1/module_system/dept/crud.py +++ b/backend/app/api/v1/module_system/dept/crud.py @@ -33,7 +33,7 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]): obj.parent_name = parent.name return obj - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DeptModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DeptModel]: """ 获取部门列表 @@ -51,7 +51,7 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]): obj.parent_name = parent_map.get(obj.parent_id) return obj_list - async def get_tree_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DeptModel]: + async def get_tree_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DeptModel]: """ 获取部门树形列表 @@ -59,7 +59,7 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]): :param order_by: 排序字段 :return: 部门树形列表 """ - return await self.get_tree_list(search=search, order_by=order_by, children_attr='children') + return await self.tree_list(search=search, order_by=order_by, children_attr='children') async def set_available_crud(self, ids: List[int], status: bool) -> None: """ diff --git a/backend/app/api/v1/module_system/dept/param.py b/backend/app/api/v1/module_system/dept/param.py index 4eaf10a3..bf0715ee 100644 --- a/backend/app/api/v1/module_system/dept/param.py +++ b/backend/app/api/v1/module_system/dept/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -13,11 +12,10 @@ class DeptQueryParam: self, name: Optional[str] = Query(None, description="部门名称"), status: Optional[bool] = Query(None, description="部门状态(True正常 False停用)"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) @@ -27,8 +25,6 @@ class DeptQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/dept/service.py b/backend/app/api/v1/module_system/dept/service.py index 9db52fac..9aa8c29b 100644 --- a/backend/app/api/v1/module_system/dept/service.py +++ b/backend/app/api/v1/module_system/dept/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import List, Dict +from typing import List, Dict, Optional from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException @@ -39,7 +39,7 @@ class DeptService: return DeptOutSchema.model_validate(dept).model_dump() @classmethod - async def get_dept_tree_service(cls, auth: AuthSchema, search: DeptQueryParam, order_by: List[Dict] = None) -> List[Dict]: + async def get_dept_tree_service(cls, auth: AuthSchema, search: Optional[DeptQueryParam]= None, order_by: Optional[List[Dict]] = None) -> List[Dict]: """ 获取部门树形列表service @@ -48,10 +48,6 @@ class DeptService: :param order_by: 排序参数 :return: 部门树形列表对象 """ - if order_by: - order_by = eval(order_by) - else: - order_by = [{"order": "asc"}] # 使用树形结构查询,预加载children关系 dept_list = await DeptCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by) # 转换为字典列表 diff --git a/backend/app/api/v1/module_system/dict/controller.py b/backend/app/api/v1/module_system/dict/controller.py index 348174c4..7f99fd74 100644 --- a/backend/app/api/v1/module_system/dict/controller.py +++ b/backend/app/api/v1/module_system/dict/controller.py @@ -85,8 +85,8 @@ async def delete_type_controller( logger.info(f"删除字典类型成功: {ids}") return SuccessResponse(msg="删除字典类型成功") -@DictRouter.patch("/type/available/setting", summary="批量修改公告状态", description="批量修改公告状态") -async def batch_set_available_obj_controller( +@DictRouter.patch("/type/available/setting", summary="批量修改字典类型状态", description="批量修改字典类型状态") +async def batch_set_available_dict_type_controller( data: BatchSetAvailable, auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_type:patch"])) ) -> JSONResponse: @@ -164,7 +164,7 @@ async def delete_data_controller( return SuccessResponse(msg="删除字典数据成功") @DictRouter.patch("/data/available/setting", summary="批量修改字典数据状态", description="批量修改字典数据状态") -async def batch_set_available_obj_controller( +async def batch_set_available_dict_data_controller( data: BatchSetAvailable, auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_data:patch"])) ) -> JSONResponse: @@ -202,4 +202,10 @@ async def get_init_dict_data_controller( ) logger.info(f"获取初始化字典数据成功:{dict_data_query_result}") + # 确保数据是字符串类型再进行 JSON 解析 + if isinstance(dict_data_query_result, bytes): + dict_data_query_result = dict_data_query_result.decode('utf-8') + elif not isinstance(dict_data_query_result, str): + dict_data_query_result = str(dict_data_query_result) + return SuccessResponse(data=json.loads(dict_data_query_result), msg="获取初始化字典数据成功") diff --git a/backend/app/api/v1/module_system/dict/crud.py b/backend/app/api/v1/module_system/dict/crud.py index c5f0e268..e92c0ddd 100644 --- a/backend/app/api/v1/module_system/dict/crud.py +++ b/backend/app/api/v1/module_system/dict/crud.py @@ -20,7 +20,7 @@ class DictTypeCRUD(CRUDBase[DictTypeModel, DictTypeCreateSchema, DictTypeUpdateS """获取数据字典类型详情""" return await self.get(id=id) - async def get_obj_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DictTypeModel]: + async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DictTypeModel]: """获取数据字典类型列表""" return await self.list(search=search, order_by=order_by) @@ -53,7 +53,7 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS """获取数据字典数据详情""" return await self.get(id=id) - async def get_obj_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[DictDataModel]: + async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DictDataModel]: """获取数据字典数据列表""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_system/dict/param.py b/backend/app/api/v1/module_system/dict/param.py index 829b574d..b3602de8 100644 --- a/backend/app/api/v1/module_system/dict/param.py +++ b/backend/app/api/v1/module_system/dict/param.py @@ -2,7 +2,6 @@ from typing import Optional from fastapi import Query -from datetime import datetime from app.core.validator import DateTimeStr @@ -16,8 +15,8 @@ class DictTypeQueryParam: dict_type: Optional[str] = Query(None, description="字典类型"), status: Optional[bool] = Query(None, description="状态(1正常 0停用)"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: super().__init__() @@ -31,9 +30,7 @@ class DictTypeQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) class DictDataQueryParam: @@ -45,10 +42,9 @@ class DictDataQueryParam: dict_type: Optional[str] = Query(None, description="字典类型"), status: Optional[bool] = Query(None, description="状态(1正常 0停用)"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.dict_label = ("like", f"%{dict_label}%") if dict_label else None @@ -60,6 +56,4 @@ class DictDataQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) \ No newline at end of file + self.created_at = ("between", (start_time, end_time)) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/dict/service.py b/backend/app/api/v1/module_system/dict/service.py index b9c5245d..a3cc8c33 100644 --- a/backend/app/api/v1/module_system/dict/service.py +++ b/backend/app/api/v1/module_system/dict/service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import json -from typing import Any, List, Dict +from typing import Any, List, Dict, Optional from redis.asyncio.client import Redis from sqlalchemy.ext.asyncio import AsyncSession @@ -29,14 +29,8 @@ class DictTypeService: return DictTypeOutSchema.model_validate(obj).model_dump() @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: DictTypeQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: - if order_by: - order_by = eval(order_by) - obj_list = None - if search: - obj_list = await DictTypeCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by) - else: - obj_list = await DictTypeCRUD(auth).get_obj_list_crud() + async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[DictTypeQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + obj_list = await DictTypeCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by) return [DictTypeOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod @@ -80,7 +74,6 @@ class DictTypeService: for item in exist_obj_type_list: item.dict_type = data.dict_type dict_data = DictDataUpdateSchema( - id=item.id, dict_sort=item.dict_sort, dict_label=item.dict_label, dict_value=item.dict_value, @@ -179,9 +172,7 @@ class DictDataService: return DictDataOutSchema.model_validate(obj).model_dump() @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: DictDataQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: - if order_by: - order_by = eval(order_by) + async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[DictDataQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: obj_list = await DictDataCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by) return [DictDataOutSchema.model_validate(obj).model_dump() for obj in obj_list] @@ -257,15 +248,17 @@ class DictDataService: if not exist_obj: raise CustomException(msg='更新失败,该字典数据不存在') exist_obj = await DictDataCRUD(auth).get(dict_label=data.dict_label) - if exist_obj and exist_obj.id != id: + if not exist_obj: + raise CustomException(msg='更新失败,该字典数据不存在') + + if exist_obj.id != id: raise CustomException(msg='更新失败,数据字典数据重复') # 如果状态变更,需要同步更新字典类型状态并刷新缓存 - if exist_obj.status != data.status: + if exist_obj.status != data.status or exist_obj.dict_type != data.dict_type: dict_type = await DictTypeCRUD(auth).get(dict_type=exist_obj.dict_type) if dict_type: update_data = DictTypeUpdateSchema( - id=dict_type.id, dict_name=dict_type.dict_name, dict_type=dict_type.dict_type, status=data.status, diff --git a/backend/app/api/v1/module_system/log/controller.py b/backend/app/api/v1/module_system/log/controller.py index ec3cc857..9bbce836 100644 --- a/backend/app/api/v1/module_system/log/controller.py +++ b/backend/app/api/v1/module_system/log/controller.py @@ -25,7 +25,10 @@ async def get_obj_list_controller( auth: AuthSchema = Depends(AuthPermission(permissions=["system:log:query"])) ) -> JSONResponse: """ 查询日志 """ - result_dict_list = await OperationLogService.get_log_list_service(search=search, auth=auth, order_by=page.order_by) + order_by = [{"created_at": "desc"}] + if page.order_by: + order_by = page.order_by + result_dict_list = await OperationLogService.get_log_list_service(search=search, auth=auth, order_by=order_by) result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size) logger.info(f"查询日志成功") return SuccessResponse(data=result_dict, msg="查询日志成功") diff --git a/backend/app/api/v1/module_system/log/crud.py b/backend/app/api/v1/module_system/log/crud.py index ff22bb1f..f88d3c7b 100644 --- a/backend/app/api/v1/module_system/log/crud.py +++ b/backend/app/api/v1/module_system/log/crud.py @@ -34,7 +34,7 @@ class OperationLogCRUD(CRUDBase[OperationLogModel, OperationLogCreateSchema, Non """ return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[OperationLogModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[OperationLogModel]: """ 获取操作日志列表 diff --git a/backend/app/api/v1/module_system/log/param.py b/backend/app/api/v1/module_system/log/param.py index 2f405e75..db0dd60e 100644 --- a/backend/app/api/v1/module_system/log/param.py +++ b/backend/app/api/v1/module_system/log/param.py @@ -2,7 +2,6 @@ from typing import Optional from fastapi import Query -from datetime import datetime from app.core.validator import DateTimeStr @@ -17,10 +16,9 @@ class OperationLogQueryParam: request_ip: Optional[str] = Query(None, description="请求IP"), response_code: Optional[int] = Query(None, description="响应状态码"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.request_path = ("like", f"%{request_path}%") if request_path else None @@ -34,6 +32,4 @@ class OperationLogQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/log/service.py b/backend/app/api/v1/module_system/log/service.py index 78d7330a..ab9349ef 100644 --- a/backend/app/api/v1/module_system/log/service.py +++ b/backend/app/api/v1/module_system/log/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from app.core.exceptions import CustomException from app.utils.excel_util import ExcelUtil @@ -26,12 +26,8 @@ class OperationLogService: return log_dict @classmethod - async def get_log_list_service(cls, auth: AuthSchema, search: OperationLogQueryParam, order_by: List[Dict] = None) -> List[Dict]: - """获取日志列表""" - if order_by: - order_by = eval(order_by) - else: - order_by = [{"created_at": "desc"}] + async def get_log_list_service(cls, auth: AuthSchema, search: Optional[OperationLogQueryParam], order_by: Optional[List[Dict]] = None) -> List[Dict]: + """获取日志列表""" log_list = await OperationLogCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) log_dict_list = [OperationLogOutSchema.model_validate(log).model_dump() for log in log_list] return log_dict_list diff --git a/backend/app/api/v1/module_system/menu/controller.py b/backend/app/api/v1/module_system/menu/controller.py index 3d628432..da4858ce 100644 --- a/backend/app/api/v1/module_system/menu/controller.py +++ b/backend/app/api/v1/module_system/menu/controller.py @@ -24,7 +24,8 @@ async def get_menu_tree_controller( search: MenuQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["system:menu:query"])) ) -> JSONResponse: - result_dict_list = await MenuService.get_menu_tree_service(search=search, auth=auth) + order_by = [{"order": "asc"}] + result_dict_list = await MenuService.get_menu_tree_service(search=search, auth=auth, order_by=order_by) logger.info(f"查询菜单树成功") return SuccessResponse(data=result_dict_list, msg="查询菜单树成功") diff --git a/backend/app/api/v1/module_system/menu/crud.py b/backend/app/api/v1/module_system/menu/crud.py index ba1816b7..7af763f1 100644 --- a/backend/app/api/v1/module_system/menu/crud.py +++ b/backend/app/api/v1/module_system/menu/crud.py @@ -33,7 +33,7 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]): obj.parent_name = parent.name return obj - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[MenuModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[MenuModel]: """ 获取菜单列表 @@ -51,7 +51,7 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]): obj.parent_name = parent_map.get(obj.parent_id) return obj_list - async def get_tree_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[MenuModel]: + async def get_tree_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[MenuModel]: """ 获取菜单树形列表 @@ -59,7 +59,7 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]): :param order_by: 排序字段 :return: 菜单树形列表 """ - return await self.get_tree_list(search=search, order_by=order_by, children_attr='children') + return await self.tree_list(search=search, order_by=order_by, children_attr='children') async def set_available_crud(self, ids: List[int], status: bool) -> None: """ diff --git a/backend/app/api/v1/module_system/menu/model.py b/backend/app/api/v1/module_system/menu/model.py index a7b1f30c..4b495459 100644 --- a/backend/app/api/v1/module_system/menu/model.py +++ b/backend/app/api/v1/module_system/menu/model.py @@ -39,7 +39,7 @@ class MenuModel(ModelMixin): keep_alive: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False, comment='是否缓存(True:是 False:否)') always_show: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, comment='是否始终显示(True:是 False:否)') title: Mapped[Optional[str]] = mapped_column(String(50), comment='菜单标题') - params: Mapped[Optional[dict]] = mapped_column(JSON, comment='路由参数(JSON对象)') + params: Mapped[Optional[list[dict[str, str]]]] = mapped_column(JSON, comment='路由参数(JSON对象)') affix: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, comment='是否固定标签页(True:是 False:否)') parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('system_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID') diff --git a/backend/app/api/v1/module_system/menu/param.py b/backend/app/api/v1/module_system/menu/param.py index fad94ab2..7e6259c4 100644 --- a/backend/app/api/v1/module_system/menu/param.py +++ b/backend/app/api/v1/module_system/menu/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional, Literal from fastapi import Query @@ -17,10 +16,9 @@ class MenuQueryParam: type: Optional[Literal['M', 'C', 'F']] = Query(None, description="菜单类型(M目录 C菜单 F按钮)"), permission: Optional[str] = Query(None, description="权限标识"), status: Optional[bool] = Query(None, description="菜单状态(True正常 False停用)"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) @@ -34,7 +32,5 @@ class MenuQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/menu/service.py b/backend/app/api/v1/module_system/menu/service.py index e7614bbe..4a88ef94 100644 --- a/backend/app/api/v1/module_system/menu/service.py +++ b/backend/app/api/v1/module_system/menu/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import List, Dict +from typing import List, Dict, Optional from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException @@ -33,7 +33,7 @@ class MenuService: return menu_dict @classmethod - async def get_menu_tree_service(cls, auth: AuthSchema, search: MenuQueryParam, order_by: List[Dict] = None) -> List[Dict]: + async def get_menu_tree_service(cls, auth: AuthSchema, search: Optional[MenuQueryParam] = None, order_by: Optional[List[Dict]] = None) -> List[Dict]: """ 获取菜单树形列表service @@ -42,10 +42,6 @@ class MenuService: :param order_by: 排序参数 :return: 菜单树形列表对象 """ - if order_by: - order_by = eval(order_by) - else: - order_by = [{"order": "asc"}] # 使用树形结构查询,预加载children关系 menu_list = await MenuCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by) # 转换为字典列表 @@ -74,6 +70,8 @@ class MenuService: if data.parent_id: parent_menu = await MenuCRUD(auth).get_by_id_crud(id=data.parent_id) + if not parent_menu: + raise CustomException(msg='更新失败,父级菜单不存在') data.parent_name = parent_menu.name new_menu = await MenuCRUD(auth).update(id=id, data=data) diff --git a/backend/app/api/v1/module_system/notice/crud.py b/backend/app/api/v1/module_system/notice/crud.py index 4e07ab7b..aad22923 100644 --- a/backend/app/api/v1/module_system/notice/crud.py +++ b/backend/app/api/v1/module_system/notice/crud.py @@ -20,7 +20,7 @@ class NoticeCRUD(CRUDBase[NoticeModel, NoticeCreateSchema, NoticeUpdateSchema]): """获取公告详情""" return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[NoticeModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[NoticeModel]: """获取公告列表""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_system/notice/param.py b/backend/app/api/v1/module_system/notice/param.py index afbcda09..9e1c5618 100644 --- a/backend/app/api/v1/module_system/notice/param.py +++ b/backend/app/api/v1/module_system/notice/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -16,10 +15,9 @@ class NoticeQueryParam: notice_type: Optional[str] = Query(None, description="公告类型"), status: Optional[bool] = Query(None, description="是否可用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.notice_title = ("like", notice_title) @@ -31,8 +29,6 @@ class NoticeQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/notice/service.py b/backend/app/api/v1/module_system/notice/service.py index 95a9f1f2..036e078f 100644 --- a/backend/app/api/v1/module_system/notice/service.py +++ b/backend/app/api/v1/module_system/notice/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, List, Dict +from typing import Any, List, Dict, Optional from app.core.base_schema import BatchSetAvailable @@ -28,9 +28,7 @@ class NoticeService: return [NoticeOutSchema.model_validate(notice_obj).model_dump() for notice_obj in notice_obj_list] @classmethod - async def get_notice_list_service(cls, auth: AuthSchema, search: NoticeQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: - if order_by: - order_by = eval(order_by) + async def get_notice_list_service(cls, auth: AuthSchema, search: Optional[NoticeQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: notice_obj_list = await NoticeCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) return [NoticeOutSchema.model_validate(notice_obj).model_dump() for notice_obj in notice_obj_list] diff --git a/backend/app/api/v1/module_system/params/crud.py b/backend/app/api/v1/module_system/params/crud.py index 0a24df13..b68f9d5c 100644 --- a/backend/app/api/v1/module_system/params/crud.py +++ b/backend/app/api/v1/module_system/params/crud.py @@ -24,7 +24,7 @@ class ParamsCRUD(CRUDBase[ParamsModel, ParamsCreateSchema, ParamsUpdateSchema]): """根据key获取配置管理型详情""" return await self.get(config_key=key) - async def get_obj_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ParamsModel]: + async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[ParamsModel]: """获取配置管理型列表""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_system/params/param.py b/backend/app/api/v1/module_system/params/param.py index 74ea9959..a936c361 100644 --- a/backend/app/api/v1/module_system/params/param.py +++ b/backend/app/api/v1/module_system/params/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -14,11 +13,10 @@ class ParamsQueryParam: config_name: Optional[str] = Query(None, description="配置名称"), config_key: Optional[str] = Query(None, description="配置键名"), config_type: Optional[bool] = Query(None, description="系统内置((True:是 False:否))"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() - + # 模糊查询字段 self.config_name = ("like", config_name) self.config_key = ("like", config_key) @@ -28,8 +26,6 @@ class ParamsQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/params/service.py b/backend/app/api/v1/module_system/params/service.py index 745739d8..19709493 100644 --- a/backend/app/api/v1/module_system/params/service.py +++ b/backend/app/api/v1/module_system/params/service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import json -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from redis.asyncio.client import Redis from fastapi import UploadFile @@ -39,7 +39,7 @@ class ParamsService: return ParamsOutSchema.model_validate(obj).model_dump() @classmethod - async def get_config_value_by_key_service(cls, auth: AuthSchema, config_key: str) -> str: + async def get_config_value_by_key_service(cls, auth: AuthSchema, config_key: str) -> str | None: """根据配置键获取配置值""" obj = await ParamsCRUD(auth).get_obj_by_key_crud(key=config_key) if not obj: @@ -47,9 +47,7 @@ class ParamsService: return obj.config_value @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: ParamsQueryParam = None, order_by: List[Dict[str, str]] = None) -> List[Dict]: - if order_by: - order_by = eval(order_by) + async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[ParamsQueryParam] = None, order_by: Optional[List[Dict[str, str]]]= None) -> List[Dict]: obj_list = None if search: obj_list = await ParamsCRUD(auth).get_obj_list_crud(search=search.__dict__, order_by=order_by) @@ -91,6 +89,8 @@ class ParamsService: raise CustomException(msg='更新失败,系统配置key不允许修改') new_obj = await ParamsCRUD(auth).update_obj_crud(id=id, data=data) + if not new_obj: + raise CustomException(msg='更新失败,系统配置不存在') new_obj_dict = ParamsOutSchema.model_validate(new_obj).model_dump() # 同步redis @@ -171,7 +171,7 @@ class ParamsService: ).model_dump() @classmethod - async def init_config_service(cls, redis: Redis) -> bool: + async def init_config_service(cls, redis: Redis) -> None: async with AsyncSessionLocal() as session: async with session.begin(): auth = AuthSchema(db=session) diff --git a/backend/app/api/v1/module_system/position/controller.py b/backend/app/api/v1/module_system/position/controller.py index 52e25eea..b8d63c4c 100644 --- a/backend/app/api/v1/module_system/position/controller.py +++ b/backend/app/api/v1/module_system/position/controller.py @@ -29,7 +29,10 @@ async def get_obj_list_controller( search: PositionQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["system:position:query"])), ) -> JSONResponse: - result_dict_list = await PositionService.get_position_list_service(search=search, auth=auth, order_by=page.order_by) + order_by = [{"order": "asc"}] + if page.order_by: + order_by = page.order_by + result_dict_list = await PositionService.get_position_list_service(search=search, auth=auth, order_by=order_by) result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size) logger.info(f"查询岗位列表成功") return SuccessResponse(data=result_dict, msg="查询岗位列表成功") diff --git a/backend/app/api/v1/module_system/position/crud.py b/backend/app/api/v1/module_system/position/crud.py index 79b09aa9..d7734c27 100644 --- a/backend/app/api/v1/module_system/position/crud.py +++ b/backend/app/api/v1/module_system/position/crud.py @@ -25,7 +25,7 @@ class PositionCRUD(CRUDBase[PositionModel, PositionCreateSchema, PositionUpdateS """ return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[PositionModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[PositionModel]: """ 获取岗位列表 diff --git a/backend/app/api/v1/module_system/position/param.py b/backend/app/api/v1/module_system/position/param.py index 63d4fe03..c28ddfb2 100644 --- a/backend/app/api/v1/module_system/position/param.py +++ b/backend/app/api/v1/module_system/position/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -14,10 +13,9 @@ class PositionQueryParam: name: Optional[str] = Query(None, description="岗位名称"), status: Optional[bool] = Query(None, description="是否可用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) @@ -28,6 +26,4 @@ class PositionQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/position/service.py b/backend/app/api/v1/module_system/position/service.py index 26dccbfa..0fad68ba 100644 --- a/backend/app/api/v1/module_system/position/service.py +++ b/backend/app/api/v1/module_system/position/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException @@ -25,12 +25,8 @@ class PositionService: return PositionOutSchema.model_validate(position).model_dump() @classmethod - async def get_position_list_service(cls, auth: AuthSchema, search: PositionQueryParam, order_by: List[Dict] = None) -> List[Dict]: + async def get_position_list_service(cls, auth: AuthSchema, search: Optional[PositionQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: """获取岗位列表""" - if order_by: - order_by = eval(order_by) - else: - order_by = [{"order": "asc"}] position_list = await PositionCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) return [PositionOutSchema.model_validate(position).model_dump() for position in position_list] diff --git a/backend/app/api/v1/module_system/role/controller.py b/backend/app/api/v1/module_system/role/controller.py index 2e0a0f7b..24271e61 100644 --- a/backend/app/api/v1/module_system/role/controller.py +++ b/backend/app/api/v1/module_system/role/controller.py @@ -30,7 +30,10 @@ async def get_obj_list_controller( search: RoleQueryParam = Depends(), auth: AuthSchema = Depends(AuthPermission(permissions=["system:role:query"])), ) -> JSONResponse: - result_dict_list = await RoleService.get_role_list_service(search=search, auth=auth, order_by=page.order_by) + order_by = [{"order": "asc"}] + if page.order_by: + order_by = page.order_by + result_dict_list = await RoleService.get_role_list_service(search=search, auth=auth, order_by=order_by) result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size) logger.info(f"查询角色成功") return SuccessResponse(data=result_dict, msg="查询角色成功") diff --git a/backend/app/api/v1/module_system/role/crud.py b/backend/app/api/v1/module_system/role/crud.py index cf5fe7d5..7b534a1d 100644 --- a/backend/app/api/v1/module_system/role/crud.py +++ b/backend/app/api/v1/module_system/role/crud.py @@ -21,7 +21,7 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]): """根据id获取角色信息""" return await self.get(id=id) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[RoleModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[RoleModel]: """获取角色列表""" return await self.list(search=search, order_by=order_by) diff --git a/backend/app/api/v1/module_system/role/param.py b/backend/app/api/v1/module_system/role/param.py index 1d883f16..4d6ea717 100644 --- a/backend/app/api/v1/module_system/role/param.py +++ b/backend/app/api/v1/module_system/role/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -14,10 +13,9 @@ class RoleQueryParam: name: Optional[str] = Query(None, description="角色名称"), status: Optional[bool] = Query(None, description="是否可用"), creator: Optional[int] = Query(None, description="创建人"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), ) -> None: - super().__init__() # 模糊查询字段 self.name = ("like", name) @@ -28,6 +26,4 @@ class RoleQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/role/service.py b/backend/app/api/v1/module_system/role/service.py index 014bf40d..43f80b55 100644 --- a/backend/app/api/v1/module_system/role/service.py +++ b/backend/app/api/v1/module_system/role/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException @@ -26,12 +26,8 @@ class RoleService: return RoleOutSchema.model_validate(role).model_dump() @classmethod - async def get_role_list_service(cls, auth: AuthSchema, search: RoleQueryParam, order_by: List[Dict[str, str]] = None) -> List[Dict]: + async def get_role_list_service(cls, auth: AuthSchema, search: Optional[RoleQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: """获取角色列表""" - if order_by: - order_by = eval(order_by) - else: - order_by = [{"order": "asc"}] role_list = await RoleCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) return [RoleOutSchema.model_validate(role).model_dump() for role in role_list] diff --git a/backend/app/api/v1/module_system/user/controller.py b/backend/app/api/v1/module_system/user/controller.py index 5337cd83..e3622273 100644 --- a/backend/app/api/v1/module_system/user/controller.py +++ b/backend/app/api/v1/module_system/user/controller.py @@ -69,7 +69,7 @@ async def change_current_user_password_controller( return SuccessResponse(data=result_dict, msg='修改密码成功, 请重新登录') @UserRouter.put("/reset/password", summary="重置密码", description="重置密码") -async def change_current_user_password_controller( +async def reset_password_controller( data: ResetPasswordSchema, auth: AuthSchema = Depends(get_current_user) ) -> JSONResponse: diff --git a/backend/app/api/v1/module_system/user/crud.py b/backend/app/api/v1/module_system/user/crud.py index 6936b0fd..7492935b 100644 --- a/backend/app/api/v1/module_system/user/crud.py +++ b/backend/app/api/v1/module_system/user/crud.py @@ -56,7 +56,7 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): """ return await self.get(mobile=mobile) - async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[UserModel]: + async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[UserModel]: """ 获取用户列表 diff --git a/backend/app/api/v1/module_system/user/param.py b/backend/app/api/v1/module_system/user/param.py index 611c47ce..116e64e1 100644 --- a/backend/app/api/v1/module_system/user/param.py +++ b/backend/app/api/v1/module_system/user/param.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime from typing import Optional from fastapi import Query @@ -17,11 +16,10 @@ class UserQueryParam: email: Optional[str] = Query(None, description="邮箱", pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'), dept_id: Optional[int] = Query(None, description="部门ID"), status: Optional[bool] = Query(None, description="是否可用"), - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), creator: Optional[int] = Query(None, description="创建人"), ) -> None: - super().__init__() # 模糊查询字段 self.username = ("like", username) @@ -36,6 +34,4 @@ class UserQueryParam: # 时间范围查询 if start_time and end_time: - start_datetime = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S') - end_datetime = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S') - self.created_at = ("between", (start_datetime, end_datetime)) + self.created_at = ("between", (start_time, end_time)) diff --git a/backend/app/api/v1/module_system/user/service.py b/backend/app/api/v1/module_system/user/service.py index 3eb6620f..81f26405 100644 --- a/backend/app/api/v1/module_system/user/service.py +++ b/backend/app/api/v1/module_system/user/service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import io -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from fastapi import UploadFile import pandas as pd @@ -52,9 +52,7 @@ class UserService: return UserOutSchema.model_validate(user).model_dump() @classmethod - async def get_user_list_service(cls, auth: AuthSchema, search: UserQueryParam, order_by: List[Dict]= None) -> List[Dict]: - if order_by: - order_by = eval(order_by) + async def get_user_list_service(cls, auth: AuthSchema, search: Optional[UserQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: user_list = await UserCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) user_dict_list = [] for user in user_list: @@ -158,7 +156,7 @@ class UserService: raise CustomException(msg="超级管理员不能删除") if user.status: raise CustomException(msg="用户已启用,不能删除") - if auth.user.id == id: + if auth.user and auth.user.id == id: raise CustomException(msg="不能删除当前登陆用户") # 删除用户角色关联数据 await UserCRUD(auth).set_user_roles_crud(user_ids=ids, role_ids=[]) @@ -173,35 +171,35 @@ class UserService: async def get_current_user_info_service(cls, auth: AuthSchema) -> Dict: """获取当前用户信息""" # 获取用户基本信息 - user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id) - if not user: + if not auth.user: raise CustomException(msg="用户不存在") + user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id) # 获取部门名称 - if user.dept_id: - dept = await DeptCRUD(auth).get_by_id_crud(id=auth.user.dept_id) - user.dept_name = dept.name if dept else None + if user and user.dept_id: + dept = await DeptCRUD(auth).get_by_id_crud(id=user.dept_id) + UserOutSchema.dept_name = dept.name if dept else None user_dict = UserOutSchema.model_validate(user).model_dump() # 获取菜单权限 - if auth.user.is_superuser: + if auth.user and auth.user.is_superuser: # 使用树形结构查询,预加载children关系 menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'type': ('in', [1, 2, 4]), 'status': True}) menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all] else: - # 收集用户所有角色的菜单ID - menu_ids = [] - for role in auth.user.roles: - for menu in role.menus: - if menu.status and menu.type in [1, 2, 4]: - menu_ids.append(menu.id) + # 收集用户所有角色的菜单ID,使用列表推导式优化代码 + menu_ids = { + menu.id + for role in auth.user.roles or [] + for menu in role.menus + if menu.status and menu.type in [1, 2, 4] + } # 使用树形结构查询,预加载children关系 - if menu_ids: - menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'id': ('in', menu_ids)}) - menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all] - else: - menus = [] + menus = [ + MenuOutSchema.model_validate(menu).model_dump() + for menu in await MenuCRUD(auth).get_tree_list_crud(search={'id': ('in', list(menu_ids))}) + ] if menu_ids else [] user_dict["menus"] = traversal_to_tree(menus) return user_dict diff --git a/backend/app/common/request.py b/backend/app/common/request.py index ccbe96e4..087c095f 100644 --- a/backend/app/common/request.py +++ b/backend/app/common/request.py @@ -16,7 +16,7 @@ class PageResultSchema(BaseModel): page_size: Optional[int] = Field(default=None, ge=1, description="页面大小,默认为10") total: int = Field(default=0, ge=0, description="总记录数") has_next: Optional[bool] = Field(default=False, description="是否有下一页") - items: List[Any] = Field(default_factory=list, description="分页后的数据列表") + items: Optional[List[Any]] = Field(default_factory=list, description="分页后的数据列表") class PaginationService: diff --git a/backend/app/config/setting.py b/backend/app/config/setting.py index 849862f8..9ec44bfc 100755 --- a/backend/app/config/setting.py +++ b/backend/app/config/setting.py @@ -107,10 +107,8 @@ class Settings(BaseSettings): # 数据库类型 DATABASE_TYPE: Literal['sqlite','mysql', 'postgresql'] - # SQLite数据库连接 - SQLITE_DB_NAME: str - # MySQL数据库连接 + # MySQL/PostgreSQL/SQLite数据库连接 DATABASE_HOST: str DATABASE_PORT: int DATABASE_USER: str @@ -295,7 +293,7 @@ class Settings(BaseSettings): elif self.DATABASE_TYPE == "postgresql": return f"postgresql+asyncpg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}" elif self.DATABASE_TYPE == "sqlite": - return f"sqlite+aiosqlite:///{self.BASE_DIR.joinpath(self.SQLITE_DB_NAME)}?characterEncoding=UTF-8" + return f"sqlite+aiosqlite:///{self.BASE_DIR.joinpath(self.DATABASE_NAME + '.db')}?characterEncoding=UTF-8" else: raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 请选择 mysql、postgresql、sqlite") @@ -307,7 +305,7 @@ class Settings(BaseSettings): elif self.DATABASE_TYPE == "postgresql": return f"postgresql+psycopg2://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}" elif self.DATABASE_TYPE == "sqlite": - return f"sqlite:///{self.BASE_DIR.joinpath(self.SQLITE_DB_NAME)}?characterEncoding=UTF-8" + return f"sqlite:///{self.BASE_DIR.joinpath(self.DATABASE_NAME + '.db')}?charset=utf8" else: raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 mysql、postgresql、sqlite") diff --git a/backend/app/core/ap_scheduler.py b/backend/app/core/ap_scheduler.py index 56d0f5b0..fe9a50a1 100644 --- a/backend/app/core/ap_scheduler.py +++ b/backend/app/core/ap_scheduler.py @@ -3,25 +3,24 @@ import json import importlib from datetime import datetime -import croniter -from typing import Union, List, Dict, Any, Optional, Callable, Coroutine +from typing import Union, List, Any, Optional from asyncio import iscoroutinefunction from apscheduler.job import Job -from apscheduler.events import JobExecutionEvent, JobSubmissionEvent, EVENT_ALL, JobEvent, EVENT_JOB_EXECUTED, EVENT_JOB_ERROR +from apscheduler.events import JobExecutionEvent, EVENT_ALL, JobEvent from apscheduler.executors.asyncio import AsyncIOExecutor from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.executors.pool import ProcessPoolExecutor from apscheduler.jobstores.memory import MemoryJobStore from apscheduler.jobstores.redis import RedisJobStore -from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore -from apscheduler.jobstores.mongodb import MongoDBJobStore -from apscheduler.jobstores.base import JobLookupError, ConflictingIdError -from apscheduler.schedulers.background import BackgroundScheduler -from apscheduler.triggers.combining import OrTrigger from apscheduler.triggers.cron import CronTrigger from apscheduler.triggers.date import DateTrigger from apscheduler.triggers.interval import IntervalTrigger -from pymongo import MongoClient +# from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore +# from apscheduler.jobstores.mongodb import MongoDBJobStore +# from apscheduler.jobstores.base import JobLookupError, ConflictingIdError +# from apscheduler.schedulers.background import BackgroundScheduler +# from apscheduler.triggers.combining import OrTrigger +# from pymongo import MongoClient from app.config.setting import settings from app.core.database import engine, session_connect, SessionLocal, AsyncSessionLocal @@ -93,7 +92,7 @@ class SchedulerUtil: # 获取调用目标字符串 invoke_target = query_job_info.get('func') # 获取调用函数位置参数 - job_args = ','.join(query_job_info.get('args')) + job_args = ','.join(map(str, query_job_info.get('args', []))) # 获取调用函数关键字参数 job_kwargs = json.dumps(query_job_info.get('kwargs')) # 获取任务触发器 @@ -114,7 +113,8 @@ class SchedulerUtil: create_time=datetime.now(), ) session = SessionLocal() - JobLogCRUD(AuthSchema(db=session)).create_obj_log_crud(data=job_log) + session.add(**job_log.model_dump()) + session.commit() session.close() @classmethod @@ -149,7 +149,7 @@ class SchedulerUtil: logger.info('关闭定时任务成功') @classmethod - def get_job(cls, job_id: Union[str, int]) -> Job: + def get_job(cls, job_id: Union[str, int]) -> Optional[Job]: """ 获取 @@ -173,12 +173,12 @@ class SchedulerUtil: :param job_info: 任务对象信息 :return: """ + # 动态导入模块 + # 1. 解析调用目标 + # app.module_task.scheduler_test.job + module_path, func_name = str(job_info.func).rsplit('.', 1) + module_path = "app.module_task." + module_path try: - # 动态导入模块 - # 1. 解析调用目标 - # app.module_task.scheduler_test.job - module_path, func_name = str(job_info.func).rsplit('.', 1) - module_path = "app.module_task." + module_path module = importlib.import_module(module_path) job_func = getattr(module, func_name) @@ -316,7 +316,7 @@ class SchedulerUtil: logger.info(f"查看获取全部任务:{cls.get_all_jobs()}, 状态: {cls.get_job_status()}") @classmethod - def reschedule_job(cls, job_id: Union[str, int]) -> Job: + def reschedule_job(cls, job_id: Union[str, int]) -> Optional[Job]: """ 重启 :param job_id: 任务id diff --git a/backend/app/core/base_crud.py b/backend/app/core/base_crud.py index 4b65f6ab..62ecbc6b 100644 --- a/backend/app/core/base_crud.py +++ b/backend/app/core/base_crud.py @@ -1,38 +1,41 @@ # -*- coding: utf-8 -*- from pydantic import BaseModel -from typing import TypeVar, Sequence, Generic, Dict, Any, List, Union, Optional +from typing import TypeVar, Sequence, Generic, Dict, Any, List, Optional, Type from sqlalchemy.sql.elements import ColumnElement -from sqlalchemy.orm import Session, selectinload, DeclarativeBase +from sqlalchemy.orm import selectinload from sqlalchemy.engine import Result -from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import asc, func, select, delete, Select, desc, update, or_, and_ +from app.core.base_model import MappedBase from app.api.v1.module_system.auth.schema import AuthSchema from app.api.v1.module_system.dept.model import DeptModel from app.api.v1.module_system.user.model import UserModel from app.utils.common_util import get_child_id_map, get_child_recursion from app.core.exceptions import CustomException +from app.common.request import PageResultSchema +from app.core.serialize import Serialize -ModelType = TypeVar("ModelType", bound=DeclarativeBase) +ModelType = TypeVar("ModelType", bound=MappedBase) CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) +OutSchemaType = TypeVar("OutSchemaType", bound=BaseModel) class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """基础数据层""" - def __init__(self, model: ModelType, auth: AuthSchema) -> None: + def __init__(self, model: Type[ModelType], auth: AuthSchema) -> None: """ 初始化CRUDBase类 Args: - model: 数据模型 + model: 数据模型类 auth: 认证信息 """ self.model = model self.auth = auth - self.db: AsyncSession | Session | None = auth.db + self.db = auth.db self.current_user = auth.user async def get(self, **kwargs) -> Optional[ModelType]: @@ -50,8 +53,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """ try: conditions = await self.__build_conditions(**kwargs) - sql = (select(self.model) - .where(*conditions)) + sql = select(self.model).where(*conditions) # 只有继承自CreatorMixin的模型才有creator关系 if hasattr(self.model, "creator_id"): sql = sql.options(selectinload(self.model.creator)) @@ -67,7 +69,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): except Exception as e: raise CustomException(msg=f"获取查询失败: {str(e)}") - async def list(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[ModelType]: + async def list(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[ModelType]: """ 根据条件获取对象列表和总数 @@ -84,9 +86,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): try: conditions = await self.__build_conditions(**search) if search else [] order = order_by or [{'id': 'asc'}] - sql = (select(self.model) - .where(*conditions) - .order_by(*self.__order_by(order))) + sql = select(self.model).where(*conditions).order_by(*self.__order_by(order)) # 只有继承自CreatorMixin的模型才有creator关系 if hasattr(self.model, "creator_id"): sql = sql.options(selectinload(self.model.creator)) @@ -96,7 +96,84 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): except Exception as e: raise CustomException(msg=f"列表查询失败: {str(e)}") - async def create(self, data: Union[CreateSchemaType, Dict]) -> ModelType: + async def tree_list(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, children_attr: str = 'children') -> Sequence[ModelType]: + """ + 获取树形结构数据列表 + + Args: + search: 查询条件 + order_by: 排序字段 + children_attr: 子节点属性名 + + Returns: + Sequence[ModelType]: 树形结构数据列表 + + Raises: + CustomException: 查询失败时抛出异常 + """ + try: + from sqlalchemy.orm import selectinload + + conditions = await self.__build_conditions(**search) if search else [] + order = order_by or [{'id': 'asc'}] + sql = select(self.model).where(*conditions).order_by(*self.__order_by(order)) + + # 如果模型有children属性,则预加载该关系 + if hasattr(self.model, children_attr): + sql = sql.options(selectinload(getattr(self.model, children_attr))) + + # 只有继承自CreatorMixin的模型才有creator关系 + if hasattr(self.model, "creator_id"): + sql = sql.options(selectinload(self.model.creator)) + + sql = await self.__filter_permissions(sql) + result: Result = await self.db.execute(sql) + return result.scalars().all() + except Exception as e: + raise CustomException(msg=f"树形列表查询失败: {str(e)}") + + async def page(self, offset: int, limit: int, order_by: List[Dict[str, str]], search: Dict, out_schema: Type[OutSchemaType]) -> Dict: + try: + from sqlalchemy.orm import selectinload + + conditions = await self.__build_conditions(**search) if search else [] + order = order_by or [{'id': 'asc'}] + sql = select(self.model).where(*conditions).order_by(*self.__order_by(order)) + # 只有继承自CreatorMixin的模型才有creator关系 + if hasattr(self.model, "creator_id"): + sql = sql.options(selectinload(self.model.creator)) + sql = await self.__filter_permissions(sql) + + # 获取总数 + count_sql = select(func.count()).select_from(self.model) + # 应用相同的过滤条件到计数查询 + if conditions: + count_sql = count_sql.where(*conditions) + count_sql = await self.__filter_permissions(count_sql) + + total_result = await self.db.execute(count_sql) + total = total_result.scalar() + + if total is None: + total = 0 + + result: Result = await self.db.execute(sql.offset(offset).limit(limit)) + + objs = result.scalars().all() + + data=PageResultSchema( + items=[out_schema.model_validate(obj).model_dump() for obj in objs], + total=total, + page_no=offset // limit + 1 if limit else 1, + page_size=limit, + has_next=offset + limit < total, + ).model_dump() + + return data + except Exception as e: + raise CustomException(msg=f"分页查询失败: {str(e)}") + + async def create(self, data: CreateSchemaType) -> ModelType: """ 创建新对象 @@ -127,7 +204,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): except Exception as e: raise CustomException(msg=f"创建失败: {str(e)}") - async def update(self, id: int, data: Union[UpdateSchemaType, Dict]) -> ModelType: + async def update(self, id: int, data: UpdateSchemaType) -> ModelType: """ 更新对象 @@ -144,6 +221,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): try: obj_dict = data if isinstance(data, dict) else data.model_dump(exclude_unset=True, exclude={"id"}) obj = await self.get(id=id) + if not obj: + raise CustomException(msg="更新对象不存在") for key, value in obj_dict.items(): if hasattr(obj, key): @@ -226,7 +305,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): except Exception as e: raise CustomException(msg=f"更新关系失败: {str(e)}") - async def __filter_permissions(self, sql: Select[Any]) -> Select[Any]: + async def __filter_permissions(self, sql: Select) -> Select: """过滤数据权限""" # 如果不需要检查数据权限,则直接返回 if not self.current_user or not self.auth.check_data_scope: @@ -352,42 +431,4 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): conditions.append(getattr(attr, seq.replace("==", "__eq__"))(val)) else: conditions.append(attr == value) - return conditions - - async def get_tree_list(self, search: Dict = None, order_by: List[Dict[str, str]] = None, children_attr: str = 'children') -> Sequence[ModelType]: - """ - 获取树形结构数据列表 - - Args: - search: 查询条件 - order_by: 排序字段 - children_attr: 子节点属性名 - - Returns: - Sequence[ModelType]: 树形结构数据列表 - - Raises: - CustomException: 查询失败时抛出异常 - """ - try: - from sqlalchemy.orm import selectinload - - conditions = await self.__build_conditions(**search) if search else [] - order = order_by or [{'id': 'asc'}] - sql = (select(self.model) - .where(*conditions) - .order_by(*self.__order_by(order))) - - # 如果模型有children属性,则预加载该关系 - if hasattr(self.model, children_attr): - sql = sql.options(selectinload(getattr(self.model, children_attr))) - - # 只有继承自CreatorMixin的模型才有creator关系 - if hasattr(self.model, "creator_id"): - sql = sql.options(selectinload(self.model.creator)) - - sql = await self.__filter_permissions(sql) - result: Result = await self.db.execute(sql) - return result.scalars().all() - except Exception as e: - raise CustomException(msg=f"树形列表查询失败: {str(e)}") \ No newline at end of file + return conditions \ No newline at end of file diff --git a/backend/app/core/base_model.py b/backend/app/core/base_model.py index 4df3e7cf..f075c35e 100644 --- a/backend/app/core/base_model.py +++ b/backend/app/core/base_model.py @@ -4,14 +4,13 @@ 提供跨数据库兼容的基础模型类和类型装饰器 """ -from datetime import datetime import re +from datetime import datetime from typing import Literal, Optional, Dict, Any, Union -from sqlalchemy import Boolean, String, Integer, DateTime, ForeignKey, Text, BigInteger -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy import Integer, DateTime, Text from sqlalchemy.ext.asyncio import AsyncAttrs -from sqlalchemy.orm import relationship, DeclarativeBase, Mapped, declared_attr, mapped_column, MappedAsDataclass +from sqlalchemy.orm import relationship, DeclarativeBase, Mapped, declared_attr, mapped_column from sqlalchemy.engine.row import Row from sqlalchemy.orm.collections import InstrumentedList diff --git a/backend/app/core/base_params.py b/backend/app/core/base_params.py index 8c2cb74f..e083af22 100644 --- a/backend/app/core/base_params.py +++ b/backend/app/core/base_params.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Optional +from typing import Optional, List, Dict from fastapi import Query @@ -11,7 +11,7 @@ class PaginationQueryParam: self, page_no: Optional[int] = Query(default=None, description="当前页码", ge=1), page_size: Optional[int] = Query(default=None, description="每页数量", ge=1, le=100), - order_by: Optional[str] = Query(default=None, description="排序字段,格式:[{'field':'asc/desc'}]"), + order_by: Optional[str] = Query(default=None, description="排序字段,格式:field1,asc;field2,desc"), ) -> None: """ 初始化分页查询参数 @@ -22,5 +22,17 @@ class PaginationQueryParam: """ self.page_no = page_no self.page_size = page_size - self.order_by = order_by + # 将字符串格式的order_by转换为服务层需要的List[Dict[str, str]]格式 + if order_by: + try: + self.order_by = [] + for item in order_by.split(';'): + if item.strip(): + field, direction = item.split(',', 1) + self.order_by.append({field.strip(): direction.strip().lower()}) + except ValueError: + # 如果解析失败,使用默认排序 + self.order_by = [{'id': 'asc'}] + else: + self.order_by = [{'id': 'asc'}] diff --git a/backend/app/core/exceptions.py b/backend/app/core/exceptions.py index 4c3fb660..0301ebd8 100644 --- a/backend/app/core/exceptions.py +++ b/backend/app/core/exceptions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from typing import Any, Optional -from fastapi import Request, status +from fastapi import FastAPI, Request, status from fastapi.exceptions import RequestValidationError, ResponseValidationError from pydantic_validation_decorator import FieldValidationError from starlette.responses import JSONResponse @@ -43,57 +43,62 @@ class CustomException(Exception): return self.msg -async def CustomExceptionHandler(request: Request, exc: CustomException) -> JSONResponse: - """自定义异常处理器""" - logger.error(f"请求地址: {request.url}, 错误信息: {exc.msg}, 错误详情: {exc.data}") - return ErrorResponse(msg=exc.msg, code=exc.code, status_code=exc.status_code, data=exc.data) +def handle_exception(app: FastAPI): + """ + 全局异常处理 + """ + @app.exception_handler(CustomException) + async def CustomExceptionHandler(request: Request, exc: CustomException) -> JSONResponse: + """自定义异常处理器""" + logger.error(f"请求地址: {request.url}, 错误信息: {exc.msg}, 错误详情: {exc.data}") + return ErrorResponse(msg=exc.msg, code=exc.code, status_code=exc.status_code, data=exc.data) + @app.exception_handler(HTTPException) + async def HttpExceptionHandler(request: Request, exc: HTTPException) -> JSONResponse: + """HTTP异常处理器""" + logger.error(f"请求地址: {request.url}, 错误详情: {exc.detail}") + return ErrorResponse(msg=exc.detail, status_code=exc.status_code) -async def HttpExceptionHandler(request: Request, exc: HTTPException) -> JSONResponse: - """HTTP异常处理器""" - logger.error(f"请求地址: {request.url}, 错误详情: {exc.detail}") - return ErrorResponse(msg=exc.detail, status_code=exc.status_code) + @app.exception_handler(RequestValidationError) + async def ValidationExceptionHandler(request: Request, exc: RequestValidationError) -> JSONResponse: + """请求参数验证异常处理器""" + error_mapping = { + "Field required": "请求失败,缺少必填项!", + "value is not a valid list": "类型错误,提交参数应该为列表!", + "value is not a valid int": "类型错误,提交参数应该为整数!", + "value could not be parsed to a boolean": "类型错误,提交参数应该为布尔值!", + "Input should be a valid list": "类型错误,输入应该是一个有效的列表!" + } + msg = error_mapping.get(exc.errors()[0].get('msg'), exc.errors()[0].get('msg')) + logger.error(f"请求地址: {request.url}, 错误信息: {msg}, 错误详情: {exc}") + return ErrorResponse(msg=str(msg), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, data=exc.body) + @app.exception_handler(ResponseValidationError) + async def ResponseValidationHandle(request: Request, exc: ResponseValidationError) -> JSONResponse: + logger.error(f"请求地址: {request.url}, 错误详情: {exc}") + return ErrorResponse(msg=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=exc.body) -async def ValidationExceptionHandler(request: Request, exc: RequestValidationError) -> JSONResponse: - """请求参数验证异常处理器""" - error_mapping = { - "Field required": "请求失败,缺少必填项!", - "value is not a valid list": "类型错误,提交参数应该为列表!", - "value is not a valid int": "类型错误,提交参数应该为整数!", - "value could not be parsed to a boolean": "类型错误,提交参数应该为布尔值!", - "Input should be a valid list": "类型错误,输入应该是一个有效的列表!" - } - msg = error_mapping.get(exc.errors()[0].get('msg'), exc.errors()[0].get('msg')) - logger.error(f"请求地址: {request.url}, 错误信息: {msg}, 错误详情: {exc}") - return ErrorResponse(msg=str(msg), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, data=exc.body) + @app.exception_handler(SQLAlchemyError) + async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse: + """数据库异常处理器""" + error_msg = f'数据库操作失败: {exc}' + logger.error(f"请求地址: {request.url}, 错误详情: {error_msg}") + return ErrorResponse(msg=error_msg, status_code=status.HTTP_400_BAD_REQUEST, data=str(exc)) + @app.exception_handler(ValueError) + async def ValueExceptionHandler(request: Request, exc: ValueError) -> JSONResponse: + """值异常处理器""" + logger.error(f"请求地址: {request.url}, 错误详情: {exc}") + return ErrorResponse(msg=str(exc)) -async def ResponseValidationHandle(request: Request, exc: ResponseValidationError) -> JSONResponse: - logger.error(f"请求地址: {request.url}, 错误详情: {exc}") - return ErrorResponse(msg=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=exc.body) + @app.exception_handler(FieldValidationError) + async def FieldValidationExceptionHandler(request: Request, exc: FieldValidationError) -> JSONResponse: + """字段验证异常处理器""" + logger.error(f"请求地址: {request.url}, 错误信息: {exc.message}, 错误详情: {exc}") + return ErrorResponse(msg=str(exc)) - -async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse: - """数据库异常处理器""" - error_msg = f'数据库操作失败: {exc}' - logger.error(f"请求地址: {request.url}, 错误详情: {error_msg}") - return ErrorResponse(msg=error_msg, status_code=status.HTTP_400_BAD_REQUEST, data=str(exc)) - - -async def ValueExceptionHandler(request: Request, exc: ValueError) -> JSONResponse: - """值异常处理器""" - logger.error(f"请求地址: {request.url}, 错误详情: {exc}") - return ErrorResponse(msg=str(exc)) - - -async def FieldValidationExceptionHandler(request: Request, exc: FieldValidationError) -> JSONResponse: - """字段验证异常处理器""" - logger.error(f"请求地址: {request.url}, 错误信息: {exc.message}, 错误详情: {exc}") - return ErrorResponse(msg=str(exc)) - - -async def AllExceptionHandler(request: Request, exc: Exception) -> JSONResponse: - """全局异常处理器""" - logger.error(f"请求地址: {request.url}, 错误详情: {exc}") - return ErrorResponse(msg='服务器内部错误', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=str(exc)) + @app.exception_handler(Exception) + async def AllExceptionHandler(request: Request, exc: Exception) -> JSONResponse: + """全局异常处理器""" + logger.error(f"请求地址: {request.url}, 错误详情: {exc}") + return ErrorResponse(msg='服务器内部错误', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=str(exc)) diff --git a/backend/app/core/serialize.py b/backend/app/core/serialize.py new file mode 100644 index 00000000..de736df4 --- /dev/null +++ b/backend/app/core/serialize.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +from pydantic import BaseModel +from typing import TypeVar, Dict, Any, Type, Generic +from sqlalchemy.orm import DeclarativeBase + +ModelType = TypeVar("ModelType", bound=DeclarativeBase) +SchemaType = TypeVar("SchemaType", bound=BaseModel) + + +class Serialize(Generic[ModelType, SchemaType]): + """ + 序列化工具类,提供模型、Schema和字典之间的转换功能 + """ + + @classmethod + def schema_to_model(cls,schema: Type[SchemaType], model: Type[ModelType]) -> ModelType: + """ + 将Pydantic Schema转换为SQLAlchemy模型 + + Args: + schema: Pydantic Schema实例 + model: SQLAlchemy模型类 + + Returns: + SQLAlchemy模型实例 + + Raises: + Exception: 转换过程中可能抛出的异常 + """ + try: + return model(**cls.model_to_dict(model, schema)) + except Exception as e: + raise ValueError(f"序列化失败: {str(e)}") + + @classmethod + def model_to_dict(cls, model: Type[ModelType], schema: Type[SchemaType]) -> Dict[str, Any]: + """ + 将SQLAlchemy模型转换为Pydantic Schema + + Args: + model: SQLAlchemy模型实例 + schema: Pydantic Schema类 + + Returns: + 包含模型数据的字典 + + Raises: + Exception: 转换过程中可能抛出的异常 + """ + try: + return schema.model_validate(model).model_dump() + except Exception as e: + raise ValueError(f"反序列化失败: {str(e)}") + diff --git a/backend/app/core/validator.py b/backend/app/core/validator.py index 76dd4023..3def990c 100644 --- a/backend/app/core/validator.py +++ b/backend/app/core/validator.py @@ -13,8 +13,7 @@ from app.core.exceptions import CustomException DateTimeStr = Annotated[ datetime, AfterValidator(lambda x: datetime_validator(x)), - PlainSerializer(lambda x: x, return_type=str), - # PlainSerializer(lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, datetime) else x, return_type=str), + PlainSerializer(lambda x: x.strftime('%Y-%m-%d %H:%M:%S') if isinstance(x, datetime) else str(x), return_type=str), WithJsonSchema({'type': 'string'}, mode='serialization') ] @@ -34,7 +33,7 @@ Email = Annotated[ WithJsonSchema({'type': 'string'}, mode='serialization') ] -def datetime_validator(value: Union[str, datetime]) -> Union[str, datetime, None]: +def datetime_validator(value: Union[str, datetime]) -> datetime: """ 日期格式验证器 @@ -45,13 +44,14 @@ def datetime_validator(value: Union[str, datetime]) -> Union[str, datetime, None pattern = "%Y-%m-%d %H:%M:%S" try: if isinstance(value, str): - value = datetime.strptime(value, pattern) + return datetime.strptime(value, pattern) elif isinstance(value, datetime): - value = value.strftime(pattern) + return value except Exception: raise CustomException(code=RET.ERROR.code, msg="无效的日期格式") - return value + # 如果 value 是 None 或其他类型,抛出异常 + raise CustomException(code=RET.ERROR.code, msg="无效的日期格式") def email_validator(value: str) -> str: """ diff --git a/backend/app/plugin/init_app.py b/backend/app/plugin/init_app.py index 664d71c1..9473ed23 100644 --- a/backend/app/plugin/init_app.py +++ b/backend/app/plugin/init_app.py @@ -15,22 +15,7 @@ from app.config.setting import settings from app.core.ap_scheduler import SchedulerUtil from app.core.logger import logger from app.utils.common_util import import_module, import_modules_async -from app.core.exceptions import ( - CustomException, - CustomExceptionHandler, - HTTPException, - HttpExceptionHandler, - ValidationExceptionHandler, - RequestValidationError, - SQLAlchemyError, - SQLAlchemyExceptionHandler, - ValueExceptionHandler, - FieldValidationError, - FieldValidationExceptionHandler, - AllExceptionHandler, - ResponseValidationHandle, - ResponseValidationError -) +from app.core.exceptions import handle_exception from app.scripts.initialize import InitializeData from app.api.v1.module_system.params.service import ParamsService from app.api.v1.module_system.dict.service import DictDataService @@ -77,14 +62,7 @@ def register_exceptions(app: FastAPI) -> None: """ 异常捕捉 """ - app.add_exception_handler(CustomException, CustomExceptionHandler) - app.add_exception_handler(HTTPException, HttpExceptionHandler) - app.add_exception_handler(RequestValidationError,ValidationExceptionHandler) - app.add_exception_handler(SQLAlchemyError, SQLAlchemyExceptionHandler) - app.add_exception_handler(ValueError, ValueExceptionHandler) - app.add_exception_handler(Exception, AllExceptionHandler) - app.add_exception_handler(FieldValidationError,FieldValidationExceptionHandler) - app.add_exception_handler(ResponseValidationError,ResponseValidationHandle) + handle_exception(app) def register_routers(app: FastAPI) -> None: """ @@ -110,7 +88,7 @@ def reset_api_docs(app: FastAPI) -> None: @app.get(settings.DOCS_URL, include_in_schema=False) async def custom_swagger_ui_html() -> HTMLResponse: return get_swagger_ui_html( - openapi_url=app.root_path + app.openapi_url, + openapi_url=str(app.root_path) + str(app.openapi_url), title=app.title + " - Swagger UI", oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url, swagger_js_url=settings.SWAGGER_JS_URL, @@ -118,14 +96,14 @@ def reset_api_docs(app: FastAPI) -> None: swagger_favicon_url=settings.FAVICON_URL, ) - @app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False) + @app.get(str(app.swagger_ui_oauth2_redirect_url), include_in_schema=False) async def swagger_ui_redirect(): return get_swagger_ui_oauth2_redirect_html() @app.get(settings.REDOC_URL, include_in_schema=False) async def custom_redoc_html(): return get_redoc_html( - openapi_url=app.root_path + app.openapi_url, + openapi_url=str(app.root_path) + str(app.openapi_url), title=app.title + " - ReDoc", redoc_js_url=settings.REDOC_JS_URL, redoc_favicon_url=settings.FAVICON_URL, diff --git a/backend/app/scripts/data/system_notice.json b/backend/app/scripts/data/system_notice.json new file mode 100644 index 00000000..843e1c97 --- /dev/null +++ b/backend/app/scripts/data/system_notice.json @@ -0,0 +1,18 @@ +[ + { + "notice_title": "系统通知", + "notice_type": "1", + "notice_content": "维护通知:2025-10-01 fastapi_vue3_admin系统凌晨维护", + "status": true, + "description": "通知", + "creator_id": 1 + }, + { + "notice_title": "系统公告", + "notice_type": "2", + "notice_content": "温馨提醒:2025-10-01 fastapi_vue3_admin新版本发布啦", + "status": true, + "description": "公告", + "creator_id": 1 + } +] \ No newline at end of file diff --git a/backend/app/scripts/initialize.py b/backend/app/scripts/initialize.py index 603b2d18..0433da05 100644 --- a/backend/app/scripts/initialize.py +++ b/backend/app/scripts/initialize.py @@ -18,6 +18,7 @@ from app.api.v1.module_system.dept.model import DeptModel from app.api.v1.module_system.menu.model import MenuModel from app.api.v1.module_system.params.model import ParamsModel from app.api.v1.module_system.dict.model import DictTypeModel, DictDataModel +from app.api.v1.module_system.notice.model import NoticeModel class InitializeData: @@ -47,6 +48,8 @@ class InitializeData: UserRolesModel, RoleDeptsModel, RoleMenusModel, + # 通知表(无外键依赖) + NoticeModel, ] async def __init_create_table(self) -> None: diff --git a/backend/env/.env.dev b/backend/env/.env.dev index 228b5f45..6b6d837f 100644 --- a/backend/env/.env.dev +++ b/backend/env/.env.dev @@ -31,9 +31,6 @@ DEMO_ENABLE = False # 是否启用演示模式 # 数据库配置 DATABASE_TYPE = "sqlite" # sqlite、mysql、postgresql -# SQLite配置 -SQLITE_DB_NAME = "dev_sql.db" - # 数据库配置 DATABASE_HOST = "localhost" DATABASE_PORT = 3306 # MySQL默认端口3006 PostgreSQL默认端口 5432 diff --git a/backend/env/.env.prod b/backend/env/.env.prod index 81aae2c8..82057dd5 100644 --- a/backend/env/.env.prod +++ b/backend/env/.env.prod @@ -31,9 +31,6 @@ DEMO_ENABLE = True # 是否启用演示模式 # 数据库配置 DATABASE_TYPE = "mysql" # sqlite、mysql、postgresql -# SQLite配置 -SQLITE_DB_NAME = "prod_sql.db" - # 数据库配置 DATABASE_HOST = "172.18.52.77" DATABASE_PORT = 3306 # MySQL默认端口3006 PostgreSQL默认端口5432 diff --git a/backend/templates/python/param.py.j2 b/backend/templates/python/param.py.j2 index 0574c8f2..e4a12f82 100644 --- a/backend/templates/python/param.py.j2 +++ b/backend/templates/python/param.py.j2 @@ -16,8 +16,8 @@ class {{ tableName|snake_to_pascal_case }}QueryParam: {{ column.columnName }}: Optional[{{ column.pythonType }}] = Query(None, description="{{ column.columnComment }}"), {% endif %} {% endfor %} - start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"), - end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"), + start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"), + end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"), creator: Optional[int] = Query(None, description="创建人"), ) -> None: super().__init__() diff --git a/frontend/src/api/application/myapp.ts b/frontend/src/api/application/myapp.ts index c191a44e..22f3c07e 100644 --- a/frontend/src/api/application/myapp.ts +++ b/frontend/src/api/application/myapp.ts @@ -1,5 +1,7 @@ import request from "@/utils/request"; +const API_PATH = "/application/myapp"; + export const ApplicationAPI = { /** * 获取应用详情 @@ -7,7 +9,7 @@ export const ApplicationAPI = { */ getApplicationDetail(id: number) { return request>({ - url: `/application/application/detail/${id}`, + url: `${API_PATH}/detail/${id}`, method: "get", }); }, @@ -18,7 +20,7 @@ export const ApplicationAPI = { */ getApplicationList(query: ApplicationPageQuery) { return request>>({ - url: `/application/application/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -30,7 +32,7 @@ export const ApplicationAPI = { */ createApplication(body: ApplicationForm) { return request({ - url: `/application/application/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -43,7 +45,7 @@ export const ApplicationAPI = { */ updateApplication(id: number, body: ApplicationForm) { return request({ - url: `/application/application/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -55,7 +57,7 @@ export const ApplicationAPI = { */ deleteApplication(body: number[]) { return request({ - url: `/application/application/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -67,7 +69,7 @@ export const ApplicationAPI = { */ batchAvailableApplication(body: BatchType) { return request({ - url: `/application/application/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); diff --git a/frontend/src/api/demo/example.ts b/frontend/src/api/demo/example.ts index d075b1b2..80633c13 100644 --- a/frontend/src/api/demo/example.ts +++ b/frontend/src/api/demo/example.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/example/demo"; + const ExampleAPI = { getExampleList(query: ExamplePageQuery) { return request>>({ - url: `/example/demo/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const ExampleAPI = { getExampleDetail(query: number) { return request>({ - url: `/example/demo/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createExample(body: ExampleForm) { return request({ - url: `/example/demo/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const ExampleAPI = { updateExample(id: number, body: ExampleForm) { return request({ - url: `/example/demo/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const ExampleAPI = { deleteExample(body: number[]) { return request({ - url: `/example/demo/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const ExampleAPI = { batchAvailableExample(body: BatchType) { return request({ - url: `/example/demo/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); @@ -50,7 +52,7 @@ const ExampleAPI = { exportExample(body: ExamplePageQuery) { return request({ - url: `/example/demo/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", @@ -59,7 +61,7 @@ const ExampleAPI = { downloadTemplate() { return request({ - url: `/example/demo/download/template`, + url: `${API_PATH}/download/template`, method: "post", responseType: "blob", }); @@ -67,7 +69,7 @@ const ExampleAPI = { importExample(body: any) { return request({ - url: `/example/demo/import`, + url: `${API_PATH}/import`, method: "post", data: body, headers: { diff --git a/frontend/src/api/generator/gencode.ts b/frontend/src/api/generator/gencode.ts index 1a8cc0de..5f58659b 100644 --- a/frontend/src/api/generator/gencode.ts +++ b/frontend/src/api/generator/gencode.ts @@ -1,39 +1,38 @@ import request from "@/utils/request"; -const GENERATOR_BASE_URL = "/gencode"; +const API_PATH = "/generator/gencode"; const GencodeAPI = { - // 查询生成表数据 listTable(query: TablePageQuery) { - return request>({ - url: `${GENERATOR_BASE_URL}/list`, + return request>>({ + url: `${API_PATH}/list`, method: 'get', params: query }) }, // 查询db数据库列表 - listDbTable(query: TablePageQuery) { - return request>({ - url: `${GENERATOR_BASE_URL}/db/list`, + listDbTable(query: DBTablePageQuery) { + return request>>({ + url: `${API_PATH}/db/list`, method: 'get', params: query }) }, // 查询表详细信息 - getGenTableDetail(tableId: number) { - return request({ - url: `${GENERATOR_BASE_URL}/detail/${tableId}`, + getGenTableDetail(table_id: number) { + return request>({ + url: `${API_PATH}/detail/${table_id}`, method: 'get' }) }, // 创建表 createTable(sql: string) { - return request({ - url: `${GENERATOR_BASE_URL}/create`, + return request({ + url: `${API_PATH}/create`, method: 'post', params: { sql } }) @@ -41,8 +40,8 @@ const GencodeAPI = { // 修改代码生成信息 updateGenTable(data: GenTableUpdateSchema) { - return request({ - url: `${GENERATOR_BASE_URL}/update`, + return request({ + url: `${API_PATH}/update`, method: 'put', data }) @@ -50,8 +49,8 @@ const GencodeAPI = { // 导入表 importTable(tables: string[]) { - return request({ - url: `${GENERATOR_BASE_URL}/import`, + return request({ + url: `${API_PATH}/import`, method: 'post', data: { tables } }) @@ -60,16 +59,16 @@ const GencodeAPI = { // 预览生成代码 previewTable(tableId: number) { - return request({ - url: `${GENERATOR_BASE_URL}/preview/${tableId}`, + return request>({ + url: `${API_PATH}/preview/${tableId}`, method: 'get' }) }, // 删除表数据 deleteTable(tableIds: number[]) { - return request({ - url: `${GENERATOR_BASE_URL}/delete`, + return request({ + url: `${API_PATH}/delete`, method: 'delete', data: tableIds }) @@ -78,7 +77,7 @@ const GencodeAPI = { // 批量生成代码 batchGenCode(tables: string) { return request({ - url: `${GENERATOR_BASE_URL}/batch/out`, + url: `${API_PATH}/batch/out`, method: 'patch', params: { tables }, responseType: 'blob' @@ -87,16 +86,16 @@ const GencodeAPI = { // 生成代码到指定路径 genCodeToPath(tableName: string) { - return request({ - url: `${GENERATOR_BASE_URL}/out/path/${tableName}`, + return request({ + url: `${API_PATH}/out/path/${tableName}`, method: 'post' }) }, // 同步数据库 syncDb(tableName: string) { - return request({ - url: `${GENERATOR_BASE_URL}/sync/db/${tableName}`, + return request({ + url: `${API_PATH}/sync/db/${tableName}`, method: 'post' }) } @@ -109,33 +108,45 @@ export interface GeneratorPreviewVO { /** 文件生成路径 */ path: string; /** 文件名称 */ - fileName: string; + file_name: string; /** 文件内容 */ content: string; } /** 数据表分页查询参数 */ export interface TablePageQuery extends PageQuery { - /** 关键字(表名) */ - keywords?: string; + /** 表名称 */ + table_name?: string; + /** 表描述 */ + table_comment?: string; + /** 开始时间 */ + start_time?: string; + /** 结束时间 */ + end_time?: string; +} + +/** 数据库表分页查询参数 */ +export interface DBTablePageQuery extends PageQuery { + /** 数据库列名称 */ + column_name?: string; } /** 数据表分页对象 */ export interface TablePageVO { /** 表名称 */ - tableName: string; + table_name: string; /** 表描述 */ - tableComment: string; + table_comment: string; /** 存储引擎 */ engine: string; /** 字符集排序规则 */ - tableCollation: string; + table_collation: string; /** 创建时间 */ - createTime: string; + create_time: string; } /** 代码生成表输出对象 */ @@ -143,45 +154,45 @@ export interface GenTableOutVO { /** 主键 */ id?: number; /** 表名称 */ - tableName: string; + table_name: string; /** 表描述 */ - tableComment: string; + table_comment: string; /** 关联子表的表名 */ - subTableName?: string; + sub_table_name?: string; /** 子表关联的外键名 */ - subTableFkName: string; + sub_table_fk_name: string; /** 实体类名称 */ - className: string; + class_name: string; /** 使用的模板(crud单表操作 tree树表操作) */ - tplCategory?: string; + tpl_category?: string; /** 前端模板类型(element-ui模版 element-plus模版) */ - tplWebType?: string; + tpl_web_type?: string; /** 生成包路径 */ - packageName: string; + package_name: string; /** 生成模块名 */ - moduleName: string; + module_name: string; /** 生成业务名 */ - businessName: string; + business_name: string; /** 生成功能名 */ - functionName: string; + function_name: string; /** 生成功能作者 */ - functionAuthor?: string; + function_author?: string; /** 生成代码方式(0zip压缩包 1自定义路径) */ - genType?: string; + gen_type?: string; /** 生成路径(不填默认项目路径) */ - genPath?: string; + gen_path?: string; /** 其它生成选项 */ options?: string; /** 树编码字段 */ - treeCode?: string; + tree_code?: string; /** 树父编码字段 */ - treeParentCode?: string; + tree_parent_code?: string; /** 树名称字段 */ - treeName?: string; + tree_name?: string; /** 上级菜单ID字段 */ - parentMenuId?: number; + parent_menu_id?: number; /** 上级菜单名称字段 */ - parentMenuName?: string; + parent_menu_name?: string; /** 是否为子表 */ sub?: boolean; /** 是否为树表 */ @@ -193,9 +204,9 @@ export interface GenTableOutVO { /** 代码生成表更新模型 */ export interface GenTableUpdateSchema extends GenTableOutVO { /** 主键信息 */ - pkColumn?: GenTableColumnUpdateSchema; + pk_column?: GenTableColumnUpdateSchema; /** 子表信息 */ - subTable?: GenTableUpdateSchema; + sub_table?: GenTableUpdateSchema; /** 表列信息 */ columns: GenTableColumnUpdateSchema[]; } @@ -205,43 +216,43 @@ export interface GenTableColumnUpdateSchema { /** 主键 */ id?: number; /** 归属表编号 */ - tableId?: number; + table_id?: number; /** 列名称 */ - columnName: string; + column_name: string; /** 列描述 */ - columnComment?: string; + column_comment?: string; /** 列类型 */ - columnType: string; + column_type: string; /** PYTHON类型 */ - pythonType?: string; + python_type?: string; /** PYTHON字段名 */ - pythonField: string; + python_field: string; /** 是否主键(1是) */ - isPk?: string; + is_pk?: string; /** 是否自增(1是) */ - isIncrement?: string; + is_increment?: string; /** 是否必填(1是) */ - isRequired?: string; + is_required?: string; /** 是否唯一(1是) */ - isUnique?: string; + is_unique?: string; /** 是否为插入字段(1是) */ - isInsert?: string; + is_insert?: string; /** 是否编辑字段(1是) */ - isEdit?: string; + is_edit?: string; /** 是否列表字段(1是) */ - isList?: string; + is_list?: string; /** 是否查询字段(1是) */ - isQuery?: string; + is_query?: string; /** 查询方式(等于、不等于、大于、小于、范围) */ - queryType?: string; + query_type?: string; /** 显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件) */ - htmlType: string; + html_type: string; /** 字典类型 */ - dictType: string; + dict_type: string; /** 排序 */ sort?: number; /** 字段大写形式 */ - capPythonField?: string; + cap_python_field?: string; /** 是否主键 */ pk?: boolean; /** 是否自增 */ @@ -259,9 +270,9 @@ export interface GenTableColumnUpdateSchema { /** 是否查询字段 */ query?: boolean; /** 是否为基类字段 */ - superColumn?: boolean; + super_column?: boolean; /** 是否为基类字段白名单 */ - usableColumn?: boolean; + usable_column?: boolean; } /** 表详情查询结果 */ diff --git a/frontend/src/api/monitor/cache.ts b/frontend/src/api/monitor/cache.ts index d98e72db..b17253bb 100644 --- a/frontend/src/api/monitor/cache.ts +++ b/frontend/src/api/monitor/cache.ts @@ -1,51 +1,53 @@ import request from "@/utils/request"; +const API_PATH = "/monitor/cache"; + const CacheAPI = { getCacheInfo() { return request({ - url: `/monitor/cache/info`, + url: `${API_PATH}/info`, method: "get", }); }, getCacheNames() { return request({ - url: `/monitor/cache/get/names`, + url: `${API_PATH}/get/names`, method: "get", }); }, getCacheKeys(cacheName: string) { return request({ - url: `/monitor/cache/get/keys/${cacheName}`, + url: `${API_PATH}/get/keys/${cacheName}`, method: "get", }); }, getCacheValue(cacheName: string, cacheKey: string) { return request({ - url: `/monitor/cache/get/value/${cacheName}/${cacheKey}`, + url: `${API_PATH}/get/value/${cacheName}/${cacheKey}`, method: "get", }); }, deleteCacheName(cacheName: string) { return request({ - url: `/monitor/cache/delete/name/${cacheName}`, + url: `${API_PATH}/delete/name/${cacheName}`, method: "delete", }); }, deleteCacheKey(cacheKey: string) { return request({ - url: `/monitor/cache/delete/key/${cacheKey}`, + url: `${API_PATH}/delete/key/${cacheKey}`, method: "delete", }); }, deleteCacheAll() { return request({ - url: "/monitor/cache/delete/all", + url: `${API_PATH}/delete/all`, method: "delete", }); }, diff --git a/frontend/src/api/monitor/job.ts b/frontend/src/api/monitor/job.ts index 48d6e9a9..3ed9cbc4 100644 --- a/frontend/src/api/monitor/job.ts +++ b/frontend/src/api/monitor/job.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/monitor/job"; + const JobAPI = { getJobList(query: JobPageQuery) { return request>>({ - url: `/monitor/job/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const JobAPI = { getJobDetail(query: number) { return request>({ - url: `/monitor/job/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createJob(body: JobForm) { return request({ - url: `/monitor/job/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const JobAPI = { updateJob(id: number, body: JobForm) { return request({ - url: `/monitor/job/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const JobAPI = { deleteJob(body: number[]) { return request({ - url: `/monitor/job/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const JobAPI = { exportJob(body: JobPageQuery) { return request({ - url: `/monitor/job/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", @@ -51,14 +53,14 @@ const JobAPI = { clearJob() { return request({ - url: `/monitor/job/clear`, + url: `${API_PATH}/clear`, method: "delete", }); }, OptionJob(params: JobOptionData) { return request({ - url: `/monitor/job/option`, + url: `${API_PATH}/option`, method: "put", data: params, }); @@ -67,7 +69,7 @@ const JobAPI = { // 获取定时任务运行日志(实时状态) getJobRunLog() { return request>({ - url: `/monitor/job/log`, + url: `${API_PATH}/log`, method: "get", }); }, @@ -75,7 +77,7 @@ const JobAPI = { // 获取定时任务日志详情 getJobLogDetail(id: number) { return request>({ - url: `/monitor/job/log/detail/${id}`, + url: `${API_PATH}/log/detail/${id}`, method: "get", }); }, @@ -83,7 +85,7 @@ const JobAPI = { // 查询定时任务日志列表 getJobLogList(query: JobLogPageQuery) { return request>>({ - url: `/monitor/job/log/list`, + url: `${API_PATH}/log/list`, method: "get", params: query, }); @@ -92,7 +94,7 @@ const JobAPI = { // 删除定时任务日志 deleteJobLog(ids: number[]) { return request({ - url: `/monitor/job/log/delete`, + url: `${API_PATH}/log/delete`, method: "delete", data: ids, }); @@ -101,7 +103,7 @@ const JobAPI = { // 清空定时任务日志 clearJobLog() { return request({ - url: `/monitor/job/log/clear`, + url: `${API_PATH}/log/clear`, method: "delete", }); }, @@ -109,7 +111,7 @@ const JobAPI = { // 导出定时任务日志 exportJobLog(query: JobLogPageQuery) { return request({ - url: `/monitor/job/log/export`, + url: `${API_PATH}/log/export`, method: "post", data: query, responseType: "blob", diff --git a/frontend/src/api/monitor/online.ts b/frontend/src/api/monitor/online.ts index 07b486e8..8efdfdae 100644 --- a/frontend/src/api/monitor/online.ts +++ b/frontend/src/api/monitor/online.ts @@ -1,10 +1,12 @@ import request from "@/utils/request"; +const API_PATH = "/monitor/online"; + const OnlineAPI = { // 查询在线用户列表 getOnlineList(query: OnlineUserPageQuery) { return request>>({ - url: `/monitor/online/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -13,7 +15,7 @@ const OnlineAPI = { // 强退用户 deleteOnline(body: string) { return request({ - url: `/monitor/online/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -22,7 +24,7 @@ const OnlineAPI = { // 强退用户 clearOnline() { return request({ - url: `/monitor/online/clear`, + url: `${API_PATH}/clear`, method: "delete", }); }, diff --git a/frontend/src/api/monitor/resource.ts b/frontend/src/api/monitor/resource.ts index aebf6050..2e98fe8b 100644 --- a/frontend/src/api/monitor/resource.ts +++ b/frontend/src/api/monitor/resource.ts @@ -1,5 +1,7 @@ import request from "@/utils/request"; +const API_PATH = "/monitor/resource"; + export const ResourceAPI = { /** * 获取目录列表 @@ -7,7 +9,7 @@ export const ResourceAPI = { */ getResourceList(query: ResourcePageQuery) { return request>>({ - url: `/monitor/resource/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -20,7 +22,7 @@ export const ResourceAPI = { */ uploadFile(formData: FormData) { return request>({ - url: `/monitor/resource/upload`, + url: `${API_PATH}/upload`, method: "post", data: formData, headers: { "Content-Type": "multipart/form-data" }, @@ -33,7 +35,7 @@ export const ResourceAPI = { */ downloadFile(path: string) { return request({ - url: `/monitor/resource/download`, + url: `${API_PATH}/download`, method: "get", params: { path }, responseType: "blob", @@ -46,7 +48,7 @@ export const ResourceAPI = { */ deleteResource(body: string[]) { return request({ - url: `/monitor/resource/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -58,7 +60,7 @@ export const ResourceAPI = { */ moveResource(body: ResourceMoveQuery) { return request({ - url: `/monitor/resource/move`, + url: `${API_PATH}/move`, method: "post", data: body, }); @@ -70,7 +72,7 @@ export const ResourceAPI = { */ copyResource(body: ResourceCopyQuery) { return request({ - url: `/monitor/resource/copy`, + url: `${API_PATH}/copy`, method: "post", data: body, }); @@ -82,7 +84,7 @@ export const ResourceAPI = { */ renameResource(body: ResourceRenameQuery) { return request({ - url: `/monitor/resource/rename`, + url: `${API_PATH}/rename`, method: "post", data: body, }); @@ -94,7 +96,7 @@ export const ResourceAPI = { */ createDirectory(body: ResourceCreateDirQuery) { return request({ - url: `/monitor/resource/create-dir`, + url: `${API_PATH}/create-dir`, method: "post", data: body, }); @@ -106,7 +108,7 @@ export const ResourceAPI = { */ exportResource(body: ResourcePageQuery) { return request({ - url: `/monitor/resource/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/monitor/server.ts b/frontend/src/api/monitor/server.ts index 61011302..c8ba6b82 100644 --- a/frontend/src/api/monitor/server.ts +++ b/frontend/src/api/monitor/server.ts @@ -1,10 +1,12 @@ import request from "@/utils/request"; +const API_PATH = "/monitor/server"; + const ServerAPI = { // 获取服务信息 getServer() { return request({ - url: `/monitor/server/info`, + url: `${API_PATH}/info`, method: "get", }); }, diff --git a/frontend/src/api/system/auth.ts b/frontend/src/api/system/auth.ts index e237746c..3e5cf164 100644 --- a/frontend/src/api/system/auth.ts +++ b/frontend/src/api/system/auth.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/auth"; + const AuthAPI = { login(body: LoginFormData) { return request>({ - url: `/system/auth/login`, + url: `${API_PATH}/login`, method: "post", headers: { "Content-Type": "multipart/form-data", @@ -14,7 +16,7 @@ const AuthAPI = { refreshToken(body: RefreshToekenBody) { return request>({ - url: `/system/auth/token/refresh`, + url: `${API_PATH}/token/refresh`, method: "post", data: body, }); @@ -22,14 +24,14 @@ const AuthAPI = { getCaptcha() { return request>({ - url: `/system/auth/captcha/get`, + url: `${API_PATH}/captcha/get`, method: "get", }); }, logout(body: LogoutBody) { return request({ - url: `/system/auth/logout`, + url: `${API_PATH}/logout`, method: "post", data: body, }); diff --git a/frontend/src/api/system/dept.ts b/frontend/src/api/system/dept.ts index a09a2906..89c7c4d4 100644 --- a/frontend/src/api/system/dept.ts +++ b/frontend/src/api/system/dept.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/dept"; + const DeptAPI = { getDeptList(query?: DeptPageQuery) { return request>({ - url: `/system/dept/tree`, + url: `${API_PATH}/tree`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const DeptAPI = { getDeptDetail(query: number) { return request>({ - url: `/system/dept/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createDept(body: DeptForm) { return request({ - url: `/system/dept/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const DeptAPI = { updateDept(id: number, body: DeptForm) { return request({ - url: `/system/dept/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const DeptAPI = { deleteDept(body: number[]) { return request({ - url: `/system/dept/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const DeptAPI = { batchAvailableDept(body: BatchType) { return request({ - url: `/system/dept/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); diff --git a/frontend/src/api/system/dict.ts b/frontend/src/api/system/dict.ts index 5e76e87e..9a2f44ef 100644 --- a/frontend/src/api/system/dict.ts +++ b/frontend/src/api/system/dict.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/dict"; + const DictAPI = { getDictTypeList(query: DictPageQuery) { return request>>({ - url: `/system/dict/type/list`, + url: `${API_PATH}/type/list`, method: "get", params: query, }); @@ -11,21 +13,21 @@ const DictAPI = { getDictTypeOptionselect() { return request({ - url: `/system/dict/type/optionselect`, + url: `${API_PATH}/type/optionselect`, method: "get", }); }, getDictTypeDetail(query: number) { return request>({ - url: `/system/dict/type/detail/${query}`, + url: `${API_PATH}/type/detail/${query}`, method: "get", }); }, createDictType(body: DictForm) { return request({ - url: `/system/dict/type/create`, + url: `${API_PATH}/type/create`, method: "post", data: body, }); @@ -33,7 +35,7 @@ const DictAPI = { updateDictType(id: number, body: DictForm) { return request({ - url: `/system/dict/type/update/${id}`, + url: `${API_PATH}/type/update/${id}`, method: "put", data: body, }); @@ -41,7 +43,7 @@ const DictAPI = { deleteDictType(body: number[]) { return request({ - url: `/system/dict/type/delete`, + url: `${API_PATH}/type/delete`, method: "delete", data: body, }); @@ -49,7 +51,7 @@ const DictAPI = { batchAvailableDict(body: BatchType) { return request({ - url: `/system/dict/type/available/setting`, + url: `${API_PATH}/type/available/setting`, method: "patch", data: body, }); @@ -57,7 +59,7 @@ const DictAPI = { exportDictType(body: DictPageQuery) { return request({ - url: `/system/dict/type/export`, + url: `${API_PATH}/type/export`, method: "post", data: body, responseType: "blob", @@ -66,7 +68,7 @@ const DictAPI = { getDictDataList(query: DictDataPageQuery) { return request>>({ - url: `/system/dict/data/list`, + url: `${API_PATH}/data/list`, method: "get", params: query, }); @@ -74,14 +76,14 @@ const DictAPI = { getDictDataDetail(query: number) { return request>({ - url: `/system/dict/data/detail/${query}`, + url: `${API_PATH}/data/detail/${query}`, method: "get", }); }, createDictData(body: DictDataForm) { return request({ - url: `/system/dict/data/create`, + url: `${API_PATH}/data/create`, method: "post", data: body, }); @@ -89,7 +91,7 @@ const DictAPI = { updateDictData(id: number, body: DictDataForm) { return request({ - url: `/system/dict/data/update/${id}`, + url: `${API_PATH}/data/update/${id}`, method: "put", data: body, }); @@ -97,7 +99,7 @@ const DictAPI = { deleteDictData(body: number[]) { return request({ - url: `/system/dict/data/delete`, + url: `${API_PATH}/data/delete`, method: "delete", data: body, }); @@ -105,7 +107,7 @@ const DictAPI = { batchAvailableDictData(body: BatchType) { return request({ - url: `/system/dict/data/available/setting`, + url: `${API_PATH}/data/available/setting`, method: "patch", data: body, }); @@ -113,7 +115,7 @@ const DictAPI = { exportDictData(body: DictDataPageQuery) { return request({ - url: `/system/dict/data/export`, + url: `${API_PATH}/data/export`, method: "post", data: body, responseType: "blob", @@ -122,7 +124,7 @@ const DictAPI = { getInitDict(dict_type: string) { return request({ - url: `/system/dict/data/info/${dict_type}`, + url: `${API_PATH}/data/info/${dict_type}`, method: "get", }); }, diff --git a/frontend/src/api/system/log.ts b/frontend/src/api/system/log.ts index 46d4c445..5e3137b2 100644 --- a/frontend/src/api/system/log.ts +++ b/frontend/src/api/system/log.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/log"; + const LogAPI = { getLogList(query: LogPageQuery) { return request>>({ - url: `/system/log/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const LogAPI = { getLogDetail(query: number) { return request>({ - url: `/system/log/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, deleteLog(body: number[]) { return request({ - url: `/system/log/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -26,7 +28,7 @@ const LogAPI = { exportLog(body: LogPageQuery) { return request({ - url: `/system/log/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/system/menu.ts b/frontend/src/api/system/menu.ts index 130ff67d..e7e8f30c 100644 --- a/frontend/src/api/system/menu.ts +++ b/frontend/src/api/system/menu.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/menu"; + const MenuAPI = { getMenuList(query?: MenuPageQuery) { return request>({ - url: `/system/menu/tree`, + url: `${API_PATH}/tree`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const MenuAPI = { getMenuDetail(query: number) { return request>({ - url: `/system/menu/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createMenu(body: MenuForm) { return request({ - url: `/system/menu/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const MenuAPI = { updateMenu(id: number, body: MenuForm) { return request({ - url: `/system/menu/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const MenuAPI = { deleteMenu(body: number[]) { return request({ - url: `/system/menu/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const MenuAPI = { batchAvailableMenu(body: BatchType) { return request({ - url: `/system/menu/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); diff --git a/frontend/src/api/system/notice.ts b/frontend/src/api/system/notice.ts index 91eb2919..29d7af56 100644 --- a/frontend/src/api/system/notice.ts +++ b/frontend/src/api/system/notice.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/notice"; + const NoticeAPI = { getNoticeList(query: NoticePageQuery) { return request>>({ - url: `/system/notice/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,21 +13,21 @@ const NoticeAPI = { getNoticeListAvailable() { return request>>({ - url: `/system/notice/available`, + url: `${API_PATH}/available`, method: "get", }); }, getNoticeDetail(query: number) { return request>({ - url: `/system/notice/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createNotice(body: NoticeForm) { return request({ - url: `/system/notice/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -33,7 +35,7 @@ const NoticeAPI = { updateNotice(id: number, body: NoticeForm) { return request({ - url: `/system/notice/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -41,7 +43,7 @@ const NoticeAPI = { deleteNotice(body: number[]) { return request({ - url: `/system/notice/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -49,7 +51,7 @@ const NoticeAPI = { batchAvailableNotice(body: BatchType) { return request({ - url: `/system/notice/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); @@ -57,7 +59,7 @@ const NoticeAPI = { exportNotice(body: NoticePageQuery) { return request({ - url: `/system/notice/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/system/params.ts b/frontend/src/api/system/params.ts index faa1034c..88cab420 100644 --- a/frontend/src/api/system/params.ts +++ b/frontend/src/api/system/params.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/param"; + const ParamsAPI = { uploadFile(body: any) { return request>({ - url: `/system/param/upload`, + url: `${API_PATH}/upload`, method: "post", data: body, headers: { "Content-Type": "multipart/form-data" }, @@ -12,14 +14,14 @@ const ParamsAPI = { getInitConfig() { return request>({ - url: `/system/param/info`, + url: `${API_PATH}/info`, method: "get", }); }, getConfigList(query: ConfigPageQuery) { return request>>({ - url: `/system/param/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -27,14 +29,14 @@ const ParamsAPI = { getConfigDetail(query: number) { return request>({ - url: `/system/param/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createConfig(body: ConfigForm) { return request({ - url: `/system/param/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -42,7 +44,7 @@ const ParamsAPI = { updateConfig(id: number, body: ConfigForm) { return request({ - url: `/system/param/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -50,7 +52,7 @@ const ParamsAPI = { deleteConfig(body: number[]) { return request({ - url: `/system/param/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -58,7 +60,7 @@ const ParamsAPI = { exportConfig(body: ConfigPageQuery) { return request({ - url: `/system/param/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/system/position.ts b/frontend/src/api/system/position.ts index 3a0b94f9..60d24b79 100644 --- a/frontend/src/api/system/position.ts +++ b/frontend/src/api/system/position.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/position"; + const PositionAPI = { getPositionList(query?: PositionPageQuery) { return request>>({ - url: `/system/position/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const PositionAPI = { getPositionDetail(query: number) { return request>({ - url: `/system/position/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createPosition(body: PositionForm) { return request({ - url: `/system/position/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const PositionAPI = { updatePosition(id: number, body: PositionForm) { return request({ - url: `/system/position/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const PositionAPI = { deletePosition(body: number[]) { return request({ - url: `/system/position/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const PositionAPI = { batchAvailablePosition(body: BatchType) { return request({ - url: `/system/position/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); @@ -50,7 +52,7 @@ const PositionAPI = { exportPosition(body: PositionPageQuery) { return request({ - url: `/system/position/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/system/role.ts b/frontend/src/api/system/role.ts index 234e31d6..6784121e 100644 --- a/frontend/src/api/system/role.ts +++ b/frontend/src/api/system/role.ts @@ -1,9 +1,11 @@ import request from "@/utils/request"; +const API_PATH = "/system/role"; + const RoleAPI = { getRoleList(query?: TablePageQuery) { return request>>({ - url: `/system/role/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -11,14 +13,14 @@ const RoleAPI = { getRoleDetail(query: number) { return request>({ - url: `/system/role/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createRole(body: RoleForm) { return request({ - url: `/system/role/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -26,7 +28,7 @@ const RoleAPI = { updateRole(id: number, body: RoleForm) { return request({ - url: `/system/role/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -34,7 +36,7 @@ const RoleAPI = { deleteRole(body: number[]) { return request({ - url: `/system/role/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -42,7 +44,7 @@ const RoleAPI = { batchAvailableRole(body: BatchType) { return request({ - url: `/system/role/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); @@ -50,7 +52,7 @@ const RoleAPI = { setPermission(body: permissionDataType) { return request({ - url: `/system/role/permission/setting`, + url: `${API_PATH}/permission/setting`, method: "patch", data: body, }); @@ -58,7 +60,7 @@ const RoleAPI = { exportRole(body: TablePageQuery) { return request({ - url: `/system/role/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", diff --git a/frontend/src/api/system/user.ts b/frontend/src/api/system/user.ts index 19471e9e..52841990 100644 --- a/frontend/src/api/system/user.ts +++ b/frontend/src/api/system/user.ts @@ -1,17 +1,19 @@ import request from "@/utils/request"; import { MenuTable, MenuForm } from "@/api/system/menu"; +const API_PATH = "/system/user"; + export const UserAPI = { getCurrentUserInfo() { return request>({ - url: `/system/user/current/info`, + url: `${API_PATH}/current/info`, method: "get", }); }, uploadCurrentUserAvatar(body: any) { return request>({ - url: `/system/user/current/avatar/upload`, + url: `${API_PATH}/current/avatar/upload`, method: "post", data: body, headers: { "Content-Type": "multipart/form-data" }, @@ -20,7 +22,7 @@ export const UserAPI = { updateCurrentUserInfo(body: InfoFormState) { return request>({ - url: `/system/user/current/info/update`, + url: `${API_PATH}/current/info/update`, method: "put", data: body, }); @@ -28,7 +30,7 @@ export const UserAPI = { changeCurrentUserPassword(body: PasswordFormState) { return request({ - url: `/system/user/current/password/change`, + url: `${API_PATH}/current/password/change`, method: "put", data: body, }); @@ -36,7 +38,7 @@ export const UserAPI = { resetUserPassword(body: ResetPasswordForm) { return request({ - url: `/system/user/reset/password`, + url: `${API_PATH}/reset/password`, method: "put", data: body, }); @@ -44,7 +46,7 @@ export const UserAPI = { registerUser(body: RegisterForm) { return request({ - url: `/system/user/register`, + url: `${API_PATH}/register`, method: "post", data: body, }); @@ -52,7 +54,7 @@ export const UserAPI = { forgetPassword(body: ForgetPasswordForm) { return request({ - url: `/system/user/forget/password`, + url: `${API_PATH}/forget/password`, method: "post", data: body, }); @@ -60,7 +62,7 @@ export const UserAPI = { getUserList(query: UserPageQuery) { return request>>({ - url: `/system/user/list`, + url: `${API_PATH}/list`, method: "get", params: query, }); @@ -68,14 +70,14 @@ export const UserAPI = { getUserDetail(query: number) { return request>({ - url: `/system/user/detail/${query}`, + url: `${API_PATH}/detail/${query}`, method: "get", }); }, createUser(body: UserForm) { return request({ - url: `/system/user/create`, + url: `${API_PATH}/create`, method: "post", data: body, }); @@ -83,7 +85,7 @@ export const UserAPI = { updateUser(id: number, body: UserForm) { return request({ - url: `/system/user/update/${id}`, + url: `${API_PATH}/update/${id}`, method: "put", data: body, }); @@ -91,7 +93,7 @@ export const UserAPI = { deleteUser(body: number[]) { return request({ - url: `/system/user/delete`, + url: `${API_PATH}/delete`, method: "delete", data: body, }); @@ -99,7 +101,7 @@ export const UserAPI = { batchAvailableUser(body: BatchType) { return request({ - url: `/system/user/available/setting`, + url: `${API_PATH}/available/setting`, method: "patch", data: body, }); @@ -107,7 +109,7 @@ export const UserAPI = { exportUser(body: UserPageQuery) { return request({ - url: `/system/user/export`, + url: `${API_PATH}/export`, method: "post", data: body, responseType: "blob", @@ -116,7 +118,7 @@ export const UserAPI = { downloadTemplate() { return request({ - url: `/system/user/import/template`, + url: `${API_PATH}/import/template`, method: "post", responseType: "blob", }); @@ -124,7 +126,7 @@ export const UserAPI = { importUser(body: any) { return request({ - url: `/system/user/import/data`, + url: `${API_PATH}/import/data`, method: "post", data: body, headers: { diff --git a/frontend/src/views/dashboard/workplace.vue b/frontend/src/views/dashboard/workplace.vue index c38822ef..d004b6c1 100644 --- a/frontend/src/views/dashboard/workplace.vue +++ b/frontend/src/views/dashboard/workplace.vue @@ -67,7 +67,7 @@ @@ -87,7 +87,7 @@
{{ item.creator?.name }} 发布 - 详情↗ + 详情↗
@@ -128,13 +128,13 @@ - + {{ t('common.clear') }} - + @@ -153,11 +154,12 @@
{{ item.title }} - - + @@ -183,7 +185,7 @@ import NoticeAPI, { NoticeTable } from '@/api/system/notice'; import { ref, onMounted, reactive } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import { QuestionFilled, Close, CircleCloseFilled } from "@element-plus/icons-vue"; +import { QuestionFilled, Delete, CircleClose } from "@element-plus/icons-vue"; import { ElMessage, ElMessageBox } from 'element-plus'; import { quickStartManager, type QuickLink } from '@/utils/quickStartManager'; @@ -215,6 +217,13 @@ const formatTime = (time: string | undefined) => { return date.toLocaleDateString(); }; +// 跳转通知公告详情页 +const goToNotice = () => { + router.push({ name: 'Notice' }).catch(() => { + ElMessage.warning(`公告通知跳转失败,请检查路由配置`); + }); +}; + // 获取通知类型文本和颜色 const getNoticeTypeText = (type: string | undefined) => { switch (type) { diff --git a/frontend/src/views/gencode/backcode/index.vue b/frontend/src/views/gencode/backcode/index.vue index 664c82fb..231fc417 100644 --- a/frontend/src/views/gencode/backcode/index.vue +++ b/frontend/src/views/gencode/backcode/index.vue @@ -1,39 +1,81 @@