diff --git a/backend/app/api/v1/module_application/ai/controller.py b/backend/app/api/v1/module_application/ai/controller.py index f7712a35..6d1eaec5 100644 --- a/backend/app/api/v1/module_application/ai/controller.py +++ b/backend/app/api/v1/module_application/ai/controller.py @@ -7,10 +7,10 @@ from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.logger import log from app.api.v1.module_system.auth.schema import AuthSchema +from app.core.router_class import OperationLogRoute from .service import McpService from .schema import McpCreateSchema, McpUpdateSchema, ChatQuerySchema, McpQueryParam diff --git a/backend/app/api/v1/module_application/ai/crud.py b/backend/app/api/v1/module_application/ai/crud.py index 9e456139..db0e1f33 100644 --- a/backend/app/api/v1/module_application/ai/crud.py +++ b/backend/app/api/v1/module_application/ai/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence, Any from app.core.base_crud import CRUDBase @@ -22,47 +22,47 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]): self.auth = auth super().__init__(model=McpModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[McpModel]: + async def get_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> McpModel | None: """ 获取MCP服务器详情 参数: - id (int): MCP服务器ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[McpModel]: MCP服务器模型实例(如果存在) + - McpModel | None: MCP服务器模型实例(如果存在) """ return await self.get(id=id, preload=preload) - async def get_by_name_crud(self, name: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[McpModel]: + async def get_by_name_crud(self, name: str, preload: list[str | Any] | None = None) -> McpModel | None: """ 通过名称获取MCP服务器 参数: - name (str): MCP服务器名称 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Optional[McpModel]: MCP服务器模型实例(如果存在) """ return await self.get(name=name, preload=preload) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[McpModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[McpModel]: """ 列表查询MCP服务器 参数: - - search (Optional[Dict]): 查询参数字典 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数字典 + - order_by (list[dict[str, str]] | None): 排序参数列表 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[McpModel]: MCP服务器模型实例序列 """ return await self.list(search=search or {}, order_by=order_by or [{'id': 'asc'}], preload=preload) - async def create_crud(self, data: McpCreateSchema) -> Optional[McpModel]: + async def create_crud(self, data: McpCreateSchema) -> McpModel | None: """ 创建MCP服务器 @@ -74,7 +74,7 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]): """ return await self.create(data=data) - async def update_crud(self, id: int, data: McpUpdateSchema) -> Optional[McpModel]: + async def update_crud(self, id: int, data: McpUpdateSchema) -> McpModel | None: """ 更新MCP服务器 @@ -83,16 +83,16 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]): - data (McpUpdateSchema): 更新MCP服务器模型 返回: - - Optional[McpModel]: 更新的MCP服务器模型实例(如果成功) + - McpModel | None: 更新的MCP服务器模型实例(如果成功) """ return await self.update(id=id, data=data) - async def delete_crud(self, ids: List[int]) -> None: + async def delete_crud(self, ids: list[int]) -> None: """ 批量删除MCP服务器 参数: - - ids (List[int]): MCP服务器ID列表 + - ids (list[int]): MCP服务器ID列表 返回: - None diff --git a/backend/app/api/v1/module_application/ai/model.py b/backend/app/api/v1/module_application/ai/model.py index 3b65a75f..262f3aff 100644 --- a/backend/app/api/v1/module_application/ai/model.py +++ b/backend/app/api/v1/module_application/ai/model.py @@ -3,26 +3,19 @@ from sqlalchemy import JSON, String, Integer from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class McpModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class McpModel(ModelMixin, UserMixin): """ MCP 服务器表 - - 数据隔离策略: - =========== - - 系统级MCP: tenant_id=1, customer_id=NULL (平台预置MCP,所有租户可用) - - 租户级MCP: tenant_id>1, customer_id=NULL (租户自定义MCP,仅本租户可用) - - 客户级MCP: tenant_id>1, customer_id>0 (客户专属MCP,仅该客户可用) - MCP类型: - 0: stdio (标准输入输出) - 1: sse (Server-Sent Events) """ __tablename__: str = 'app_ai_mcp' __table_args__: dict[str, str] = ({'comment': 'MCP 服务器表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] name: Mapped[str] = mapped_column(String(50), comment='MCP 名称') type: Mapped[int] = mapped_column(Integer, default=0, comment='MCP 类型(0:stdio 1:sse)') diff --git a/backend/app/api/v1/module_application/ai/schema.py b/backend/app/api/v1/module_application/ai/schema.py index bf0a292c..5e68f8b2 100644 --- a/backend/app/api/v1/module_application/ai/schema.py +++ b/backend/app/api/v1/module_application/ai/schema.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from typing import Optional, Dict, Any, List from pydantic import ConfigDict, Field, HttpUrl, BaseModel from fastapi import Query from app.core.base_schema import BaseSchema from app.common.enums import McpLLMProvider -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema from app.common.enums import McpType +from app.core.validator import DateTimeStr class ChatQuerySchema(BaseModel): @@ -17,13 +17,13 @@ class ChatQuerySchema(BaseModel): class McpCreateSchema(BaseModel): """创建 MCP 服务器参数""" - name: str = Field(..., max_length=50, description='MCP 名称') + name: str = Field(..., max_length=64, description='MCP 名称') type: McpType = Field(McpType.stdio, description='MCP 类型') - description: Optional[str] = Field(None, max_length=255, description='MCP 描述') - url: Optional[HttpUrl] = Field(None, description='远程 SSE 地址') - command: Optional[str] = Field(None, max_length=255, description='MCP 命令') - args: Optional[str] = Field(None, max_length=255, description='MCP 命令参数,多个参数用英文逗号隔开') - env: Optional[Dict[str, Any]] = Field(None, description='MCP 环境变量') + description: str | None = Field(None, max_length=255, description='MCP 描述') + url: HttpUrl | None = Field(None, description='远程 SSE 地址') + command: str | None = Field(None, max_length=255, description='MCP 命令') + args: str | None = Field(None, max_length=255, description='MCP 命令参数,多个参数用英文逗号隔开') + env: dict[str, str] | None = Field(None, description='MCP 环境变量') class McpUpdateSchema(McpCreateSchema): @@ -31,7 +31,7 @@ class McpUpdateSchema(McpCreateSchema): ... -class McpOutSchema(McpCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class McpOutSchema(McpCreateSchema, BaseSchema, UserBySchema): """MCP 服务器详情""" model_config = ConfigDict(from_attributes=True) @@ -41,8 +41,12 @@ class McpQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="MCP 名称"), - type: Optional[int] = Query(None, description="MCP 类型"), + name: str | None = Query(None, description="MCP 名称"), + type: McpType | None = Query(None, description="MCP 类型"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -50,13 +54,21 @@ class McpQueryParam: # 精确查询字段 self.type = type + self.created_id = created_id + self.updated_id = updated_id + + # 时间范围查询 + if created_time and len(created_time) == 2: + self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) class McpChatParam(BaseSchema): """MCP 聊天参数""" - pk: List[int] = Field(..., description='MCP ID 列表') + pk: list[int] = Field(..., description='MCP ID 列表') provider: McpLLMProvider = Field(McpLLMProvider.openai, description='LLM 供应商') model: str = Field(..., description='LLM 名称') key: str = Field(..., description='LLM API Key') - base_url: Optional[str] = Field(None, description='自定义 LLM API 地址,必须兼容 openai 供应商') + base_url: str | None = Field(None, description='自定义 LLM API 地址,必须兼容 openai 供应商') prompt: str = Field(..., description='用户提示词') \ No newline at end of file diff --git a/backend/app/api/v1/module_application/ai/service.py b/backend/app/api/v1/module_application/ai/service.py index e27da514..73b5bff8 100644 --- a/backend/app/api/v1/module_application/ai/service.py +++ b/backend/app/api/v1/module_application/ai/service.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import List, Dict, Optional, Any +from typing import Any, AsyncGenerator from app.core.exceptions import CustomException from app.api.v1.module_system.auth.schema import AuthSchema @@ -13,7 +13,7 @@ class McpService: """MCP服务层""" @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> Dict[str, Any]: + async def detail_service(cls, auth: AuthSchema, id: int) -> dict[str, Any]: """ 获取MCP服务器详情 @@ -22,7 +22,7 @@ class McpService: - id (int): MCP服务器ID 返回: - - Dict[str, Any]: MCP服务器详情字典 + - dict[str, Any]: MCP服务器详情字典 """ obj = await McpCRUD(auth).get_by_id_crud(id=id) if not obj: @@ -30,24 +30,24 @@ class McpService: return McpOutSchema.model_validate(obj).model_dump() @classmethod - async def list_service(cls, auth: AuthSchema, search: Optional[McpQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict[str, Any]]: + async def list_service(cls, auth: AuthSchema, search: McpQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict[str, Any]]: """ 列表查询MCP服务器 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[McpQueryParam]): 查询参数模型 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 + - search (McpQueryParam | None): 查询参数模型 + - order_by (list[dict[str, str]] | None): 排序参数列表 返回: - - List[Dict[str, Any]]: MCP服务器详情字典列表 + - list[dict[str, Any]]: MCP服务器详情字典列表 """ search_dict = search.__dict__ if search else None obj_list = await McpCRUD(auth).get_list_crud(search=search_dict, order_by=order_by) return [McpOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def create_service(cls, auth: AuthSchema, data: McpCreateSchema) -> Dict[str, Any]: + async def create_service(cls, auth: AuthSchema, data: McpCreateSchema) -> dict[str, Any]: """ 创建MCP服务器 @@ -56,7 +56,7 @@ class McpService: - data (McpCreateSchema): 创建MCP服务器模型 返回: - - Dict[str, Any]: 创建的MCP服务器详情字典 + - dict[str, Any]: 创建的MCP服务器详情字典 """ obj = await McpCRUD(auth).get_by_name_crud(name=data.name) if obj: @@ -65,7 +65,7 @@ class McpService: return McpOutSchema.model_validate(obj).model_dump() @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: McpUpdateSchema) -> Dict[str, Any]: + async def update_service(cls, auth: AuthSchema, id: int, data: McpUpdateSchema) -> dict[str, Any]: """ 更新MCP服务器 @@ -75,7 +75,7 @@ class McpService: - data (McpUpdateSchema): 更新MCP服务器模型 返回: - - Dict[str, Any]: 更新的MCP服务器详情字典 + - dict[str, Any]: 更新的MCP服务器详情字典 """ obj = await McpCRUD(auth).get_by_id_crud(id=id) if not obj: @@ -87,13 +87,13 @@ class McpService: return McpOutSchema.model_validate(obj).model_dump() @classmethod - async def delete_service(cls, auth: AuthSchema, ids: List[int]) -> None: + async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None: """ 批量删除MCP服务器 参数: - auth (AuthSchema): 认证信息模型 - - ids (List[int]): MCP服务器ID列表 + - ids (list[int]): MCP服务器ID列表 返回: - None @@ -107,7 +107,7 @@ class McpService: await McpCRUD(auth).delete_crud(ids=ids) @classmethod - async def chat_query(cls, query: ChatQuerySchema): + async def chat_query(cls, query: ChatQuerySchema) -> AsyncGenerator[str, Any]: """ 处理聊天查询 diff --git a/backend/app/api/v1/module_application/ai/tools/ai_util.py b/backend/app/api/v1/module_application/ai/tools/ai_util.py index af9c2f20..fc318d64 100644 --- a/backend/app/api/v1/module_application/ai/tools/ai_util.py +++ b/backend/app/api/v1/module_application/ai/tools/ai_util.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, AsyncGenerator +from typing import AsyncGenerator from openai import AsyncOpenAI, OpenAI from openai.types.chat.chat_completion import ChatCompletion import httpx diff --git a/backend/app/api/v1/module_application/job/controller.py b/backend/app/api/v1/module_application/job/controller.py index bd76a78e..33f71098 100644 --- a/backend/app/api/v1/module_application/job/controller.py +++ b/backend/app/api/v1/module_application/job/controller.py @@ -5,10 +5,10 @@ from fastapi.responses import JSONResponse, StreamingResponse from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.logger import log from app.api.v1.module_system.auth.schema import AuthSchema @@ -211,7 +211,7 @@ async def get_job_log_controller(): "coalesce": i.coalesce, "max_instances": i.max_instances, "next_run_time": i.next_run_time, - "state": SchedulerUtil.get_single_job_status(job_id=i.id, tenant_id=None) + "state": SchedulerUtil.get_single_job_status(job_id=i.id) } for i in SchedulerUtil.get_all_jobs() ] diff --git a/backend/app/api/v1/module_application/job/crud.py b/backend/app/api/v1/module_application/job/crud.py index ce288053..cc1cca71 100644 --- a/backend/app/api/v1/module_application/job/crud.py +++ b/backend/app/api/v1/module_application/job/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence, Any from app.core.base_crud import CRUDBase @@ -22,34 +22,34 @@ class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]): self.auth = auth super().__init__(model=JobModel, auth=auth) - async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[JobModel]: + async def get_obj_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> JobModel | None: """ 获取定时任务详情 参数: - id (int): 定时任务ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[JobModel]: 定时任务模型,如果不存在则为None + - JobModel | None: 定时任务模型,如果不存在则为None """ return await self.get(id=id, preload=preload) - async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[JobModel]: + async def get_obj_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[JobModel]: """ 获取定时任务列表 参数: - - search (Optional[Dict]): 查询参数字典 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数字典 + - order_by (list[dict[str, str]] | None): 排序参数列表 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[JobModel]: 定时任务模型序列 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_obj_crud(self, data: JobCreateSchema) -> Optional[JobModel]: + async def create_obj_crud(self, data: JobCreateSchema) -> JobModel | None: """ 创建定时任务 @@ -57,11 +57,11 @@ class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]): - data (JobCreateSchema): 创建定时任务模型 返回: - - Optional[JobModel]: 创建的定时任务模型,如果创建失败则为None + - JobModel | None: 创建的定时任务模型,如果创建失败则为None """ return await self.create(data=data) - async def update_obj_crud(self, id: int, data: JobUpdateSchema) -> Optional[JobModel]: + async def update_obj_crud(self, id: int, data: JobUpdateSchema) -> JobModel | None: """ 更新定时任务 @@ -70,25 +70,25 @@ class JobCRUD(CRUDBase[JobModel, JobCreateSchema, JobUpdateSchema]): - data (JobUpdateSchema): 更新定时任务模型 返回: - - Optional[JobModel]: 更新后的定时任务模型,如果更新失败则为None + - JobModel | None: 更新后的定时任务模型,如果更新失败则为None """ return await self.update(id=id, data=data) - async def delete_obj_crud(self, ids: List[int]) -> None: + async def delete_obj_crud(self, ids: list[int]) -> None: """ 删除定时任务 参数: - - ids (List[int]): 定时任务ID列表 + - ids (list[int]): 定时任务ID列表 """ return await self.delete(ids=ids) - async def set_obj_field_crud(self, ids: List[int], **kwargs) -> None: + async def set_obj_field_crud(self, ids: list[int], **kwargs) -> None: """ 设置定时任务的可用状态 参数: - - ids (List[int]): 定时任务ID列表 + - ids (list[int]): 定时任务ID列表 - kwargs: 其他要设置的字段,例如 available=True 或 available=False """ return await self.set(ids=ids, **kwargs) @@ -116,39 +116,39 @@ class JobLogCRUD(CRUDBase[JobLogModel, JobLogCreateSchema, JobLogUpdateSchema]): self.auth = auth super().__init__(model=JobLogModel, auth=auth) - async def get_obj_log_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[JobLogModel]: + async def get_obj_log_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> JobLogModel | None: """ 获取定时任务日志详情 参数: - id (int): 定时任务日志ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[JobLogModel]: 定时任务日志模型,如果不存在则为None + - JobLogModel | None: 定时任务日志模型,如果不存在则为None """ return await self.get(id=id, preload=preload) - async def get_obj_log_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[JobLogModel]: + async def get_obj_log_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[JobLogModel]: """ 获取定时任务日志列表 参数: - - search (Optional[Dict]): 查询参数字典 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数字典 + - order_by (list[dict[str, str]] | None): 排序参数列表 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[JobLogModel]: 定时任务日志模型序列 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def delete_obj_log_crud(self, ids: List[int]) -> None: + async def delete_obj_log_crud(self, ids: list[int]) -> None: """ 删除定时任务日志 参数: - - ids (List[int]): 定时任务日志ID列表 + - ids (list[int]): 定时任务日志ID列表 """ return await self.delete(ids=ids) diff --git a/backend/app/api/v1/module_application/job/model.py b/backend/app/api/v1/module_application/job/model.py index 9b7eab15..aa473ea4 100644 --- a/backend/app/api/v1/module_application/job/model.py +++ b/backend/app/api/v1/module_application/job/model.py @@ -3,26 +3,18 @@ from sqlalchemy import Boolean, String, Integer, Text, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class JobModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class JobModel(ModelMixin, UserMixin): """ 定时任务调度表 - - 数据隔离策略: - =========== - - 系统级任务: tenant_id=1, customer_id=NULL (平台定时任务,如系统维护) - - 租户级任务: tenant_id>1, customer_id=NULL (租户定时任务,如数据统计) - - 客户级任务: tenant_id>1, customer_id>0 (客户专属定时任务) - - 任务状态: - 0: 运行中 - 1: 暂停中 """ __tablename__: str = 'app_job' __table_args__: dict[str, str] = ({'comment': '定时任务调度表'}) - __loader_options__: list[str] = ["job_logs", "created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["job_logs", "created_by", "updated_by"] name: Mapped[str | None] = mapped_column(String(64), nullable=True, default='', comment='任务名称') jobstore: Mapped[str | None] = mapped_column(String(64), nullable=True, default='default', comment='存储器') @@ -44,12 +36,9 @@ class JobModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): ) -class JobLogModel(ModelMixin, TenantMixin): +class JobLogModel(ModelMixin): """ 定时任务调度日志表 - - 添加tenant_id字段以支持多租户隔离,提高查询性能 - 即使job记录被删除,日志仍然保留租户标识信息 """ __tablename__: str = 'app_job_log' __table_args__: dict[str, str] = ({'comment': '定时任务调度日志表'}) @@ -73,17 +62,6 @@ class JobLogModel(ModelMixin, TenantMixin): comment='任务ID' ) - # 索引优化 - 为租户ID创建索引 - __table_args__ = ({ - 'comment': '定时任务调度日志表', - }) - - # 为多租户查询性能优化添加复合索引 - # 注意:实际索引会在数据库迁移时创建 - __indexes__ = [ - 'tenant_id_idx', # 租户ID索引 - 'job_id_tenant_id_idx' # 任务ID和租户ID的复合索引 - ] job: Mapped["JobModel | None"] = relationship( back_populates="job_logs", lazy="selectin" diff --git a/backend/app/api/v1/module_application/job/schema.py b/backend/app/api/v1/module_application/job/schema.py index 3573e8e4..faae16d8 100644 --- a/backend/app/api/v1/module_application/job/schema.py +++ b/backend/app/api/v1/module_application/job/schema.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator -from typing import Optional from fastapi import Query -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema from app.core.validator import DateTimeStr, datetime_validator @@ -12,20 +11,20 @@ class JobCreateSchema(BaseModel): """ 定时任务调度表对应pydantic模型 """ - name: Optional[str] = Field(..., max_length=64, description='任务名称') + name: str = Field(..., max_length=64, description='任务名称') func: str = Field(..., description='任务函数') trigger: str = Field(..., description='触发器:控制此作业计划的 trigger 对象') - args: Optional[str] = Field(default=None, description='位置参数') - kwargs: Optional[str] = Field(default=None, description='关键字参数') - coalesce: Optional[bool] = Field(..., description='是否合并运行:是否在多个运行时间到期时仅运行作业一次') - max_instances: Optional[int] = Field(default=1, ge=1, description='最大实例数:允许的最大并发执行实例数') - jobstore: Optional[str] = Field(..., max_length=64, description='任务存储') - executor: Optional[str] = Field(..., max_length=64, description='任务执行器:将运行此作业的执行程序的名称') - trigger_args: Optional[str] = Field(default=None, description='触发器参数') - start_date: Optional[str] = Field(default=None, description='开始时间') - end_date: Optional[str] = Field(default=None, description='结束时间') - description: Optional[str] = Field(default=None, max_length=255, description='描述') - status: Optional[str] = Field(default='0', description='任务状态:启动,停止') + args: str | None = Field(default=None, description='位置参数') + kwargs: str | None = Field(default=None, description='关键字参数') + coalesce: bool | None = Field(..., description='是否合并运行:是否在多个运行时间到期时仅运行作业一次') + max_instances: int | None = Field(default=1, ge=1, description='最大实例数:允许的最大并发执行实例数') + jobstore: str | None = Field(..., max_length=64, description='任务存储') + executor: str | None = Field(..., max_length=64, description='任务执行器:将运行此作业的执行程序的名称') + trigger_args: str | None = Field(default=None, description='触发器参数') + start_date: str | None = Field(default=None, description='开始时间') + end_date: str | None = Field(default=None, description='结束时间') + description: str | None = Field(default=None, max_length=255, description='描述') + status: str = Field(default='0', description='任务状态:启动,停止') @field_validator('trigger') @classmethod @@ -55,7 +54,7 @@ class JobUpdateSchema(JobCreateSchema): ... -class JobOutSchema(JobCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class JobOutSchema(JobCreateSchema, BaseSchema, UserBySchema): """定时任务响应模型""" model_config = ConfigDict(from_attributes=True) ... @@ -68,26 +67,28 @@ class JobLogCreateSchema(BaseModel): model_config = ConfigDict(from_attributes=True) - job_name: Optional[str] = Field(default=None, description='任务名称') - job_group: Optional[str] = Field(default=None, description='任务组名') - job_executor: Optional[str] = Field(default=None, description='任务执行器') - invoke_target: Optional[str] = Field(default=None, description='调用目标字符串') - job_args: Optional[str] = Field(default=None, description='位置参数') - job_kwargs: Optional[str] = Field(default=None, description='关键字参数') - job_trigger: Optional[str] = Field(default=None, description='任务触发器') - job_message: Optional[str] = Field(default=None, description='日志信息') - exception_info: Optional[str] = Field(default=None, description='异常信息') - status: Optional[str] = Field(default='0', description='任务状态:正常,失败') - create_time: Optional[DateTimeStr] = Field(default=None, description='创建时间') + job_name: str = Field(..., description='任务名称') + job_group: str | None = Field(default=None, description='任务组名') + job_executor: str | None = Field(default=None, description='任务执行器') + invoke_target: str | None = Field(default=None, description='调用目标字符串') + job_args: str | None = Field(default=None, description='位置参数') + job_kwargs: str | None = Field(default=None, description='关键字参数') + job_trigger: str | None = Field(default=None, description='任务触发器') + job_message: str | None = Field(default=None, description='日志信息') + exception_info: str | None = Field(default=None, description='异常信息') + status: str = Field(default='0', description='任务状态:正常,失败') + description: str | None = Field(default=None, max_length=255, description='描述') + create_time: DateTimeStr | None = Field(default=None, description='创建时间') + update_time: DateTimeStr | None = Field(default=None, description='更新时间') class JobLogUpdateSchema(JobLogCreateSchema): """定时任务调度日志表更新模型""" ... - id: Optional[int] = Field(default=None, description='任务日志ID') + id: int | None = Field(default=None, description='任务日志ID') -class JobLogOutSchema(JobLogUpdateSchema, BaseSchema, TenantSchema): +class JobLogOutSchema(JobLogUpdateSchema, BaseSchema, UserBySchema): """定时任务调度日志表响应模型""" model_config = ConfigDict(from_attributes=True) ... @@ -98,22 +99,27 @@ class JobQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="任务名称"), - status: Optional[str] = Query(None, description="状态: 启动,停止"), - creator: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="任务名称"), + status: str | None = Query(None, description="状态: 启动,停止"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 self.name = ("like", f"%{name}%") if name else None # 精确查询字段 - self.created_id = creator + self.created_id = created_id + self.updated_id = updated_id self.status = status # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) class JobLogQueryParam: @@ -121,10 +127,11 @@ class JobLogQueryParam: def __init__( self, - job_id: Optional[int] = Query(None, description="定时任务ID"), - job_name: Optional[str] = Query(None, description="任务名称"), - status: Optional[str] = Query(None, description="状态: 正常,失败"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + job_id: int | None = Query(None, description="定时任务ID"), + job_name: str | None = Query(None, description="任务名称"), + status: str | None = Query(None, description="状态: 正常,失败"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: # 定时任务ID查询 self.job_id = job_id @@ -135,3 +142,5 @@ class JobLogQueryParam: # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_application/job/service.py b/backend/app/api/v1/module_application/job/service.py index 44a1a325..c8db7cf3 100644 --- a/backend/app/api/v1/module_application/job/service.py +++ b/backend/app/api/v1/module_application/job/service.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- -from typing import Any, List, Dict, Optional - from app.core.exceptions import CustomException from app.utils.cron_util import CronUtil from app.utils.excel_util import ExcelUtil from app.api.v1.module_system.auth.schema import AuthSchema from .tools.ap_scheduler import SchedulerUtil +from .crud import JobCRUD, JobLogCRUD from .schema import ( JobCreateSchema, JobUpdateSchema, @@ -15,7 +14,6 @@ from .schema import ( JobQueryParam, JobLogQueryParam ) -from .crud import JobCRUD, JobLogCRUD class JobService: @@ -24,7 +22,7 @@ class JobService: """ @classmethod - async def get_job_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_job_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取定时任务详情 @@ -39,14 +37,14 @@ class JobService: return JobOutSchema.model_validate(obj).model_dump() @classmethod - async def get_job_list_service(cls, auth: AuthSchema, search: Optional[JobQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_job_list_service(cls, auth: AuthSchema, search: JobQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]: """ 获取定时任务列表 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[JobQueryParam]): 查询参数模型 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 + - search (JobQueryParam | None): 查询参数模型 + - order_by (list[dict[str, str]] | None): 排序参数列表 返回: - List[Dict]: 定时任务详情字典列表 @@ -55,7 +53,7 @@ class JobService: return [JobOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def create_job_service(cls, auth: AuthSchema, data: JobCreateSchema) -> Dict: + async def create_job_service(cls, auth: AuthSchema, data: JobCreateSchema) -> dict: """ 创建定时任务 @@ -77,7 +75,7 @@ class JobService: return JobOutSchema.model_validate(obj).model_dump() @classmethod - async def update_job_service(cls, auth: AuthSchema, id:int, data: JobUpdateSchema) -> Dict: + async def update_job_service(cls, auth: AuthSchema, id:int, data: JobUpdateSchema) -> dict: """ 更新定时任务 @@ -87,7 +85,7 @@ class JobService: - data (JobUpdateSchema): 定时任务更新模型 返回: - - Dict: 定时任务详情字典 + - dict: 定时任务详情字典 """ exist_obj = await JobCRUD(auth).get_obj_by_id_crud(id=id) if not exist_obj: @@ -153,7 +151,7 @@ class JobService: SchedulerUtil().pause_job(job_id=id) await JobCRUD(auth).set_obj_field_crud(ids=[id], status=False) elif option == 2: - SchedulerUtil().resume_job(job_id=id, tenant_id=obj.tenant_id) + SchedulerUtil().resume_job(job_id=id) await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True) elif option == 3: # 重启任务:先移除再添加,确保使用最新的任务配置 @@ -167,12 +165,12 @@ class JobService: await JobCRUD(auth).set_obj_field_crud(ids=[id], status=True) @classmethod - async def export_job_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_job_service(cls, data_list: list[dict]) -> bytes: """ 导出定时任务列表 参数: - - data_list (List[Dict[str, Any]]): 定时任务列表 + - data_list (list[dict]): 定时任务列表 返回: - bytes: Excel文件字节流 @@ -195,15 +193,14 @@ class JobService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 data = data_list.copy() for item in data: - item['status'] = '已完成' if item['status'] == 0 else '运行中' if item['status'] == 1 else '暂停' - item['creator'] = item.get('creator', {}).get('name', '未知') if isinstance(item.get('creator'), dict) else '未知' - + item['status'] = '已完成' if item['status'] == '0' else '运行中' if item['status'] == '1' else '暂停' + return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict) @@ -213,7 +210,7 @@ class JobLogService: """ @classmethod - async def get_job_log_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_job_log_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取定时任务日志详情 @@ -222,23 +219,23 @@ class JobLogService: - id (int): 定时任务日志ID 返回: - - Dict: 定时任务日志详情字典 + - dict: 定时任务日志详情字典 """ obj = await JobLogCRUD(auth).get_obj_log_by_id_crud(id=id) return JobLogOutSchema.model_validate(obj).model_dump() @classmethod - async def get_job_log_list_service(cls, auth: AuthSchema, search: Optional[JobLogQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_job_log_list_service(cls, auth: AuthSchema, search: JobLogQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取定时任务日志列表 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[JobLogQueryParam]): 查询参数模型, 包含分页信息和查询条件 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表, 每个元素为一个字典, 包含字段名和排序方向 + - search (JobLogQueryParam | None): 查询参数模型, 包含分页信息和查询条件 + - order_by (list[dict] | None): 排序参数列表, 每个元素为一个字典, 包含字段名和排序方向 返回: - - List[Dict]: 定时任务日志详情字典列表 + - list[dict]: 定时任务日志详情字典列表 """ 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] @@ -275,7 +272,7 @@ class JobLogService: await JobLogCRUD(auth).delete_obj_log_crud(ids=ids) @classmethod - async def export_job_log_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_job_log_service(cls, data_list: list[dict]) -> bytes: """ 导出定时任务日志列表 @@ -303,7 +300,7 @@ class JobLogService: # 复制数据并转换状态 data = data_list.copy() for item in data: - item['status'] = '成功' if item.get('status') else '失败' + item['status'] = '成功' if item.get('status') == '0' else '失败' return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict) \ No newline at end of file diff --git a/backend/app/api/v1/module_application/job/tools/ap_scheduler.py b/backend/app/api/v1/module_application/job/tools/ap_scheduler.py index 4879500b..723b1121 100644 --- a/backend/app/api/v1/module_application/job/tools/ap_scheduler.py +++ b/backend/app/api/v1/module_application/job/tools/ap_scheduler.py @@ -3,10 +3,10 @@ import json import importlib from datetime import datetime -from typing import Union, List, Any, Optional, Callable, Dict +from typing import Any from asyncio import iscoroutinefunction from apscheduler.job import Job -from apscheduler.events import JobExecutionEvent, JobEvent +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 @@ -24,36 +24,7 @@ from app.core.exceptions import CustomException from app.core.logger import log from app.utils.cron_util import CronUtil -from app.api.v1.module_application.job.model import JobLogModel, JobModel - -# 租户上下文管理器 -class TenantContext: - """ - 租户上下文管理器 - 用于在任务执行时保存和恢复租户上下文 - """ - _current_tenant_id = None - _current_user_id = None - - @classmethod - def set(cls, tenant_id: int | None = None, user_id: int | None = None): - """设置租户上下文""" - cls._current_tenant_id = tenant_id - cls._current_user_id = user_id - - @classmethod - def get(cls) -> Dict[str, Optional[int]]: - """获取租户上下文""" - return { - 'tenant_id': cls._current_tenant_id, - 'user_id': cls._current_user_id - } - - @classmethod - def clear(cls): - """清除租户上下文""" - cls._current_tenant_id = None - cls._current_user_id = None +from app.api.v1.module_application.job.model import JobModel job_stores = { 'default': MemoryJobStore(), @@ -89,7 +60,6 @@ class SchedulerUtil: """ 定时任务相关方法 """ - @classmethod def scheduler_event_listener(cls, event: JobEvent | JobExecutionEvent) -> None: """ @@ -101,8 +71,8 @@ class SchedulerUtil: 返回: - None """ - # 使用配置的模型路径 - from ..model import JobLogModel + # 延迟导入避免循环导入 + from app.api.v1.module_application.job.model import JobLogModel # 获取事件类型和任务ID event_type = event.__class__.__name__ @@ -114,20 +84,14 @@ class SchedulerUtil: status = False if hasattr(event, 'job_id'): job_id = event.job_id - - # 从任务ID中提取租户信息 - tenant_info = cls._extract_tenant_info(job_id) - tenant_id = tenant_info.get('tenant_id') - - # 使用原始任务ID查询任务信息 - query_job = cls.get_job(job_id=job_id, tenant_id=tenant_id) + query_job = cls.get_job(job_id=job_id) if query_job: query_job_info = query_job.__getstate__() # 获取任务名称 job_name = query_job_info.get('name') # 获取任务组名 job_group = query_job._jobstore_alias - # 获取任务执行器 + # # 获取任务执行器 job_executor = query_job_info.get('executor') # 获取调用目标字符串 invoke_target = query_job_info.get('func') @@ -138,7 +102,7 @@ class SchedulerUtil: # 获取任务触发器 job_trigger = str(query_job_info.get('trigger')) # 构造日志消息 - job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 租户ID: {tenant_id}, 任务名称: {job_name}, 状态: {status}, 任务组: {job_group}, 错误详情: {exception_info}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + job_message = f"事件类型: {event_type}, 任务ID: {job_id}, 任务名称: {job_name}, 状态: {status}, 任务组: {job_group}, 错误详情: {exception_info}, 执行于{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" # 创建ORM对象 job_log = JobLogModel( @@ -154,155 +118,63 @@ class SchedulerUtil: exception_info=exception_info, create_time=datetime.now(), job_id=job_id, - tenant_id=tenant_id # 添加租户ID ) # 使用线程池执行操作以避免阻塞调度器和数据库锁定问题 executor = ThreadPoolExecutor(max_workers=1) - executor.submit(cls._save_job_log_async_wrapper, job_log, tenant_id) + executor.submit(cls._save_job_log_async_wrapper, job_log) executor.shutdown(wait=False) - - log.info(f"任务执行事件: {event_type}, 租户ID: {tenant_id}, 任务ID: {job_id}") @classmethod - def _save_job_log_async_wrapper(cls, job_log, tenant_id): + def _save_job_log_async_wrapper(cls, job_log) -> None: """ 异步保存任务日志的包装器函数,在独立线程中运行 参数: - job_log (JobLogModel): 任务日志对象 - - tenant_id (int): 租户ID 返回: - None """ - # 设置租户上下文用于日志保存 - TenantContext.set(tenant_id=tenant_id) - try: - with db_session() as session: - try: - # 确保session能正确处理多租户隔离 - session.add(job_log) - session.commit() - except Exception as e: - session.rollback() - log.error(f"保存任务日志失败 (租户ID: {tenant_id}): {str(e)}") - finally: - session.close() - finally: - # 清除租户上下文 - TenantContext.clear() - - @classmethod - def _format_job_id(cls, job_id: str, tenant_id: int) -> str: - """ - 格式化任务ID,添加租户标识前缀 - - 参数: - - job_id: 原始任务ID - - tenant_id: 租户ID - - 返回: - - str: 格式化后的任务ID - """ - return f"tenant_{tenant_id}_{job_id}" - - @classmethod - def _extract_tenant_info(cls, formatted_job_id: str) -> Dict[str, Optional[int]]: - """ - 从格式化的任务ID中提取租户信息 - - 参数: - - formatted_job_id: 格式化的任务ID - - 返回: - - Dict: 包含租户信息的字典 - """ - parts = formatted_job_id.split('_', 2) - if len(parts) >= 3 and parts[0] == 'tenant': + with db_session.begin() as session: try: - return { - 'tenant_id': int(parts[1]), - } - except ValueError: - pass - return { - 'tenant_id': None, - } - - @classmethod - def _wrap_function_with_context(cls, func: Callable, tenant_id: int | None = None, user_id: int | None = None) -> Callable: - """ - 包装函数,在执行前设置租户上下文 - - 参数: - - func: 原始函数 - - tenant_id: 租户ID - - user_id: 用户ID - - 返回: - - Callable: 包装后的函数 - """ - async def async_wrapped(*args, **kwargs): - try: - # 设置租户上下文 - TenantContext.set(tenant_id=tenant_id, user_id=user_id) - # 执行原始函数 - return await func(*args, **kwargs) + session.add(job_log) + session.commit() + except Exception as e: + session.rollback() + log.error(f"保存任务日志失败: {str(e)}") finally: - # 清除租户上下文 - TenantContext.clear() - - def sync_wrapped(*args, **kwargs): - try: - # 设置租户上下文 - TenantContext.set(tenant_id=tenant_id, user_id=user_id) - # 执行原始函数 - return func(*args, **kwargs) - finally: - # 清除租户上下文 - TenantContext.clear() - - return async_wrapped if iscoroutinefunction(func) else sync_wrapped + session.close() @classmethod - async def init_system_scheduler(cls): + async def init_system_scheduler(cls) -> None: """ 应用启动时初始化定时任务。 返回: - None """ + # 延迟导入避免循环导入 + from app.api.v1.module_application.job.crud import JobCRUD from app.api.v1.module_system.auth.schema import AuthSchema - from ..crud import JobCRUD - log.info('🔎 开始启动定时任务...') scheduler.start() async with async_db_session() as session: async with session.begin(): - # 在初始化过程中,不需要检查数据权限 - auth = AuthSchema(db=session, check_data_scope=False) + auth = AuthSchema(db=session) job_list = await JobCRUD(auth).get_obj_list_crud() for item in job_list: - - # 删除旧任务(使用租户ID进行格式化) - cls.remove_job(job_id=item.id, tenant_id=item.tenant_id) - - # 添加任务,传入租户ID - cls.add_job(item, tenant_id=item.tenant_id) - + cls.remove_job(job_id=item.id) # 删除旧任务 + cls.add_job(item) # 根据数据库中保存的状态来设置任务状态 - if item.status is False: + if hasattr(item, 'status') and item.status == "1": # 如果任务状态为暂停,则立即暂停刚添加的任务 - cls.pause_job(job_id=item.id, tenant_id=item.tenant_id) - - # 添加租户隔离的事件监听器,只监听任务执行相关事件 - from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR, EVENT_JOB_MISSED, EVENT_JOB_ADDED, EVENT_JOB_REMOVED - scheduler.add_listener(cls.scheduler_event_listener, EVENT_JOB_EXECUTED | EVENT_JOB_ERROR | EVENT_JOB_MISSED | EVENT_JOB_ADDED | EVENT_JOB_REMOVED) + cls.pause_job(job_id=item.id) + scheduler.add_listener(cls.scheduler_event_listener, EVENT_ALL) log.info('✅️ 系统初始定时任务加载成功') @classmethod - async def close_system_scheduler(cls): + async def close_system_scheduler(cls) -> None: """ 关闭系统定时任务。 @@ -319,54 +191,43 @@ class SchedulerUtil: log.error(f'关闭定时任务失败: {str(e)}') @classmethod - def get_job(cls, job_id: int, tenant_id: Optional[int] = None) -> Optional[Job]: + def get_job(cls, job_id: str | int) -> Job | None: """ 根据任务ID获取任务对象。 参数: - job_id (str | int): 任务ID。 - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 返回: - - Optional[Job]: 任务对象,未找到则为 None。 + - Job | None: 任务对象,未找到则为 None。 """ - # 如果提供了租户ID,则格式化任务ID - formatted_job_id = cls._format_job_id(str(job_id), tenant_id) if tenant_id is not None else str(job_id) - return scheduler.get_job(job_id=formatted_job_id) + return scheduler.get_job(job_id=str(job_id)) @classmethod - def get_all_jobs(cls) -> List[Job]: + def get_all_jobs(cls) -> list[Job]: """ 获取全部调度任务列表。 返回: - - List[Job]: 任务列表。 + - list[Job]: 任务列表。 """ return scheduler.get_jobs() @classmethod - def add_job(cls, job_info: JobModel, tenant_id: int | None = None) -> Job: + def add_job(cls, job_info: JobModel) -> Job: """ 根据任务配置创建并添加调度任务。 参数: - job_info (JobModel): 任务对象信息(包含触发器、函数、参数等)。 - - tenant_id (int, optional): 租户ID,用于多租户隔离。 返回: - Job: 新增的任务对象。 """ - # 从job_info中获取租户ID(如果存在) - if tenant_id is None and hasattr(job_info, 'tenant_id'): - tenant_id = job_info.tenant_id - # 动态导入模块 # 1. 解析调用目标 module_path, func_name = str(job_info.func).rsplit('.', 1) - # 使用配置或动态模块路径,避免硬编码 - base_module_path = getattr(settings, 'TASK_MODULE_BASE_PATH', 'app.api.v1.module_application.job.function_task') - module_path = f"{base_module_path}.{module_path}" - + module_path = "app.api.v1.module_application.job.function_task." + module_path try: module = importlib.import_module(module_path) job_func = getattr(module, func_name) @@ -380,11 +241,8 @@ class SchedulerUtil: if job_info.trigger_args is None: raise ValueError("interval 触发器缺少参数") - # 确定执行器类型 if iscoroutinefunction(job_func): job_executor = 'default' - - # 3. 创建触发器 if job_info.trigger == 'date': trigger = DateTrigger(run_date=job_info.trigger_args) elif job_info.trigger == 'interval': @@ -433,78 +291,44 @@ class SchedulerUtil: else: raise ValueError("无效的 trigger 触发器") - # 4. 准备任务参数 - args = str(job_info.args).split(',') if job_info.args else None - kwargs = json.loads(job_info.kwargs) if job_info.kwargs else {} - - # 添加租户信息到kwargs - if tenant_id is not None: - kwargs['tenant_id'] = tenant_id - # 获取创建者信息(如果存在) - if hasattr(job_info, 'created_by'): - kwargs['created_by'] = job_info.created_by - - # 5. 包装函数,添加租户上下文 - # 获取创建者信息作为user_id - user_id = getattr(job_info, 'created_by', None) - wrapped_func = cls._wrap_function_with_context(job_func, tenant_id, user_id) - - # 6. 生成任务ID - job_id = str(job_info.id) - formatted_job_id = cls._format_job_id(job_id, tenant_id) if tenant_id is not None else job_id - - # 7. 添加任务 + # 3. 添加任务 job = scheduler.add_job( - func=wrapped_func, # 使用包装后的函数 + func=job_func, # 直接使用函数对象 trigger=trigger, - args=args, - kwargs=kwargs, - id=formatted_job_id, - name=f"{job_info.name} (租户:{tenant_id or '系统'})" if tenant_id is not None else job_info.name, + args=str(job_info.args).split(',') if job_info.args else None, + kwargs=json.loads(job_info.kwargs) if job_info.kwargs else None, + id=str(job_info.id), + name=job_info.name, coalesce=job_info.coalesce, max_instances=job_info.max_instances, jobstore=job_info.jobstore, executor=job_executor, - # 添加任务元数据 - replace_existing=True ) - - log.info(f"添加任务成功: ID={formatted_job_id}, 名称={job_info.name}, 租户ID={tenant_id}") return job except ModuleNotFoundError: raise ValueError(f"未找到该模块:{module_path}") except AttributeError: raise ValueError(f"未找到该模块下的方法:{func_name}") except Exception as e: - log.error(f"添加任务失败 (租户ID: {tenant_id}, 任务ID: {job_info.id}): {str(e)}") raise CustomException(msg=f"添加任务失败: {str(e)}") @classmethod - def remove_job(cls, job_id: Union[str, int], tenant_id: Optional[int] = None) -> None: + def remove_job(cls, job_id: str | int) -> None: """ 根据任务ID删除调度任务。 参数: - job_id (str | int): 任务ID。 - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 返回: - None """ - # 格式化任务ID - job_id_str = str(job_id) - formatted_job_id = cls._format_job_id(job_id_str, tenant_id) if tenant_id is not None else job_id_str - - # 先尝试直接删除格式化后的任务ID - try: - scheduler.remove_job(job_id=formatted_job_id) - log.info(f"删除任务成功: ID={formatted_job_id}, 租户ID={tenant_id}") - except Exception as e: - # 如果失败,记录日志但不抛出异常 - log.warning(f"删除任务失败 (可能不存在): ID={formatted_job_id}, 租户ID={tenant_id}, 错误: {str(e)}") + query_job = cls.get_job(job_id=str(job_id)) + if query_job: + scheduler.remove_job(job_id=str(job_id)) @classmethod - def clear_jobs(cls): + def clear_jobs(cls) -> None: """ 删除所有调度任务。 @@ -514,7 +338,7 @@ class SchedulerUtil: scheduler.remove_all_jobs() @classmethod - def modify_job(cls, job_id: int) -> Job: + def modify_job(cls, job_id: str | int) -> Job: """ 更新指定任务的配置(运行中的任务下次执行生效)。 @@ -527,19 +351,18 @@ class SchedulerUtil: 异常: - CustomException: 当任务不存在时抛出。 """ - query_job = cls.get_job(job_id=job_id) + query_job = cls.get_job(job_id=str(job_id)) if not query_job: raise CustomException(msg=f"未找到该任务:{job_id}") return scheduler.modify_job(job_id=str(job_id)) @classmethod - def pause_job(cls, job_id: int, tenant_id: int | None = None): + def pause_job(cls, job_id: str | int) -> None: """ 暂停指定任务(仅运行中可暂停,已终止不可)。 参数: - job_id (str | int): 任务ID。 - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 返回: - None @@ -547,21 +370,18 @@ class SchedulerUtil: 异常: - ValueError: 当任务不存在时抛出。 """ - formatted_job_id = cls._format_job_id(str(job_id), tenant_id) if tenant_id is not None else str(job_id) - query_job = cls.get_job(job_id=job_id, tenant_id=tenant_id) + query_job = cls.get_job(job_id=str(job_id)) if not query_job: - raise ValueError(f"未找到该任务:{job_id} (租户: {tenant_id})") - scheduler.pause_job(job_id=formatted_job_id) - log.info(f"暂停任务成功: ID={formatted_job_id}, 租户ID={tenant_id}") + raise ValueError(f"未找到该任务:{job_id}") + scheduler.pause_job(job_id=str(job_id)) @classmethod - def resume_job(cls, job_id: int, tenant_id: int | None = None): + def resume_job(cls, job_id: str | int) -> None: """ 恢复指定任务(仅暂停中可恢复,已终止不可)。 参数: - job_id (str | int): 任务ID。 - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 返回: - None @@ -569,21 +389,18 @@ class SchedulerUtil: 异常: - ValueError: 当任务不存在时抛出。 """ - formatted_job_id = cls._format_job_id(str(job_id), tenant_id) if tenant_id is not None else str(job_id) - query_job = cls.get_job(job_id=job_id, tenant_id=tenant_id) + query_job = cls.get_job(job_id=str(job_id)) if not query_job: - raise ValueError(f"未找到该任务:{job_id} (租户: {tenant_id})") - scheduler.resume_job(job_id=formatted_job_id) - log.info(f"恢复任务成功: ID={formatted_job_id}, 租户ID={tenant_id}") + raise ValueError(f"未找到该任务:{job_id}") + scheduler.resume_job(job_id=str(job_id)) @classmethod - def reschedule_job(cls, job_id: int, tenant_id: int, trigger=None, **trigger_args) -> Optional[Job]: + def reschedule_job(cls, job_id: str | int, trigger=None, **trigger_args) -> Job | None: """ 重启指定任务的触发器。 参数: - job_id (str | int): 任务ID。 - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 - trigger: 触发器类型 - **trigger_args: 触发器参数 @@ -593,48 +410,37 @@ class SchedulerUtil: 异常: - CustomException: 当任务不存在时抛出。 """ - # 格式化任务ID - job_id_str = str(job_id) - formatted_job_id = cls._format_job_id(job_id_str, tenant_id) if tenant_id is not None else job_id_str - - query_job = cls.get_job(job_id=job_id, tenant_id=tenant_id) + query_job = cls.get_job(job_id=str(job_id)) if not query_job: - raise CustomException(msg=f"未找到该任务:{job_id} (租户: {tenant_id})") + raise CustomException(msg=f"未找到该任务:{job_id}") # 如果没有提供新的触发器,则使用现有触发器 if trigger is None: # 获取当前任务的触发器配置 current_trigger = query_job.trigger # 重新调度任务,使用当前的触发器 - result = scheduler.reschedule_job(job_id=formatted_job_id, trigger=current_trigger) + return scheduler.reschedule_job(job_id=str(job_id), trigger=current_trigger) else: # 使用新提供的触发器 - result = scheduler.reschedule_job(job_id=formatted_job_id, trigger=trigger, **trigger_args) - - log.info(f"重新调度任务成功: ID={formatted_job_id}, 租户ID={tenant_id}") - return result + return scheduler.reschedule_job(job_id=str(job_id), trigger=trigger, **trigger_args) @classmethod - def get_single_job_status(cls, job_id: int, tenant_id: int | None = None) -> str: + def get_single_job_status(cls, job_id: str | int) -> str: """ 获取单个任务的当前状态。 参数: - job_id (str | int): 任务ID - - tenant_id (int, optional): 租户ID,如果提供则使用租户隔离的任务ID。 返回: - str: 任务状态('running' | 'paused' | 'stopped' | 'unknown') """ - job = cls.get_job(job_id=job_id, tenant_id=tenant_id) + job = cls.get_job(job_id=str(job_id)) if not job: return 'unknown' - job_id_str = str(job_id) - formatted_job_id = cls._format_job_id(job_id_str, tenant_id) if tenant_id is not None else job_id_str - # 检查任务是否在暂停列表中 - if formatted_job_id in scheduler._jobstores[job._jobstore_alias]._paused_jobs: + if job_id in scheduler._jobstores[job._jobstore_alias]._paused_jobs: return 'paused' # 检查调度器状态 @@ -643,82 +449,23 @@ class SchedulerUtil: return 'running' - @classmethod - def get_jobs_by_tenant(cls, tenant_id: int, jobstore: Optional[str] = None) -> List[Job]: - """ - 获取指定租户的所有任务 - - 参数: - - tenant_id (int): 租户ID - - jobstore (str, optional): 任务存储别名 - - 返回: - - List[Job]: 任务列表 - """ - all_jobs = scheduler.get_jobs(jobstore=jobstore) - tenant_jobs = [] - - for job in all_jobs: - tenant_info = cls._extract_tenant_info(job.id) - if tenant_info.get('tenant_id') == tenant_id: - tenant_jobs.append(job) - - return tenant_jobs - - # 获取当前租户上下文的辅助函数 - @classmethod - def get_current_tenant(cls) -> Dict[str, Optional[int]]: - """ - 获取当前任务执行的租户上下文 - - 返回: - - Dict: 包含租户ID和用户ID的字典 - """ - return TenantContext.get() - @classmethod def export_jobs(cls): - """ - 导出任务到文件,使用配置的路径。 - """ - from app.config.setting import settings - from app.core.logger import log - - # 使用配置的导出路径或默认路径 - export_path = getattr(settings, 'JOB_EXPORT_PATH', '/tmp/jobs.json') - try: - scheduler.export_jobs(export_path) - log.info(f"任务导出成功: {export_path}") - except Exception as e: - log.error(f"任务导出失败: {str(e)}") - raise + scheduler.export_jobs("/temp/jobs.json") @classmethod def import_jobs(cls): - """ - 从文件导入任务,使用配置的路径。 - """ - from app.config.setting import settings - from app.core.logger import log - - # 使用配置的导入路径或默认路径 - import_path = getattr(settings, 'JOB_IMPORT_PATH', '/tmp/jobs.json') - try: - scheduler.import_jobs(import_path) - log.info(f"任务导入成功: {import_path}") - except Exception as e: - log.error(f"任务导入失败: {str(e)}") - raise + scheduler.import_jobs("/temp/jobs.json") @classmethod def print_jobs(cls,jobstore: Any | None = None, out: Any | None = None): """ 打印调度任务列表。 - + 参数: - jobstore (Any | None): 任务存储别名。 - out (Any | None): 输出目标。 - + 返回: - None """ @@ -728,7 +475,7 @@ class SchedulerUtil: def get_job_status(cls) -> str: """ 获取调度器当前状态。 - + 返回: - str: 状态字符串('stopped' | 'running' | 'paused' | 'unknown')。 """ diff --git a/backend/app/api/v1/module_application/myapp/controller.py b/backend/app/api/v1/module_application/myapp/controller.py index 5c4f9e0a..d69281b8 100644 --- a/backend/app/api/v1/module_application/myapp/controller.py +++ b/backend/app/api/v1/module_application/myapp/controller.py @@ -7,11 +7,11 @@ from app.common.response import SuccessResponse from app.common.request import PaginationService from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.base_schema import BatchSetAvailable from app.core.logger import log from app.api.v1.module_system.auth.schema import AuthSchema +from app.core.router_class import OperationLogRoute from .service import ApplicationService from .schema import ( ApplicationCreateSchema, diff --git a/backend/app/api/v1/module_application/myapp/crud.py b/backend/app/api/v1/module_application/myapp/crud.py index b8620ddb..09d40abd 100644 --- a/backend/app/api/v1/module_application/myapp/crud.py +++ b/backend/app/api/v1/module_application/myapp/crud.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence, Any from app.core.base_crud import CRUDBase - from app.api.v1.module_system.auth.schema import AuthSchema from .model import ApplicationModel from .schema import ApplicationCreateSchema, ApplicationUpdateSchema @@ -22,34 +21,34 @@ class ApplicationCRUD(CRUDBase[ApplicationModel, ApplicationCreateSchema, Applic self.auth = auth super().__init__(model=ApplicationModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ApplicationModel]: + async def get_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> ApplicationModel | None: """ 根据id获取应用详情 参数: - id (int): 应用ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[ApplicationModel]: 应用详情,如果不存在则为None + - ApplicationModel | None: 应用详情,如果不存在则为None """ return await self.get(id=id, preload=preload) - async def list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[ApplicationModel]: + async def list_crud(self, search: dict[str, Any] | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[ApplicationModel]: """ 列表查询应用 参数: - - search (Optional[Dict]): 查询参数,默认None - - order_by (Optional[List[Dict[str, str]]]): 排序参数,默认None - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict[str, Any] | None): 查询参数,默认None + - order_by (list[dict[str, str]] | None): 排序参数,默认None + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[ApplicationModel]: 应用列表 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_crud(self, data: ApplicationCreateSchema) -> Optional[ApplicationModel]: + async def create_crud(self, data: ApplicationCreateSchema) -> ApplicationModel | None: """ 创建应用 @@ -57,11 +56,11 @@ class ApplicationCRUD(CRUDBase[ApplicationModel, ApplicationCreateSchema, Applic - data (ApplicationCreateSchema): 应用创建模型 返回: - - Optional[ApplicationModel]: 创建的应用详情,如果创建失败则为None + - ApplicationModel | None: 创建的应用详情,如果创建失败则为None """ return await self.create(data=data) - async def update_crud(self, id: int, data: ApplicationUpdateSchema) -> Optional[ApplicationModel]: + async def update_crud(self, id: int, data: ApplicationUpdateSchema) -> ApplicationModel | None: """ 更新应用 @@ -70,25 +69,25 @@ class ApplicationCRUD(CRUDBase[ApplicationModel, ApplicationCreateSchema, Applic - data (ApplicationUpdateSchema): 应用更新模型 返回: - - Optional[ApplicationModel]: 更新后的应用详情,如果更新失败则为None + - ApplicationModel | None: 更新后的应用详情,如果更新失败则为None """ return await self.update(id=id, data=data) - async def delete_crud(self, ids: List[int]) -> None: + async def delete_crud(self, ids: list[int]) -> None: """ 批量删除应用 参数: - - ids (List[int]): 应用ID列表 + - ids (list[int]): 应用ID列表 """ return await self.delete(ids=ids) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置可用状态 参数: - - ids (List[int]): 应用ID列表 - - status (bool): 可用状态,True为可用,False为不可用 + - ids (list[int]): 应用ID列表 + - status (str): 可用状态,True为可用,False为不可用 """ return await self.set(ids=ids, status=status) \ No newline at end of file diff --git a/backend/app/api/v1/module_application/myapp/model.py b/backend/app/api/v1/module_application/myapp/model.py index 45682794..5cfa8d51 100644 --- a/backend/app/api/v1/module_application/myapp/model.py +++ b/backend/app/api/v1/module_application/myapp/model.py @@ -3,24 +3,16 @@ from sqlalchemy import String from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class ApplicationModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class ApplicationModel(ModelMixin, UserMixin): """ - 应用系统表 - 用于管理分系统和外部应用 - - 数据隔离策略: - =========== - - 系统级应用: tenant_id=1, customer_id=NULL (平台级应用,所有租户可见) - - 租户级应用: tenant_id>1, customer_id=NULL (租户自己的应用,仅本租户可见) - - 客户级应用: tenant_id>1, customer_id>0 (客户专属应用,仅该客户可见) - - 根据业务需求,此表支持三级隔离 + 应用系统表 """ __tablename__: str = 'app_myapp' __table_args__: dict[str, str] = ({'comment': '应用系统表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] name: Mapped[str] = mapped_column(String(64), nullable=False, comment='应用名称') access_url: Mapped[str] = mapped_column(String(500), nullable=False, comment='访问地址') diff --git a/backend/app/api/v1/module_application/myapp/schema.py b/backend/app/api/v1/module_application/myapp/schema.py index 5ad0e150..382508fd 100644 --- a/backend/app/api/v1/module_application/myapp/schema.py +++ b/backend/app/api/v1/module_application/myapp/schema.py @@ -1,21 +1,20 @@ # -*- coding: utf-8 -*- -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator from urllib.parse import urlparse from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema class ApplicationCreateSchema(BaseModel): """应用创建模型""" name: str = Field(..., max_length=64, description='应用名称') access_url: str = Field(..., max_length=255, description="访问地址") - icon_url: Optional[str] = Field(None, max_length=300, description="应用图标URL") - status: str = Field(True, description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + icon_url: str | None = Field(None, max_length=300, description="应用图标URL") + status: str = Field("0", description="是否启用(0:启用 1:禁用)") + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator('access_url') @classmethod @@ -30,7 +29,7 @@ class ApplicationCreateSchema(BaseModel): @field_validator('icon_url') @classmethod - def _validate_icon_url(cls, v: Optional[str]) -> Optional[str]: + def _validate_icon_url(cls, v: str | None) -> str | None: if v is None: return v v = v.strip() @@ -47,7 +46,7 @@ class ApplicationUpdateSchema(ApplicationCreateSchema): ... -class ApplicationOutSchema(ApplicationCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class ApplicationOutSchema(ApplicationCreateSchema, BaseSchema, UserBySchema): """应用响应模型""" model_config = ConfigDict(from_attributes=True) @@ -57,10 +56,12 @@ class ApplicationQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="应用名称"), - status: Optional[str] = Query(None, description="是否启用"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="应用名称"), + status: str | None = Query(None, description="是否启用"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -69,7 +70,10 @@ class ApplicationQueryParam: # 精确查询字段 self.status = status self.created_id = created_id + self.updated_id = updated_id # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_application/myapp/service.py b/backend/app/api/v1/module_application/myapp/service.py index 7c82c766..3ddb6eef 100644 --- a/backend/app/api/v1/module_application/myapp/service.py +++ b/backend/app/api/v1/module_application/myapp/service.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from typing import List, Dict, Optional, Union - from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException @@ -21,7 +19,7 @@ class ApplicationService: """ @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取应用详情 @@ -30,7 +28,7 @@ class ApplicationService: - id (int): 应用ID 返回: - - Dict: 应用详情字典 + - dict: 应用详情字典 """ obj = await ApplicationCRUD(auth).get_by_id_crud(id=id) if not obj: @@ -38,17 +36,17 @@ class ApplicationService: return ApplicationOutSchema.model_validate(obj).model_dump() @classmethod - async def list_service(cls, auth: AuthSchema, search: Optional[ApplicationQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def list_service(cls, auth: AuthSchema, search: ApplicationQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]: """ 获取应用列表 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[ApplicationQueryParam]): 查询参数模型 - - order_by (Optional[Union[str, List[Dict[str, str]]]]): 排序参数,支持字符串或字典列表 + - search (ApplicationQueryParam | None): 查询参数模型 + - order_by (list[dict[str, str]] | None): 排序参数,支持字符串或字典列表 返回: - - List[Dict]: 应用详情字典列表 + - list[dict]: 应用详情字典列表 """ # 过滤空值 search_dict = search.__dict__ if search else None @@ -56,7 +54,7 @@ class ApplicationService: return [ApplicationOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def create_service(cls, auth: AuthSchema, data: ApplicationCreateSchema) -> Dict: + async def create_service(cls, auth: AuthSchema, data: ApplicationCreateSchema) -> dict: """ 创建应用 @@ -76,7 +74,7 @@ class ApplicationService: return ApplicationOutSchema.model_validate(obj).model_dump() @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: ApplicationUpdateSchema) -> Dict: + async def update_service(cls, auth: AuthSchema, id: int, data: ApplicationUpdateSchema) -> dict: """ 更新应用 diff --git a/backend/app/api/v1/module_common/file/controller.py b/backend/app/api/v1/module_common/file/controller.py index 3079cee6..59e213a7 100644 --- a/backend/app/api/v1/module_common/file/controller.py +++ b/backend/app/api/v1/module_common/file/controller.py @@ -5,9 +5,9 @@ from fastapi import APIRouter, BackgroundTasks, Body, Depends, UploadFile, Reque from fastapi.responses import JSONResponse, FileResponse from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.logger import log from app.common.response import SuccessResponse, UploadFileResponse +from app.core.router_class import OperationLogRoute from app.utils.upload_util import UploadUtil from .service import FileService diff --git a/backend/app/api/v1/module_common/file/schema.py b/backend/app/api/v1/module_common/file/schema.py index 5d7c6f25..88c272f9 100644 --- a/backend/app/api/v1/module_common/file/schema.py +++ b/backend/app/api/v1/module_common/file/schema.py @@ -2,16 +2,16 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic.alias_generators import to_camel -from typing import Optional class ImportFieldModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - base_column: Optional[str] = Field(description='数据库字段名') - excel_column: Optional[str] = Field(description='excel字段名', default=None) - default_value: Optional[str] = Field(description='默认值', default=None) - is_required: Optional[str] = Field(description='是否必传') - selected: Optional[bool] = Field(description='是否勾选') + + base_column: str | None = Field(description='数据库字段名', default=None) + excel_column: str | None = Field(description='excel字段名', default=None) + default_value: str | None = Field(description='默认值', default=None) + is_required: bool | None = Field(description='是否必传', default=None) + selected: bool | None = Field(description='是否勾选', default=None) @model_validator(mode='before') @classmethod @@ -45,10 +45,11 @@ class ImportFieldModel(BaseModel): class ImportModel(BaseModel): model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - table_name: Optional[str] = Field(description='表名') - sheet_name: Optional[str] = Field(description='Sheet名') - filed_info: Optional[list[ImportFieldModel]] = Field(description='字段关联表') - file_name: Optional[str] = Field(description='文件名') + + table_name: str | None = Field(description='表名', default=None) + sheet_name: str | None = Field(description='Sheet名', default=None) + filed_info: list[ImportFieldModel] | None = Field(description='字段关联表', default=None) + file_name: str | None = Field(description='文件名', default=None) @model_validator(mode='after') def _validate(self): diff --git a/backend/app/api/v1/module_example/demo/controller.py b/backend/app/api/v1/module_example/demo/controller.py index 7079d4fa..c87a5199 100644 --- a/backend/app/api/v1/module_example/demo/controller.py +++ b/backend/app/api/v1/module_example/demo/controller.py @@ -5,13 +5,12 @@ from fastapi.responses import JSONResponse, StreamingResponse import urllib.parse from app.common.response import StreamResponse, SuccessResponse +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.base_schema import BatchSetAvailable from app.core.logger import log - from app.api.v1.module_system.auth.schema import AuthSchema from .service import DemoService from .schema import ( diff --git a/backend/app/api/v1/module_example/demo/crud.py b/backend/app/api/v1/module_example/demo/crud.py index 4e5f8f05..e8d210d7 100644 --- a/backend/app/api/v1/module_example/demo/crud.py +++ b/backend/app/api/v1/module_example/demo/crud.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any - +from collections.abc import Sequence from app.core.base_crud import CRUDBase from app.api.v1.module_system.auth.schema import AuthSchema @@ -21,34 +20,34 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]): """ super().__init__(model=DemoModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DemoModel]: + async def get_by_id_crud(self, id: int, preload: list[str] | None = None) -> DemoModel | None: """ 详情 参数: - id (int): 示例ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[DemoModel]: 示例模型实例或None + - DemoModel | None: 示例模型实例或None """ return await self.get(id=id, preload=preload) - async def list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DemoModel]: + async def list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list[str] | None = None) -> Sequence[DemoModel]: """ 列表查询 参数: - - search (Optional[Dict]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数 + - order_by (list[dict] | None): 排序参数 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[DemoModel]: 示例模型实例序列 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_crud(self, data: DemoCreateSchema) -> Optional[DemoModel]: + async def create_crud(self, data: DemoCreateSchema) -> DemoModel | None: """ 创建 @@ -56,11 +55,11 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]): - data (DemoCreateSchema): 示例创建模型 返回: - - Optional[DemoModel]: 示例模型实例或None + - DemoModel | None: 示例模型实例或None """ return await self.create(data=data) - async def update_crud(self, id: int, data: DemoUpdateSchema) -> Optional[DemoModel]: + async def update_crud(self, id: int, data: DemoUpdateSchema) -> DemoModel | None: """ 更新 @@ -69,11 +68,11 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]): - data (DemoUpdateSchema): 示例更新模型 返回: - - Optional[DemoModel]: 示例模型实例或None + - DemoModel | None: 示例模型实例或None """ return await self.update(id=id, data=data) - async def delete_crud(self, ids: List[int]) -> None: + async def delete_crud(self, ids: list[int]) -> None: """ 批量删除 @@ -85,32 +84,32 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]): """ return await self.delete(ids=ids) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置可用状态 参数: - - ids (List[int]): 示例ID列表 - - status (bool): 可用状态 + - ids (list[int]): 示例ID列表 + - status (str): 可用状态 返回: - None """ return await self.set(ids=ids, status=status) - async def page_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict: + async def page_crud(self, offset: int, limit: int, order_by: list[dict] | None = None, search: dict | None = None, preload: list | None = None) -> dict: """ 分页查询 参数: - offset (int): 偏移量 - limit (int): 每页数量 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - search (Optional[Dict]): 查询参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - order_by (list[dict] | None): 排序参数 + - search (dict | None): 查询参数 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Dict: 分页数据 + - dict: 分页数据 """ order_by_list = order_by or [{'id': 'asc'}] search_dict = search or {} diff --git a/backend/app/api/v1/module_example/demo/model.py b/backend/app/api/v1/module_example/demo/model.py index 0d513104..a8ce30c0 100644 --- a/backend/app/api/v1/module_example/demo/model.py +++ b/backend/app/api/v1/module_example/demo/model.py @@ -3,21 +3,15 @@ from sqlalchemy import String from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class DemoModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class DemoModel(ModelMixin, UserMixin): """ 示例表 - - 数据隔离策略: - - 租户级示例: tenant_id必填, customer_id=NULL - - 客户级示例: tenant_id必填, customer_id>0 - - 根据业务需求,此示例表支持客户级数据隔离 """ __tablename__: str = 'gen_demo' __table_args__: dict[str, str] = ({'comment': '示例表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] name: Mapped[str | None] = mapped_column(String(64), nullable=True, default='', comment='名称') diff --git a/backend/app/api/v1/module_example/demo/schema.py b/backend/app/api/v1/module_example/demo/schema.py index 3058671f..fba38662 100644 --- a/backend/app/api/v1/module_example/demo/schema.py +++ b/backend/app/api/v1/module_example/demo/schema.py @@ -1,18 +1,17 @@ # -*- coding: utf-8 -*- -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema class DemoCreateSchema(BaseModel): """新增模型""" name: str = Field(..., min_length=2, max_length=50, description='名称') status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator('name') @classmethod @@ -37,7 +36,6 @@ class DemoCreateSchema(BaseModel): raise ValueError('名称只能包含字母、数字、下划线和中划线') return self - class DemoUpdateSchema(DemoCreateSchema): @@ -45,7 +43,7 @@ class DemoUpdateSchema(DemoCreateSchema): ... -class DemoOutSchema(DemoCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class DemoOutSchema(DemoCreateSchema, BaseSchema, UserBySchema): """响应模型""" model_config = ConfigDict(from_attributes=True) @@ -55,10 +53,12 @@ class DemoQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="名称"), - status: Optional[str] = Query(None, description="是否启用"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="名称"), + status: str | None = Query(None, description="是否启用"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -66,10 +66,11 @@ class DemoQueryParam: # 精确查询字段 self.created_id = created_id + self.updated_id = updated_id self.status = status # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) - - + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_example/demo/service.py b/backend/app/api/v1/module_example/demo/service.py index 584e2e31..2796c5c3 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, Optional +from typing import Any from fastapi import UploadFile import pandas as pd @@ -21,7 +21,7 @@ class DemoService: """ @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 详情 @@ -30,7 +30,7 @@ class DemoService: - id (int): 示例ID 返回: - - Dict: 示例模型实例字典 + - dict: 示例模型实例字典 """ obj = await DemoCRUD(auth).get_by_id_crud(id=id) if not obj: @@ -38,24 +38,24 @@ class DemoService: return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def list_service(cls, auth: AuthSchema, search: Optional[DemoQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def list_service(cls, auth: AuthSchema, search: DemoQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]: """ 列表查询 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[DemoQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 + - search (DemoQueryParam | None): 查询参数 + - order_by (list[dict[str, str]] | None): 排序参数 返回: - - List[Dict]: 示例模型实例字典列表 + - list[dict]: 示例模型实例字典列表 """ search_dict = search.__dict__ if search else None obj_list = await DemoCRUD(auth).list_crud(search=search_dict, order_by=order_by) return [DemoOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def page_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[DemoQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict: + async def page_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: DemoQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> dict: """ 分页查询 @@ -63,11 +63,11 @@ class DemoService: - auth (AuthSchema): 认证信息模型 - page_no (int): 页码 - page_size (int): 每页数量 - - search (Optional[DemoQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 + - search (DemoQueryParam | None): 查询参数 + - order_by (list[dict[str, str]] | None): 排序参数 返回: - - Dict: 分页数据 + - dict: 分页数据 """ search_dict = search.__dict__ if search else {} order_by_list = order_by or [{'id': 'asc'}] @@ -82,7 +82,7 @@ class DemoService: return result @classmethod - async def create_service(cls, auth: AuthSchema, data: DemoCreateSchema) -> Dict: + async def create_service(cls, auth: AuthSchema, data: DemoCreateSchema) -> dict: """ 创建 @@ -91,7 +91,7 @@ class DemoService: - data (DemoCreateSchema): 示例创建模型 返回: - - Dict: 示例模型实例字典 + - dict: 示例模型实例字典 """ obj = await DemoCRUD(auth).get(name=data.name) if obj: @@ -100,7 +100,7 @@ class DemoService: return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: DemoUpdateSchema) -> Dict: + async def update_service(cls, auth: AuthSchema, id: int, data: DemoUpdateSchema) -> dict: """ 更新 @@ -110,7 +110,7 @@ class DemoService: - data (DemoUpdateSchema): 示例更新模型 返回: - - Dict: 示例模型实例字典 + - dict: 示例模型实例字典 """ # 检查数据是否存在 obj = await DemoCRUD(auth).get_by_id_crud(id=id) @@ -126,13 +126,13 @@ class DemoService: return DemoOutSchema.model_validate(obj).model_dump() @classmethod - async def delete_service(cls, auth: AuthSchema, ids: List[int]) -> None: + async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None: """ 删除 参数: - auth (AuthSchema): 认证信息模型 - - ids (List[int]): 示例ID列表 + - ids (list[int]): 示例ID列表 返回: - None @@ -163,12 +163,12 @@ class DemoService: await DemoCRUD(auth).set_available_crud(ids=data.ids, status=data.status) @classmethod - async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes: + async def batch_export_service(cls, obj_list: list[dict[str, Any]]) -> bytes: """ 批量导出 参数: - - obj_list (List[Dict[str, Any]]): 示例模型实例字典列表 + - obj_list (list[dict[str, Any]]): 示例模型实例字典列表 返回: - bytes: Excel文件字节流 diff --git a/backend/app/api/v1/module_generator/gencode/controller.py b/backend/app/api/v1/module_generator/gencode/controller.py index e752f17c..09a4ae1e 100644 --- a/backend/app/api/v1/module_generator/gencode/controller.py +++ b/backend/app/api/v1/module_generator/gencode/controller.py @@ -6,9 +6,9 @@ from fastapi.responses import JSONResponse from app.common.response import SuccessResponse, StreamResponse from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.base_params import PaginationQueryParam from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.logger import log from app.api.v1.module_system.auth.schema import AuthSchema diff --git a/backend/app/api/v1/module_generator/gencode/crud.py b/backend/app/api/v1/module_generator/gencode/crud.py index 25735fa9..320bf650 100644 --- a/backend/app/api/v1/module_generator/gencode/crud.py +++ b/backend/app/api/v1/module_generator/gencode/crud.py @@ -2,7 +2,7 @@ from sqlalchemy.engine.row import Row from sqlalchemy import and_, select, text -from typing import List, Optional, Sequence, Dict, Union, Any +from typing import Sequence from sqlglot.expressions import Expression from app.core.logger import log @@ -32,51 +32,51 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): """ super().__init__(model=GenTableModel, auth=auth) - async def get_gen_table_by_id(self, table_id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableModel]: + async def get_gen_table_by_id(self, table_id: int, preload: list | None = None) -> GenTableModel | None: """ 根据业务表ID获取需要生成的业务表信息。 参数: - table_id (int): 业务表ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - GenTableModel | None: 业务表信息对象。 """ return await self.get(id=table_id, preload=preload) - async def get_gen_table_by_name(self, table_name: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableModel]: + async def get_gen_table_by_name(self, table_name: str, preload: list | None = None) -> GenTableModel | None: """ 根据业务表名称获取需要生成的业务表信息。 参数: - table_name (str): 业务表名称。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - GenTableModel | None: 业务表信息对象。 """ return await self.get(table_name=table_name, preload=preload) - async def get_gen_table_all(self, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]: + async def get_gen_table_all(self, preload: list | None = None) -> Sequence[GenTableModel]: """ 获取所有业务表信息。 参数: - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[GenTableModel]: 所有业务表信息列表。 """ return await self.list(preload=preload) - async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]: + async def get_gen_table_list(self, search: GenTableQueryParam | None = None, preload: list | None = None) -> Sequence[GenTableModel]: """ 根据查询参数获取代码生成业务表列表信息。 参数: - search (GenTableQueryParam | None): 查询参数对象。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[GenTableModel]: 业务表列表信息。 @@ -109,16 +109,16 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): # 排除嵌套对象字段,避免SQLAlchemy尝试直接将字典设置到模型实例上 return await self.update(id=table_id, data=edit_model.model_dump(exclude_unset=True, exclude={"columns"})) - async def delete_gen_table(self, ids: List[int]) -> None: + async def delete_gen_table(self, ids: list[int]) -> None: """ 删除业务表信息。除了系统表。 参数: - - ids (List[int]): 业务表ID列表。 + - ids (list[int]): 业务表ID列表。 """ await self.delete(ids=ids) - async def get_db_table_list(self, search: Optional[GenTableQueryParam] = None) -> list[Dict]: + async def get_db_table_list(self, search: GenTableQueryParam | None = None) -> list[dict]: """ 根据查询参数获取数据库表列表信息。 @@ -126,7 +126,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): - search (GenTableQueryParam | None): 查询参数对象。 返回: - - list[Dict]: 数据库表列表信息(已转为可序列化字典)。 + - list[dict]: 数据库表列表信息(已转为可序列化字典)。 """ # 使用更健壮的方式检测数据库方言 @@ -188,7 +188,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): params['table_comment'] = f"%{search.table_comment}%" # 执行查询并绑定参数 - all_data = (await self.db.execute(query_sql, params)).fetchall() + all_data = (await self.auth.db.execute(query_sql, params)).fetchall() # 将Row对象转换为字典列表,解决JSON序列化问题 dict_data = [] @@ -203,12 +203,12 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): dict_data.append(dict_row) return dict_data - async def get_db_table_list_by_names(self, table_names: List[str]) -> list[GenDBTableSchema]: + async def get_db_table_list_by_names(self, table_names: list[str]) -> list[GenDBTableSchema]: """ 根据业务表名称列表获取数据库表信息。 参数: - - table_names (List[str]): 业务表名称列表。 + - table_names (list[str]): 业务表名称列表。 返回: - list[GenDBTableSchema]: 数据库表信息对象列表。 @@ -258,9 +258,9 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): # 使用只读事务执行查询,不影响主事务 if settings.DATABASE_TYPE == "postgres": - gen_db_table_list = (await self.db.execute(text(query_sql), {"table_names": unique_table_names})).fetchall() + gen_db_table_list = (await self.auth.db.execute(text(query_sql), {"table_names": unique_table_names})).fetchall() else: - gen_db_table_list = (await self.db.execute(text(query_sql), {"table_names": tuple(unique_table_names)})).fetchall() + gen_db_table_list = (await self.auth.db.execute(text(query_sql), {"table_names": tuple(unique_table_names)})).fetchall() except Exception as e: log.error(f"查询表信息时发生错误: {e}") # 查询错误时直接抛出,不需要事务处理 @@ -296,14 +296,14 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): else: query = text("SELECT 1 FROM pg_tables WHERE tablename = :table_name") - result = await self.db.execute(query, {"table_name": table_name}) + result = await self.auth.db.execute(query, {"table_name": table_name}) return result.scalar() is not None except Exception as e: log.error(f"检查表格存在性时发生错误: {e}") # 出错时返回False,避免误报表已存在 return False - async def create_table_by_sql(self, sql_statements: List[Expression | None]) -> bool: + async def create_table_by_sql(self, sql_statements: list[Expression | None]) -> bool: """ 根据SQL语句创建表结构。 @@ -313,15 +313,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]): 返回: - bool: 是否创建成功。 """ - - try: # 执行SQL但不手动提交事务,由框架管理事务生命周期 for sql_statement in sql_statements: if not sql_statement: continue sql = sql_statement.sql(dialect=settings.DATABASE_TYPE) - await self.db.execute(text(sql)) + await self.auth.db.execute(text(sql)) return True except Exception as e: log.error(f"创建表时发生错误: {e}") @@ -340,44 +338,44 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen """ super().__init__(model=GenTableColumnModel, auth=auth) - async def get_gen_table_column_by_id(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableColumnModel]: + async def get_gen_table_column_by_id(self, id: int, preload: list | None = None) -> GenTableColumnModel | None: """根据业务表字段ID获取业务表字段信息。 参数: - id (int): 业务表字段ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[GenTableColumnModel]: 业务表字段信息对象。 + - GenTableColumnModel | None: 业务表字段信息对象。 """ return await self.get(id=id, preload=preload) - async def get_gen_table_column_list_by_table_id(self, table_id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableColumnModel]: + async def get_gen_table_column_list_by_table_id(self, table_id: int, preload: list | None = None) -> GenTableColumnModel | None: """根据业务表ID获取业务表字段列表信息。 参数: - table_id (int): 业务表ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[GenTableColumnModel]: 业务表字段列表信息对象。 + - GenTableColumnModel | None: 业务表字段列表信息对象。 """ return await self.get(table_id=table_id, preload=preload) - async def list_gen_table_column_crud_by_table_id(self, table_id: int, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableColumnModel]: + async def list_gen_table_column_crud_by_table_id(self, table_id: int, order_by: list | None = None, preload: list | None = None) -> Sequence[GenTableColumnModel]: """根据业务表ID查询业务表字段列表。 参数: - table_id (int): 业务表ID。 - - order_by (Optional[List[Dict[str, str]]]): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - order_by (list | None): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[GenTableColumnModel]: 业务表字段列表信息对象序列。 """ return await self.list(search={"table_id": table_id}, order_by=order_by, preload=preload) - async def get_gen_db_table_columns_by_name(self, table_name: str | None) -> List[GenTableColumnOutSchema]: + async def get_gen_db_table_columns_by_name(self, table_name: str | None) -> list[GenTableColumnOutSchema]: """ 根据业务表名称获取业务表字段列表信息。 @@ -385,7 +383,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen - table_name (str | None): 业务表名称。 返回: - - List[GenTableColumnOutSchema]: 业务表字段列表信息对象。 + - list[GenTableColumnOutSchema]: 业务表字段列表信息对象。 """ # 检查表名是否为空 if not table_name: @@ -460,7 +458,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen """ query = text(query_sql).bindparams(table_name=table_name) - result = await self.db.execute(query) + result = await self.auth.db.execute(query) rows = result.fetchall() if result else [] # 确保rows是可迭代对象 @@ -491,31 +489,31 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen # 确保即使出错也返回空列表而不是None raise - async def list_gen_table_column_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableColumnModel]: + async def list_gen_table_column_crud(self, search: dict | None = None, order_by: list | None = None, preload: list | None = None) -> Sequence[GenTableColumnModel]: """根据业务表字段查询业务表字段列表。 参数: - - search (Optional[Dict]): 查询参数,例如{"table_id": 1}。 - - order_by (Optional[List[Dict[str, str]]]): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数,例如{"table_id": 1}。 + - order_by (list | None): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[GenTableColumnModel]: 业务表字段列表信息对象序列。 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_gen_table_column_crud(self, data: GenTableColumnSchema) -> Optional[GenTableColumnModel]: + async def create_gen_table_column_crud(self, data: GenTableColumnSchema) -> GenTableColumnModel | None: """创建业务表字段。 参数: - data (GenTableColumnSchema): 业务表字段模型。 返回: - - Optional[GenTableColumnModel]: 业务表字段列表信息对象。 + - GenTableColumnModel | None: 业务表字段列表信息对象。 """ return await self.create(data=data) - async def update_gen_table_column_crud(self, id: int, data: GenTableColumnSchema) -> Optional[GenTableColumnModel]: + async def update_gen_table_column_crud(self, id: int, data: GenTableColumnSchema) -> GenTableColumnModel | None: """更新业务表字段。 参数: @@ -523,35 +521,35 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen - data (GenTableColumnSchema): 业务表字段模型。 返回: - - Optional[GenTableColumnModel]: 业务表字段列表信息对象。 + - GenTableColumnModel | None: 业务表字段列表信息对象。 """ # 将对象转换为字典,避免SQLAlchemy直接操作对象时出现的状态问题 data_dict = data.model_dump(exclude_unset=True) return await self.update(id=id, data=data_dict) - async def delete_gen_table_column_by_table_id_crud(self, table_ids: List[int]) -> None: + async def delete_gen_table_column_by_table_id_crud(self, table_ids: list[int]) -> None: """根据业务表ID批量删除业务表字段。 参数: - - table_ids (List[int]): 业务表ID列表。 + - table_ids (list[int]): 业务表ID列表。 返回: - None """ # 先查询出这些表ID对应的所有字段ID query = select(GenTableColumnModel.id).where(GenTableColumnModel.table_id.in_(table_ids)) - result = await self.db.execute(query) + result = await self.auth.db.execute(query) column_ids = [row[0] for row in result.fetchall()] # 如果有字段ID,则删除这些字段 if column_ids: await self.delete(ids=column_ids) - async def delete_gen_table_column_by_column_id_crud(self, column_ids: List[int]) -> None: + async def delete_gen_table_column_by_column_id_crud(self, column_ids: list[int]) -> None: """根据业务表字段ID批量删除业务表字段。 参数: - - column_ids (List[int]): 业务表字段ID列表。 + - column_ids (list[int]): 业务表字段ID列表。 返回: - None diff --git a/backend/app/api/v1/module_generator/gencode/model.py b/backend/app/api/v1/module_generator/gencode/model.py index a2ab21c7..c3ccbfae 100644 --- a/backend/app/api/v1/module_generator/gencode/model.py +++ b/backend/app/api/v1/module_generator/gencode/model.py @@ -5,25 +5,17 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship, validates from sqlalchemy.sql import expression from app.config.setting import settings -from app.core.base_model import ModelMixin +from app.core.base_model import ModelMixin, UserMixin from app.utils.common_util import SqlalchemyUtil -class GenTableModel(ModelMixin): +class GenTableModel(ModelMixin, UserMixin): """ 代码生成表 - - 数据隔离策略: - =========== - - 系统级生成: tenant_id=1 (平台管理员生成系统代码) - - 租户级生成: tenant_id>1 (租户管理员生成租户业务代码) - - 不需要customer_id: 代码生成是租户级功能,不需要客户隔离 - - 用于存储代码生成器的表结构配置 """ __tablename__: str = 'gen_table' __table_args__: dict[str, str] = ({'comment': '代码生成表'}) - __loader_options__: list[str] = ["columns", "created_by", "updated_by", "tenant"] + __loader_options__: list[str] = ["columns", "created_by", "updated_by"] table_name: Mapped[str] = mapped_column(String(200), nullable=False, default='', comment='表名称') table_comment: Mapped[str | None] = mapped_column(String(500), nullable=True, comment='表描述') @@ -71,7 +63,7 @@ class GenTableModel(ModelMixin): return class_name.strip() -class GenTableColumnModel(ModelMixin): +class GenTableColumnModel(ModelMixin, UserMixin): """ 代码生成表字段 @@ -83,7 +75,7 @@ class GenTableColumnModel(ModelMixin): """ __tablename__: str = 'gen_table_column' __table_args__: dict[str, str] = ({'comment': '代码生成表字段'}) - __loader_options__: list[str] = ["table", "created_by", "updated_by", "tenant"] + __loader_options__: list[str] = ["created_by", "updated_by"] # 数据库设计表字段 column_name: Mapped[str] = mapped_column(String(200), nullable=False, comment='列名称') diff --git a/backend/app/api/v1/module_generator/gencode/schema.py b/backend/app/api/v1/module_generator/gencode/schema.py index 495334e2..e794839f 100644 --- a/backend/app/api/v1/module_generator/gencode/schema.py +++ b/backend/app/api/v1/module_generator/gencode/schema.py @@ -1,6 +1,5 @@ # -*- coding:utf-8 -*- -from typing import List, Optional from pydantic import BaseModel, ConfigDict, Field, field_validator from fastapi import Query @@ -14,10 +13,47 @@ 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='表描述') + database_name: str | None = Field(default=None, description='数据库名称') + table_name: str | None = Field(default=None, description='表名称') + table_type: str | None = Field(default=None, description='表类型') + table_comment: str | None = Field(default=None, description='表描述') + + +class GenTableColumnSchema(BaseModel): + """代码生成业务表字段创建模型(原始字段+生成配置)。 + - 从根本上解决问题:所有字段都设置了合理的默认值,避免None值问题 + """ + model_config = ConfigDict(from_attributes=True) + + table_id: int = Field(default=0, description='归属表编号') + column_name: str = Field(default='', description='列名称') + column_comment: str | None = Field(default='', description='列描述') + column_type: str = Field(default='varchar(255)', description='列类型') + column_length: str | None = Field(default='', description='列长度') + column_default: str | None = Field(default='', description='列默认值') + is_pk: bool = Field(default=False, description='是否主键(True是 False否)') + is_increment: bool = Field(default=False, description='是否自增(True是 False否)') + is_nullable: bool = Field(default=True, description='是否允许为空(True是 False否)') + is_unique: bool = Field(default=False, description='是否唯一(True是 False否)') + python_type: str | None = Field(default='str', description='python类型') + python_field: str | None = Field(default='', description='python字段名') + is_insert: bool = Field(default=True, description='是否为插入字段(True是 False否)') + is_edit: bool = Field(default=True, description='是否编辑字段(True是 False否)') + is_list: bool = Field(default=True, description='是否列表字段(True是 False否)') + is_query: bool = Field(default=True, description='是否查询字段(True是 False否)') + query_type: str | None = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)') + html_type: str | None = Field(default='input', description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)') + dict_type: str | None = Field(default='', description='字典类型') + sort: int = Field(default=0, description='排序') + + +class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema): + """ + 业务表字段输出模型 + """ + model_config = ConfigDict(from_attributes=True) + + super_column: str | None = Field(default='0', description='是否为基类字段(1是 0否)') class GenTableSchema(BaseModel): @@ -30,18 +66,18 @@ class GenTableSchema(BaseModel): model_config = ConfigDict(from_attributes=True) table_name: str= Field(..., description='表名称') - table_comment: Optional[str] = Field(default=None, description='表描述') - class_name: Optional[str] = Field(default=None, 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='生成功能名') - sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名') - sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名') - parent_menu_id: Optional[int] = Field(default=None, description='所属父级分类,生成页面时候生成菜单使用') - description: Optional[str] = Field(default=None, max_length=255, description="描述") + table_comment: str | None = Field(default=None, description='表描述') + class_name: str | None = Field(default=None, description='实体类名称') + package_name: str | None = Field(default=None, description='生成包路径') + module_name: str | None = Field(default=None, description='生成模块名') + business_name: str | None = Field(default=None, description='生成业务名') + function_name: str | None = Field(default=None, description='生成功能名') + sub_table_name: str | None = Field(default=None, description='关联子表的表名') + sub_table_fk_name: str | None = Field(default=None, description='子表关联的外键名') + parent_menu_id: int | None = Field(default=None, description='所属父级分类,生成页面时候生成菜单使用') + description: str | None = Field(default=None, max_length=255, description="描述") - columns: Optional[List['GenTableColumnOutSchema']] = Field(default=None, description='表列信息') + columns: list['GenTableColumnOutSchema'] | None = Field(default=None, description='表列信息') @field_validator('table_name') @classmethod @@ -57,46 +93,9 @@ class GenTableOutSchema(GenTableSchema, BaseSchema): """ model_config = ConfigDict(from_attributes=True) - pk_column: Optional['GenTableColumnOutSchema'] = Field(default=None, description='主键信息') - sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息') - sub: Optional[bool] = Field(default=None, description='是否为子表') - - -class GenTableColumnSchema(BaseModel): - """代码生成业务表字段创建模型(原始字段+生成配置)。 - - 从根本上解决问题:所有字段都设置了合理的默认值,避免None值问题 - """ - model_config = ConfigDict(from_attributes=True) - - table_id: int = Field(default=0, description='归属表编号') - column_name: str = Field(default='', description='列名称') - column_comment: Optional[str] = Field(default='', description='列描述') - column_type: str = Field(default='varchar(255)', description='列类型') - column_length: Optional[str] = Field(default='', description='列长度') - column_default: Optional[str] = Field(default='', description='列默认值') - is_pk: bool = Field(default=False, description='是否主键(True是 False否)') - is_increment: bool = Field(default=False, description='是否自增(True是 False否)') - is_nullable: bool = Field(default=True, description='是否允许为空(True是 False否)') - is_unique: bool = Field(default=False, description='是否唯一(True是 False否)') - python_type: Optional[str] = Field(default='str', description='python类型') - python_field: Optional[str] = Field(default='', description='python字段名') - is_insert: bool = Field(default=True, description='是否为插入字段(True是 False否)') - is_edit: bool = Field(default=True, description='是否编辑字段(True是 False否)') - is_list: bool = Field(default=True, description='是否列表字段(True是 False否)') - is_query: bool = Field(default=True, description='是否查询字段(True是 False否)') - query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)') - html_type: Optional[str] = Field(default='input', description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)') - dict_type: Optional[str] = Field(default='', description='字典类型') - sort: int = Field(default=0, description='排序') - - -class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema): - """ - 业务表字段输出模型 - """ - model_config = ConfigDict(from_attributes=True) - - super_column: Optional[str] = Field(default='0', description='是否为基类字段(1是 0否)') + pk_column: GenTableColumnOutSchema | None = Field(default=None, description='主键信息') + sub_table: GenTableSchema | None = Field(default=None, description='子表信息') + sub: bool | None = Field(default=None, description='是否为子表') class GenTableQueryParam: @@ -107,8 +106,8 @@ class GenTableQueryParam: def __init__( self, - table_name: Optional[str] = Query(None, description="表名称"), - table_comment: Optional[str] = Query(None, description="表注释"), + table_name: str | None = Query(None, description="表名称"), + table_comment: str | None = Query(None, description="表注释"), ) -> None: # 模糊查询字段 self.table_name = table_name @@ -122,7 +121,7 @@ class GenTableColumnQueryParam: def __init__( self, - column_name: Optional[str] = Query(None, description="列名称"), + column_name: str | None = Query(None, description="列名称"), ) -> None: # 模糊查询字段:约定("like", 值)格式,便于CRUD解析 self.column_name = ("like", column_name) diff --git a/backend/app/api/v1/module_generator/gencode/service.py b/backend/app/api/v1/module_generator/gencode/service.py index 474bd244..39b43688 100644 --- a/backend/app/api/v1/module_generator/gencode/service.py +++ b/backend/app/api/v1/module_generator/gencode/service.py @@ -3,7 +3,7 @@ import io import os import zipfile -from typing import Any, List, Dict, Literal, Optional +from typing import Any from sqlglot.expressions import Add, Alter, Create, Delete, Drop, Expression, Insert, Table, TruncateTable, Update from sqlglot import parse as sqlglot_parse @@ -35,7 +35,7 @@ class GenTableService: @classmethod @handle_service_exception - async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> Dict: + async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> dict: """获取业务表详细信息(含字段与其他表列表)。 - 备注:优先解析`options`为`GenTableOptionSchema`,设置`parent_menu_id`等选项;保证`columns`与`tables`结构完整。 """ @@ -44,7 +44,7 @@ class GenTableService: @classmethod @handle_service_exception - async def get_gen_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam) -> List[Dict]: + async def get_gen_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam) -> list[dict]: """ 获取代码生成业务表列表信息。 @@ -53,7 +53,7 @@ class GenTableService: - search (GenTableQueryParam): 查询参数模型。 返回: - - List[Dict]: 包含业务表列表信息的字典列表。 + - list[dict]: 包含业务表列表信息的字典列表。 """ gen_table_list_result = await GenTableCRUD(auth=auth).get_gen_table_list(search) return [GenTableOutSchema.model_validate(obj).model_dump() for obj in gen_table_list_result] @@ -69,7 +69,7 @@ class GenTableService: @classmethod @handle_service_exception - async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: List[str]) -> List[GenTableOutSchema]: + async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_names: list[str]) -> list[GenTableOutSchema]: """根据表名称组获取数据库表信息。 - 校验:如有不存在的表名,抛出明确异常;返回统一的`GenTableOutSchema`列表。 """ @@ -90,7 +90,7 @@ class GenTableService: @classmethod @handle_service_exception - async def import_gen_table_service(cls, auth: AuthSchema, gen_table_list: List[GenTableOutSchema]) -> Literal[True] | None: + async def import_gen_table_service(cls, auth: AuthSchema, gen_table_list: list[GenTableOutSchema]) -> bool | None: """导入表结构到生成器(持久化并初始化列)。 - 备注:避免重复导入;为每列调用`GenUtils.init_column_field`填充默认属性,保留语义一致性。 """ @@ -135,7 +135,7 @@ class GenTableService: @classmethod @handle_service_exception - async def create_table_service(cls, auth: AuthSchema, sql: str) -> Literal[True] | None: + async def create_table_service(cls, auth: AuthSchema, sql: str) -> bool | None: """创建表结构并导入至代码生成模块。 - 校验:使用`sqlglot`确保仅包含`CREATE TABLE`语句;失败抛出明确异常。 - 唯一性检查:在创建前检查该表是否已存在于数据库中。 @@ -186,12 +186,12 @@ class GenTableService: raise CustomException(msg=f'创建表结构失败: {str(e)}') @classmethod - def __is_valid_create_table(cls, sql_statements: List[Expression | None]) -> bool: + def __is_valid_create_table(cls, sql_statements: list[Expression | None]) -> bool: """ 校验SQL语句是否为合法的建表语句。 参数: - - sql_statements (List[Expression | None]): SQL的AST列表。 + - sql_statements (list[Expression | None]): SQL的AST列表。 返回: - bool: 校验结果。 @@ -209,15 +209,15 @@ class GenTableService: return True @classmethod - def __get_table_names(cls, sql_statements: List[Expression | None]) -> List[str]: + def __get_table_names(cls, sql_statements: list[Expression | None]) -> list[str]: """ 获取SQL语句中所有的建表表名。 参数: - - sql_statements (List[Expression | None]): SQL的AST列表。 + - sql_statements (list[Expression | None]): SQL的AST列表。 返回: - - List[str]: 建表表名列表。 + - list[str]: 建表表名列表。 """ table_names = [] for sql_statement in sql_statements: @@ -229,7 +229,7 @@ class GenTableService: @classmethod @handle_service_exception - async def update_gen_table_service(cls, auth: AuthSchema, data: GenTableSchema, table_id: int) -> Dict[str, Any]: + async def update_gen_table_service(cls, auth: AuthSchema, data: GenTableSchema, table_id: int) -> dict[str, Any]: """编辑业务表信息(含选项与字段)。 - 备注:将`params`序列化写入`options`以持久化;仅更新存在`id`的列,避免误创建。 """ @@ -255,7 +255,7 @@ class GenTableService: @classmethod @handle_service_exception - async def delete_gen_table_service(cls, auth: AuthSchema, ids: List[int]) -> None: + async def delete_gen_table_service(cls, auth: AuthSchema, ids: list[int]) -> None: """删除业务表信息(先删字段,再删表)。""" # 验证ID列表非空 if not ids: @@ -284,13 +284,12 @@ class GenTableService: @classmethod @handle_service_exception - async def get_gen_table_all_service(cls, auth: AuthSchema) -> List[GenTableOutSchema]: + async def get_gen_table_all_service(cls, auth: AuthSchema) -> list[GenTableOutSchema]: """获取所有业务表信息(列表)。""" gen_table_all = await GenTableCRUD(auth=auth).get_gen_table_all() or [] result = [] for gen_table in gen_table_all: try: - # 简化转换,利用schema层的默认值处理None情况 table_out = GenTableOutSchema.model_validate(gen_table) result.append(table_out) except Exception as e: @@ -300,7 +299,7 @@ class GenTableService: @classmethod @handle_service_exception - async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> Dict[Any, Any]: + async def preview_code_service(cls, auth: AuthSchema, table_id: int) -> dict[str, Any]: """ 预览代码(根据模板渲染内存结果)。 - 备注:构建Jinja2上下文;根据模板类型与前端类型选择模板清单;返回文件名到内容映射。 @@ -354,7 +353,7 @@ class GenTableService: @classmethod @handle_service_exception - async def batch_gen_code_service(cls, auth: AuthSchema, table_names: List[str]) -> bytes: + async def batch_gen_code_service(cls, auth: AuthSchema, table_names: list[str]) -> bytes: """ 批量生成代码并打包为ZIP。 - 备注:内存生成并压缩,兼容多模板类型;供下载使用。 @@ -474,7 +473,7 @@ class GenTableService: gen_table.pk_column = gen_table.columns[0] @classmethod - async def __get_gen_render_info(cls, auth: AuthSchema, table_name: str) -> List[Any]: + async def __get_gen_render_info(cls, auth: AuthSchema, table_name: str) -> list[Any]: """ 获取生成代码渲染模板相关信息。 @@ -483,7 +482,7 @@ class GenTableService: - table_name (str): 业务表名称。 返回: - - List[Any]: [模板列表, 输出文件名列表, 渲染上下文, 业务表对象]。 + - list[Any]: [模板列表, 输出文件名列表, 渲染上下文, 业务表对象]。 异常: - CustomException: 当业务表不存在或数据转换失败时抛出。 @@ -502,7 +501,7 @@ class GenTableService: return [template_list, output_files, context, gen_table] @classmethod - def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> Optional[str]: + def __get_gen_path(cls, gen_table: GenTableOutSchema, template: str) -> str | None: """根据GenTableOutSchema对象和模板名称生成路径。""" try: file_name = Jinja2TemplateUtil.get_file_name(template, gen_table) @@ -527,7 +526,7 @@ class GenTableColumnService: @classmethod @handle_service_exception - async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[Dict[str, Any]]: + async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> list[dict[str, Any]]: """获取业务表字段列表信息(输出模型)。""" gen_table_column_list_result = await GenTableColumnCRUD(auth).list_gen_table_column_crud({"table_id": table_id}) result = [GenTableColumnOutSchema.model_validate(gen_table_column).model_dump() for gen_table_column in gen_table_column_list_result] diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/controller.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/controller.py.j2 index acccc8a1..4515f5c9 100644 --- a/backend/app/api/v1/module_generator/gencode/templates/python/controller.py.j2 +++ b/backend/app/api/v1/module_generator/gencode/templates/python/controller.py.j2 @@ -1,10 +1,10 @@ # -*- coding:utf-8 -*- -from fastapi import APIRouter, Depends, UploadFile, Body, Path +from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query from fastapi.responses import StreamingResponse, JSONResponse + from app.common.response import SuccessResponse, StreamResponse from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.api.v1.module_system.auth.schema import AuthSchema from app.core.base_params import PaginationQueryParam from app.utils.common_util import bytes2file_response @@ -15,7 +15,7 @@ from .service import {{ class_name }}Service from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema from .param import {{ class_name }}QueryParam -{{ class_name }}Router = APIRouter(route_class=OperationLogRoute, prefix='/{{ business_name }}', tags=["{{ function_name }}模块"]) +{{ class_name }}Router = APIRouter(prefix='/{{ business_name }}', tags=["{{ function_name }}模块"]) @{{ class_name }}Router.get("/detail/{id}", summary="获取{{ function_name }}详情", description="获取{{ function_name }}详情") async def get_{{ business_name }}_detail_controller( @@ -34,7 +34,7 @@ async def get_{{ business_name }}_list_controller( auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"])) ) -> JSONResponse: """查询{{ function_name }}列表接口(数据库分页)""" - result_dict = await {{ class_name }}Service.page_service( + result_dict = await {{ class_name }}Service.page_{{ business_name }}_service( auth=auth, page_no=page.page_no if page.page_no is not None else 1, page_size=page.page_size if page.page_size is not None else 10, @@ -92,7 +92,7 @@ async def export_{{ business_name }}_list_controller( ) -> StreamingResponse: """导出{{ function_name }}接口""" result_dict_list = await {{ class_name }}Service.list_{{ business_name }}_service(search=search, auth=auth) - export_result = await {{ class_name }}Service.batch_export_service(obj_list=result_dict_list) + export_result = await {{ class_name }}Service.batch_export_{{ business_name }}_service(obj_list=result_dict_list) log.info('导出{{ function_name }}成功') return StreamResponse( diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/crud.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/crud.py.j2 index 7170542c..d5e7eacd 100644 --- a/backend/app/api/v1/module_generator/gencode/templates/python/crud.py.j2 +++ b/backend/app/api/v1/module_generator/gencode/templates/python/crud.py.j2 @@ -1,6 +1,6 @@ # -*- coding:utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase from app.api.v1.module_system.auth.schema import AuthSchema @@ -20,34 +20,34 @@ class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}Creat """ super().__init__(model={{ class_name }}Model, auth=auth) - async def get_by_id_{{ business_name }}_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[{{ class_name }}Model]: + async def get_by_id_{{ business_name }}_crud(self, id: int, preload: list | None = None) -> Optional[{{ class_name }}Model]: """ 详情 参数: - id (int): 对象ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Optional[{{ class_name }}Model]: 模型实例或None """ return await self.get(id=id, preload=preload) - async def list_{{ business_name }}_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[{{ class_name }}Model]: + async def list_{{ business_name }}_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[{{ class_name }}Model]: """ 列表查询 参数: - - search (Optional[Dict]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数 + - order_by (list[dict] | None): 排序参数,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[{{ class_name }}Model]: 模型实例序列 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_{{ business_name }}_crud(self, data: {{ class_name }}CreateSchema) -> Optional[{{ class_name }}Model]: + async def create_{{ business_name }}_crud(self, data: {{ class_name }}CreateSchema) -> {{ class_name }}Model | None: """ 创建 @@ -55,11 +55,11 @@ class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}Creat - data ({{ class_name }}CreateSchema): 创建模型 返回: - - Optional[{{ class_name }}Model]: 模型实例或None + - {{ class_name }}Model | None: 模型实例或None """ return await self.create(data=data) - async def update_{{ business_name }}_crud(self, id: int, data: {{ class_name }}UpdateSchema) -> Optional[{{ class_name }}Model]: + async def update_{{ business_name }}_crud(self, id: int, data: {{ class_name }}UpdateSchema) -> {{ class_name }}Model | None: """ 更新 @@ -68,45 +68,45 @@ class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}Creat - data ({{ class_name }}UpdateSchema): 更新模型 返回: - - Optional[{{ class_name }}Model]: 模型实例或None + - {{ class_name }}Model | None: 模型实例或None """ return await self.update(id=id, data=data) - async def delete_{{ business_name }}_crud(self, ids: List[int]) -> None: + async def delete_{{ business_name }}_crud(self, ids: list[int]) -> None: """ 批量删除 参数: - - ids (List[int]): 对象ID列表 + - ids (list[int]): 对象ID列表 返回: - None """ return await self.delete(ids=ids) - async def set_available_{{ business_name }}_crud(self, ids: List[int], status: str) -> None: + async def set_available_{{ business_name }}_crud(self, ids: list[int], status: str) -> None: """ 批量设置可用状态 参数: - - ids (List[int]): 对象ID列表 - - status (bool): 可用状态 + - ids (list[int]): 对象ID列表 + - status (str): 可用状态 返回: - None """ return await self.set(ids=ids, status=status) - async def page_{{ business_name }}_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict: + async def page_{{ business_name }}_crud(self, offset: int, limit: int, order_by: list[dict] | None = None, search: dict | None = None, preload: list | None = None) -> Dict: """ 分页查询 参数: - offset (int): 偏移量 - limit (int): 每页数量 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - search (Optional[Dict]): 查询参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - order_by (list[dict] | None): 排序参数,未提供时使用模型默认项 + - search (dict | None): 查询参数,未提供时查询所有 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Dict: 分页数据 diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/model.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/model.py.j2 index ff136368..bebc84e0 100644 --- a/backend/app/api/v1/module_generator/gencode/templates/python/model.py.j2 +++ b/backend/app/api/v1/module_generator/gencode/templates/python/model.py.j2 @@ -6,23 +6,22 @@ {% if table.sub %} from sqlalchemy.orm import relationship {% endif %} -from typing import Optional from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class {{ class_name }}Model(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class {{ class_name }}Model(ModelMixin, UserMixin): """ {{ function_name }}表 """ __tablename__ = '{{ table_name }}' __table_args__ = {'comment': '{{ function_name }}'} - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] {% for column in columns %} - {% if column.column_name not in ['id', 'created_id', 'description', 'created_time', 'updated_time'] %} - {{ column.column_name }}: Mapped[Optional[{{ column.python_type }}]] = mapped_column({{ column.column_type|get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}') + {% if column.column_name not in ['id', 'uuid', 'status', 'description', 'created_time', 'updated_time', 'created_id', 'updated_id'] %} + {{ column.column_name }}: Mapped[{{ column.python_type }} | None] = mapped_column({{ column.column_type|get_sqlalchemy_type }}, {% if column.pk %}primary_key=True, {% endif %}{% if column.increment %}autoincrement=True, {% endif %}{% if column.required or column.pk %}nullable=False{% else %}nullable=True{% endif %}, comment='{{ column.column_comment }}') {% endif %} {% endfor %} diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/param.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/param.py.j2 deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/schema.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/schema.py.j2 index 6ac8f605..b4a84ac2 100644 --- a/backend/app/api/v1/module_generator/gencode/templates/python/schema.py.j2 +++ b/backend/app/api/v1/module_generator/gencode/templates/python/schema.py.j2 @@ -3,21 +3,20 @@ {% if table.sub %} from typing import List, Optional {% else %} -from typing import Optional {% endif %} from pydantic import BaseModel, ConfigDict, Field - from fastapi import Query + from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema class {{ class_name }}CreateSchema(BaseModel): """ {{ function_name }}新增模型 """ {% for column in columns %} - {% if column.column_name not in ['id', 'created_id', 'created_time', 'updated_time'] %} - {{ column.column_name }}: Optional[{{ column.python_type }}] = Field(default=None, description='{{ column.column_comment }}') + {% if column.column_name not in ['id', 'uuid', 'created_time', 'updated_time', 'created_id', 'updated_id'] %} + {{ column.column_name }}: {{ column.python_type }} | None = Field(default=None, description='{{ column.column_comment }}') {% endif %} {% endfor %} @@ -29,7 +28,7 @@ class {{ class_name }}UpdateSchema({{ class_name }}CreateSchema): ... -class {{ class_name }}OutSchema({{ class_name }}CreateSchema, BaseSchema): +class {{ class_name }}OutSchema({{ class_name }}CreateSchema, BaseSchema, UserBySchema): """ {{ function_name }}响应模型 """ @@ -51,8 +50,10 @@ class {{ class_name }}QueryParam: {{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"), {% endif %} {% endfor %} - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -69,7 +70,10 @@ class {{ class_name }}QueryParam: {% endif %} {% endfor %} self.created_id = created_id + self.updated_id = updated_id # 时间范围查询 if created_time and len(created_time) == 2: - self.created_time = ("between", (created_time[0], created_time[1])) \ No newline at end of file + self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_generator/gencode/templates/python/service.py.j2 b/backend/app/api/v1/module_generator/gencode/templates/python/service.py.j2 index d8f0f571..eb9285bf 100644 --- a/backend/app/api/v1/module_generator/gencode/templates/python/service.py.j2 +++ b/backend/app/api/v1/module_generator/gencode/templates/python/service.py.j2 @@ -1,7 +1,7 @@ # -*- coding:utf-8 -*- import io -from typing import Any, List, Dict, Optional +from typing import Any from fastapi import UploadFile import pandas as pd @@ -21,7 +21,7 @@ class {{ class_name }}Service: """ @classmethod - async def detail_{{ business_name }}_service(cls, auth: AuthSchema, id: int) -> Dict: + async def detail_{{ business_name }}_service(cls, auth: AuthSchema, id: int) -> dict: """详情""" obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id) if not obj: @@ -29,14 +29,14 @@ class {{ class_name }}Service: return {{ class_name }}OutSchema.model_validate(obj).model_dump() @classmethod - async def list_{{ business_name }}_service(cls, auth: AuthSchema, search: Optional[{{ class_name }}QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def list_{{ business_name }}_service(cls, auth: AuthSchema, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """列表查询""" search_dict = search.__dict__ if search else None obj_list = await {{ class_name }}CRUD(auth).list_{{ business_name }}_crud(search=search_dict, order_by=order_by) return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def page_{{ business_name }}_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[{{ class_name }}QueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict: + async def page_{{ business_name }}_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> dict: """分页查询(数据库分页)""" search_dict = search.__dict__ if search else {} order_by_list = order_by or [{'id': 'asc'}] @@ -50,7 +50,7 @@ class {{ class_name }}Service: return result @classmethod - async def create_{{ business_name }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> Dict: + async def create_{{ business_name }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> dict: """创建""" # 检查唯一性约束 {% for column in columns %} @@ -64,7 +64,7 @@ class {{ class_name }}Service: return {{ class_name }}OutSchema.model_validate(obj).model_dump() @classmethod - async def update_{{ business_name }}_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> Dict: + async def update_{{ business_name }}_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> dict: """更新""" # 检查数据是否存在 obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id) @@ -84,7 +84,7 @@ class {{ class_name }}Service: return {{ class_name }}OutSchema.model_validate(obj).model_dump() @classmethod - async def delete_{{ business_name }}_service(cls, auth: AuthSchema, ids: List[int]) -> None: + async def delete_{{ business_name }}_service(cls, auth: AuthSchema, ids: list[int]) -> None: """删除""" if len(ids) < 1: raise CustomException(msg='删除失败,删除对象不能为空') @@ -100,13 +100,13 @@ class {{ class_name }}Service: await {{ class_name }}CRUD(auth).set_available_{{ business_name }}_crud(ids=data.ids, status=data.status) @classmethod - async def batch_export_{{ business_name }}_service(cls, obj_list: List[Dict[str, Any]]) -> bytes: + async def batch_export_{{ business_name }}_service(cls, obj_list: list[dict]) -> bytes: """批量导出""" mapping_dict = { {% for column in columns %} '{{ column.column_name }}': '{{ column.column_comment }}', {% endfor %} - 'creator': '创建者', + 'updated_id': '更新者ID', } data = obj_list.copy() diff --git a/backend/app/api/v1/module_generator/gencode/tools/gen_util.py b/backend/app/api/v1/module_generator/gencode/tools/gen_util.py index 6c582f93..769f8eaa 100644 --- a/backend/app/api/v1/module_generator/gencode/tools/gen_util.py +++ b/backend/app/api/v1/module_generator/gencode/tools/gen_util.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import re -from typing import List from app.common.constant import GenConstant from app.utils.string_util import StringUtil @@ -154,13 +153,13 @@ class GenUtils: return StringUtil.convert_to_camel_case(table_name) @classmethod - def replace_first(cls, input_string: str, search_list: List[str]) -> str: + def replace_first(cls, input_string: str, search_list: list[str]) -> str: """ 批量替换前缀 参数: - input_string (str): 需要被替换的字符串。 - - search_list (List[str]): 可替换的字符串列表。 + - search_list (list[str]): 可替换的字符串列表。 返回: - str: 替换后的字符串。 @@ -202,7 +201,7 @@ class GenUtils: return 0 @classmethod - def split_column_type(cls, column_type: str) -> List[str]: + def split_column_type(cls, column_type: str) -> list[str]: """ 拆分列类型 @@ -210,7 +209,7 @@ class GenUtils: - column_type (str): 字段类型。 返回: - - List[str]: 拆分结果。 + - list[str]: 拆分结果。 """ if '(' in column_type and ')' in column_type: return column_type.split('(')[1].split(')')[0].split(',') @@ -221,8 +220,11 @@ class GenUtils: """ 将字符串转换为驼峰命名 - param text: 需要转换的字符串 - :return: 驼峰命名 + 参数: + - text (str): 需要转换的字符串 + + 返回: + - str: 驼峰命名 """ parts = text.split('_') return parts[0] + ''.join(word.capitalize() for word in parts[1:]) \ No newline at end of file diff --git a/backend/app/api/v1/module_generator/gencode/tools/jinja2_template_util.py b/backend/app/api/v1/module_generator/gencode/tools/jinja2_template_util.py index ce114f5d..7a286e84 100644 --- a/backend/app/api/v1/module_generator/gencode/tools/jinja2_template_util.py +++ b/backend/app/api/v1/module_generator/gencode/tools/jinja2_template_util.py @@ -3,7 +3,7 @@ from datetime import datetime from jinja2.environment import Environment from jinja2 import Environment, FileSystemLoader, Template -from typing import List, Any, Set +from typing import Any from app.common.constant import GenConstant from app.config.path_conf import TEMPLATE_DIR @@ -136,7 +136,6 @@ class Jinja2TemplateUtil: 'python/service.py.j2', 'python/crud.py.j2', 'python/schema.py.j2', - 'python/param.py.j2', 'python/model.py.j2', 'sql/sql.sql.j2', 'ts/api.ts.j2', @@ -173,7 +172,6 @@ class Jinja2TemplateUtil: 'service.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/service.py', 'crud.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/crud.py', 'model.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/model.py', - 'param.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/param.py', 'schema.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/schema.py', 'sql.sql.j2': f'{cls.BACKEND_PROJECT_PATH}/sql/menu/{module_name}/{business_name}.sql', 'api.ts.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/api/{module_name}/{business_name}.ts', @@ -275,16 +273,16 @@ class Jinja2TemplateUtil: return column_type @classmethod - def merge_same_imports(cls, imports: List[str], import_start: str) -> List[str]: + def merge_same_imports(cls, imports: list[str], import_start: str) -> list[str]: """ 合并相同的导入语句。 参数: - - imports (List[str]): 导入语句列表。 + - imports (list[str]): 导入语句列表。 - import_start (str): 导入语句的起始字符串。 返回: - - List[str]: 合并后的导入语句列表。 + - list[str]: 合并后的导入语句列表。 """ merged_imports = [] _imports = [] @@ -323,16 +321,16 @@ class Jinja2TemplateUtil: return ', '.join(dicts) @classmethod - def add_dicts(cls, dicts: Set[str], columns: List[GenTableColumnOutSchema]): + def add_dicts(cls, dicts: set[str], columns: list[GenTableColumnOutSchema]): """ 添加字典类型到集合。 参数: - - dicts (Set[str]): 字典类型集合。 - - columns (List[GenTableColumnOutSchema]): 字段列表。 + - dicts (set[str]): 字典类型集合。 + - columns (list[GenTableColumnOutSchema]): 字段列表。 返回: - - Set[str]: 更新后的字典类型集合。 + - set[str]: 更新后的字典类型集合。 """ for column in columns: super_column = column.super_column if column.super_column is not None else '0' diff --git a/backend/app/api/v1/module_monitor/cache/controller.py b/backend/app/api/v1/module_monitor/cache/controller.py index 4c812011..79e8b0ca 100644 --- a/backend/app/api/v1/module_monitor/cache/controller.py +++ b/backend/app/api/v1/module_monitor/cache/controller.py @@ -6,9 +6,9 @@ from redis.asyncio.client import Redis from app.common.response import SuccessResponse from app.core.exceptions import CustomException -from app.core.router_class import OperationLogRoute from app.core.dependencies import AuthPermission, redis_getter from app.core.logger import log +from app.core.router_class import OperationLogRoute from .service import CacheService diff --git a/backend/app/api/v1/module_monitor/cache/schema.py b/backend/app/api/v1/module_monitor/cache/schema.py index a74c2088..11c2c552 100644 --- a/backend/app/api/v1/module_monitor/cache/schema.py +++ b/backend/app/api/v1/module_monitor/cache/schema.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- +from typing import Any from pydantic import BaseModel, ConfigDict, Field -from typing import Any, Dict, List, Optional class CacheMonitorSchema(BaseModel): """缓存监控信息模型""" model_config = ConfigDict(from_attributes=True) - command_stats: List[Dict] = Field(default_factory=list, description='Redis命令统计信息') + command_stats: list[dict] = Field(default_factory=list, description='Redis命令统计信息') db_size: int = Field(default=0, description='Redis数据库中的Key总数') - info: Dict[str, Any] = Field(default_factory=dict, description='Redis服务器信息') + info: dict = Field(default_factory=dict, description='Redis服务器信息') class CacheInfoSchema(BaseModel): @@ -20,4 +20,4 @@ class CacheInfoSchema(BaseModel): cache_key: str = Field(..., description='缓存键名') cache_name: str = Field(..., description='缓存名称') cache_value: Any = Field(default=None, description='缓存值') - remark: Optional[str] = Field(default=None, description='备注说明') + remark: str | None = Field(default=None, description='备注说明') diff --git a/backend/app/api/v1/module_monitor/online/schema.py b/backend/app/api/v1/module_monitor/online/schema.py index cd163b5b..8ce2f70a 100644 --- a/backend/app/api/v1/module_monitor/online/schema.py +++ b/backend/app/api/v1/module_monitor/online/schema.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from pydantic import BaseModel, ConfigDict, Field -from typing import Optional from fastapi import Query from app.core.validator import DateTimeStr @@ -18,13 +17,12 @@ class OnlineOutSchema(BaseModel): session_id: str = Field(..., description='会话编号') user_id: int = Field(..., description='用户ID') user_name: str = Field(..., description='用户名') - tenant_name: Optional[str] = Field(default=None, description='租户名称') - ipaddr: Optional[str] = Field(default=None, description='登陆IP地址') - login_location: Optional[str] = Field(default=None, description='登录所属地') - os: Optional[str] = Field(default=None, description='操作系统') - browser: Optional[str] = Field(default=None, description='浏览器') - login_time: Optional[DateTimeStr] = Field(default=None, description='登录时间') - login_type: Optional[str] = Field(default=None, description='登录类型 PC端 | 移动端') + ipaddr: str | None = Field(default=None, description='登陆IP地址') + login_location: str | None = Field(default=None, description='登录所属地') + os: str | None = Field(default=None, description='操作系统') + browser: str | None = Field(default=None, description='浏览器') + login_time: DateTimeStr | None = Field(default=None, description='登录时间') + login_type: str | None = Field(default=None, description='登录类型 PC端 | 移动端') class OnlineQueryParam: @@ -32,9 +30,9 @@ class OnlineQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="登录名称"), - ipaddr: Optional[str] = Query(None, description="登陆IP地址"), - login_location: Optional[str] = Query(None, description="登录所属地"), + name: str | None = Query(None, description="登录名称"), + ipaddr: str | None = Query(None, description="登陆IP地址"), + login_location: str | None = Query(None, description="登录所属地"), ) -> None: # 模糊查询字段 diff --git a/backend/app/api/v1/module_monitor/online/service.py b/backend/app/api/v1/module_monitor/online/service.py index 48818147..833cb8e3 100644 --- a/backend/app/api/v1/module_monitor/online/service.py +++ b/backend/app/api/v1/module_monitor/online/service.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import json -from typing import Dict, List, Optional from redis.asyncio.client import Redis from app.common.enums import RedisInitKeyConfig @@ -16,16 +15,16 @@ class OnlineService: """在线用户管理模块服务层""" @classmethod - async def get_online_list_service(cls, redis: Redis, search: Optional[OnlineQueryParam] = None) -> List[Dict]: + async def get_online_list_service(cls, redis: Redis, search: OnlineQueryParam | None = None) -> list[dict]: """ 获取在线用户列表信息(支持分页和搜索) 参数: - redis (Redis): Redis异步客户端实例。 - - search (Optional[OnlineQueryParam]): 查询参数模型。 + - search (OnlineQueryParam | None): 查询参数模型。 返回: - - List[Dict]: 在线用户详情字典列表。 + - list[dict]: 在线用户详情字典列表。 """ keys = await RedisCURD(redis).get_keys(f"{RedisInitKeyConfig.ACCESS_TOKEN.key}:*") @@ -89,13 +88,13 @@ class OnlineService: @staticmethod - def _match_search_conditions(online_info: Dict, search: Optional[OnlineQueryParam]) -> bool: + def _match_search_conditions(online_info: dict, search: OnlineQueryParam | None = None) -> bool: """ 检查是否匹配搜索条件 参数: - - online_info (Dict): 在线用户信息字典。 - - search (Optional[OnlineQueryParam]): 查询参数模型。 + - online_info (dict): 在线用户信息字典。 + - search (OnlineQueryParam | None): 查询参数模型。 返回: - bool: 如果匹配则返回True,否则返回False。 diff --git a/backend/app/api/v1/module_monitor/resource/controller.py b/backend/app/api/v1/module_monitor/resource/controller.py index 47757c6e..d301afdb 100644 --- a/backend/app/api/v1/module_monitor/resource/controller.py +++ b/backend/app/api/v1/module_monitor/resource/controller.py @@ -2,14 +2,13 @@ from fastapi import APIRouter, Body, Depends, Query, Request, UploadFile, Form from fastapi.responses import JSONResponse, StreamingResponse, FileResponse -from typing import List, Optional from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.logger import log from .service import ResourceService @@ -70,7 +69,7 @@ async def get_directory_list_controller( async def upload_file_controller( file: UploadFile, request: Request, - target_path: Optional[str] = Form(None, description="目标目录路径") + target_path: str | None = Form(None, description="目标目录路径") ) -> JSONResponse: """ 上传文件 @@ -78,7 +77,7 @@ async def upload_file_controller( 参数: - file (UploadFile): 要上传的文件对象。 - request (Request): FastAPI请求对象,用于获取基础URL。 - - target_path (Optional[str]): 目标目录路径,默认为None。 + - target_path (str | None): 目标目录路径,默认为None。 返回: - JSONResponse: 包含上传文件信息的JSON响应。 @@ -136,13 +135,13 @@ async def download_file_controller( dependencies=[Depends(AuthPermission(["module_monitor:resource:delete"]))] ) async def delete_files_controller( - paths: List[str] = Body(..., description="文件路径列表") + paths: list[str] = Body(..., description="文件路径列表") ) -> JSONResponse: """ 删除文件 参数: - - paths (List[str]): 文件路径列表。 + - paths (list[str]): 文件路径列表。 返回: - JSONResponse: 包含删除结果的JSON响应。 diff --git a/backend/app/api/v1/module_monitor/resource/schema.py b/backend/app/api/v1/module_monitor/resource/schema.py index 213491cf..6528e03c 100644 --- a/backend/app/api/v1/module_monitor/resource/schema.py +++ b/backend/app/api/v1/module_monitor/resource/schema.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Optional, List from datetime import datetime from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from urllib.parse import urlparse @@ -16,9 +15,9 @@ class ResourceItemSchema(BaseModel): relative_path: str = Field(..., description="相对路径") is_file: bool = Field(..., description="是否为文件") is_dir: bool = Field(..., description="是否为目录") - size: Optional[int] = Field(None, description="文件大小(字节)") - created_time: Optional[datetime] = Field(None, description="创建时间") - modified_time: Optional[datetime] = Field(None, description="修改时间") + size: int | None = Field(None, description="文件大小(字节)") + created_time: datetime | None = Field(None, description="创建时间") + modified_time: datetime | None = Field(None, description="修改时间") is_hidden: bool = Field(False, description="是否为隐藏文件") @field_validator('file_url') @@ -55,7 +54,7 @@ class ResourceDirectorySchema(BaseModel): path: str = Field(..., description="目录路径") name: str = Field(..., description="目录名称") - items: List[ResourceItemSchema] = Field(default_factory=list, description="目录项") + items: list[ResourceItemSchema] = Field(default_factory=list, description="目录项") total_files: int = Field(0, description="文件总数") total_dirs: int = Field(0, description="目录总数") total_size: int = Field(0, description="总大小") @@ -146,8 +145,8 @@ class ResourceSearchQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="搜索关键词"), - path: Optional[str] = Query(None, description="目录路径"), + name: str | None = Query(None, description="搜索关键词"), + path: str | None = Query(None, description="目录路径"), ) -> None: # 模糊查询字段 diff --git a/backend/app/api/v1/module_monitor/resource/service.py b/backend/app/api/v1/module_monitor/resource/service.py index 7af2befa..78f8523e 100644 --- a/backend/app/api/v1/module_monitor/resource/service.py +++ b/backend/app/api/v1/module_monitor/resource/service.py @@ -3,7 +3,6 @@ import os import shutil from datetime import datetime -from typing import List, Dict, Any, Optional from pathlib import Path from urllib.parse import urlparse from fastapi import UploadFile @@ -48,12 +47,12 @@ class ResourceService: return str(settings.STATIC_ROOT) @classmethod - def _get_safe_path(cls, path: Optional[str] = None) -> str: + def _get_safe_path(cls, path: str | None = None) -> str: """ 获取安全的文件路径 参数: - - path (Optional[str]): 原始文件路径。 + - path (str | None): 原始文件路径。 返回: - str: 安全的文件路径。 @@ -123,13 +122,13 @@ class ResourceService: return False @classmethod - def _generate_http_url(cls, file_path: str, base_url: Optional[str] = None) -> str: + def _generate_http_url(cls, file_path: str, base_url: str | None = None) -> str: """ 生成文件的HTTP URL 参数: - file_path (str): 文件的绝对路径。 - - base_url (Optional[str]): 基础URL,用于生成完整URL。 + - base_url (str | None): 基础URL,用于生成完整URL。 返回: - str: 文件的HTTP URL。 @@ -145,7 +144,6 @@ class ResourceService: # 如果提供了base_url,使用它生成完整URL,否则使用settings.STATIC_URL if base_url: - from urllib.parse import urljoin # 修复URL生成逻辑 base_part = base_url.rstrip('/') static_part = settings.STATIC_URL.lstrip('/') @@ -159,16 +157,16 @@ class ResourceService: return http_url @classmethod - def _get_file_info(cls, file_path: str, base_url: Optional[str] = None) -> Dict[str, Any]: + def _get_file_info(cls, file_path: str, base_url: str | None = None) -> dict: """ 获取文件或目录的详细信息,如名称、大小、创建时间、修改时间、路径、深度、HTTP URL、是否隐藏、是否为目录等。 参数: - file_path (str): 文件或目录的路径。 - - base_url (Optional[str]): 基础URL,用于生成完整URL。 + - base_url (str | None): 基础URL,用于生成完整URL。 返回: - - Dict[str, Any]: 文件或目录的详细信息字典。 + - dict: 文件或目录的详细信息字典。 """ try: safe_path = cls._get_safe_path(file_path) @@ -220,17 +218,17 @@ class ResourceService: return {} @classmethod - async def get_directory_list_service(cls, path: Optional[str] = None, include_hidden: bool = False, base_url: Optional[str] = None) -> Dict: + async def get_directory_list_service(cls, path: str | None = None, include_hidden: bool = False, base_url: str | None = None) -> dict: """ 获取目录列表 参数: - - path (Optional[str]): 目录路径。如果未指定,将使用静态文件根目录。 + - path (str | None): 目录路径。如果未指定,将使用静态文件根目录。 - include_hidden (bool): 是否包含隐藏文件。 - - base_url (Optional[str]): 基础URL,用于生成完整URL。 + - base_url (str | None): 基础URL,用于生成完整URL。 返回: - - Dict: 包含目录列表和统计信息的字典。 + - dict: 包含目录列表和统计信息的字典。 """ try: # 如果没有指定路径,使用静态文件根目录 @@ -289,17 +287,17 @@ class ResourceService: raise CustomException(msg=f'获取目录列表失败: {str(e)}') @classmethod - async def get_resources_list_service(cls, search: Optional[ResourceSearchQueryParam] = None, order_by: Optional[str] = None, base_url: Optional[str] = None) -> List[Dict]: + async def get_resources_list_service(cls, search: ResourceSearchQueryParam | None = None, order_by: str | None = None, base_url: str | None = None) -> list[dict]: """ 搜索资源列表(用于分页和导出) 参数: - - search (Optional[ResourceSearchQueryParam]): 查询参数模型。 - - order_by (Optional[str]): 排序参数。 - - base_url (Optional[str]): 基础URL,用于生成完整URL。 + - search (ResourceSearchQueryParam | None): 查询参数模型。 + - order_by (str | None): 排序参数。 + - base_url (str | None): 基础URL,用于生成完整URL。 返回: - - List[Dict]: 资源详情字典列表。 + - list[dict]: 资源详情字典列表。 """ try: # 确定搜索路径 @@ -353,12 +351,12 @@ class ResourceService: raise CustomException(msg=f'搜索资源失败: {str(e)}') @classmethod - async def export_resource_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_resource_service(cls, data_list: list[dict]) -> bytes: """ 导出资源列表 参数: - - data_list (List[Dict[str, Any]]): 资源详情字典列表。 + - data_list (list[dict]): 资源详情字典列表。 返回: - bytes: Excel文件的二进制数据。 @@ -383,7 +381,7 @@ class ResourceService: return ExcelUtil.export_list2excel(list_data=export_data, mapping_dict=mapping_dict) @classmethod - async def _get_directory_stats(cls, path: str, include_hidden: bool = False) -> Dict[str, int]: + async def _get_directory_stats(cls, path: str, include_hidden: bool = False) -> dict[str, int]: """ 递归获取目录统计信息 @@ -392,7 +390,7 @@ class ResourceService: - include_hidden (bool): 是否包含隐藏文件。 返回: - - Dict[str, int]: 包含文件数、目录数和总大小的字典。 + - dict[str, int]: 包含文件数、目录数和总大小的字典。 """ stats = {'files': 0, 'dirs': 0, 'size': 0} @@ -419,16 +417,16 @@ class ResourceService: return stats @classmethod - def _sort_results(cls, results: List[Dict], order_by: Optional[str] = None) -> List[Dict]: + def _sort_results(cls, results: list[dict], order_by: str | None = None) -> list[dict]: """ 排序搜索结果 参数: - - results (List[Dict]): 资源详情字典列表。 - - order_by (Optional[str]): 排序参数。 + - results (list[dict]): 资源详情字典列表。 + - order_by (str | None): 排序参数。 返回: - - List[Dict]: 排序后的资源详情字典列表。 + - list[dict]: 排序后的资源详情字典列表。 """ try: # 默认按名称升序排序 @@ -469,17 +467,17 @@ class ResourceService: return results @classmethod - async def upload_file_service(cls, file: UploadFile, target_path: Optional[str] = None, base_url: Optional[str] = None) -> Dict: + async def upload_file_service(cls, file: UploadFile, target_path: str | None = None, base_url: str | None = None) -> dict: """ 上传文件到指定目录 参数: - file (UploadFile): 上传的文件对象。 - - target_path (Optional[str]): 目标目录路径。 - - base_url (Optional[str]): 基础URL,用于生成完整URL。 + - target_path (str | None): 目标目录路径。 + - base_url (str | None): 基础URL,用于生成完整URL。 返回: - - Dict: 包含文件信息的字典。 + - dict: 包含文件信息的字典。 """ if not file or not file.filename: raise CustomException(msg="请选择要上传的文件") @@ -542,13 +540,13 @@ class ResourceService: raise CustomException(msg=f"文件上传失败: {str(e)}") @classmethod - async def download_file_service(cls, file_path: str, base_url: Optional[str] = None) -> str: + async def download_file_service(cls, file_path: str, base_url: str | None = None) -> str: """ 下载文件(返回本地文件系统路径) 参数: - file_path (str): 文件路径(可为相对路径、绝对路径或完整URL)。 - - base_url (Optional[str]): 基础URL,用于生成完整URL(不再直接返回URL)。 + - base_url (str | None): 基础URL,用于生成完整URL(不再直接返回URL)。 返回: - str: 本地文件系统路径。 @@ -573,12 +571,12 @@ class ResourceService: raise CustomException(msg=f"下载文件失败: {str(e)}") @classmethod - async def delete_file_service(cls, paths: List[str]) -> None: + async def delete_file_service(cls, paths: list[str]) -> None: """ 删除文件或目录 参数: - - paths (List[str]): 文件或目录路径列表。 + - paths (list[str]): 文件或目录路径列表。 返回: - None @@ -606,7 +604,7 @@ class ResourceService: raise CustomException(msg=f"删除失败 {path}: {str(e)}") @classmethod - async def batch_delete_service(cls, paths: List[str]) -> Dict[str, List[str]]: + async def batch_delete_service(cls, paths: list[str]) -> dict[str, list[str]]: """ 批量删除文件或目录 diff --git a/backend/app/api/v1/module_monitor/server/controller.py b/backend/app/api/v1/module_monitor/server/controller.py index 2d2ad782..cd6b31a2 100644 --- a/backend/app/api/v1/module_monitor/server/controller.py +++ b/backend/app/api/v1/module_monitor/server/controller.py @@ -6,8 +6,8 @@ from fastapi.responses import JSONResponse from app.common.response import SuccessResponse from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute from app.core.logger import log +from app.core.router_class import OperationLogRoute from .service import ServerService diff --git a/backend/app/api/v1/module_monitor/server/schema.py b/backend/app/api/v1/module_monitor/server/schema.py index 381aaa46..2a83aad6 100644 --- a/backend/app/api/v1/module_monitor/server/schema.py +++ b/backend/app/api/v1/module_monitor/server/schema.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from pydantic import BaseModel, ConfigDict, Field -from typing import List class CpuInfoSchema(BaseModel): @@ -77,4 +76,4 @@ class ServerMonitorSchema(BaseModel): mem: MemoryInfoSchema = Field(description="内存信息") py: PyInfoSchema = Field(description="Python运行信息") sys: SysInfoSchema = Field(description="系统信息") - disks: List[DiskInfoSchema] = Field(default_factory=list, description="磁盘信息") + disks: list[DiskInfoSchema] = Field(default_factory=list, description="磁盘信息") diff --git a/backend/app/api/v1/module_monitor/server/service.py b/backend/app/api/v1/module_monitor/server/service.py index b30f298a..152aa047 100644 --- a/backend/app/api/v1/module_monitor/server/service.py +++ b/backend/app/api/v1/module_monitor/server/service.py @@ -5,7 +5,6 @@ import psutil import socket import time from pathlib import Path -from typing import List, Dict from app.utils.common_util import bytes2human @@ -23,7 +22,7 @@ class ServerService: """服务监控模块服务层""" @classmethod - async def get_server_monitor_info_service(cls) -> Dict: + async def get_server_monitor_info_service(cls) -> dict: """ 获取服务器监控信息 @@ -118,12 +117,12 @@ class ServerService: ) @classmethod - def _get_disk_info(cls) -> List[DiskInfoSchema]: + def _get_disk_info(cls) -> list[DiskInfoSchema]: """ 获取磁盘信息 返回: - - List[DiskInfoSchema]: 磁盘信息模型列表。 + - list[DiskInfoSchema]: 磁盘信息模型列表。 """ disk_info = [] for partition in psutil.disk_partitions(): diff --git a/backend/app/api/v1/module_system/auth/schema.py b/backend/app/api/v1/module_system/auth/schema.py index 9ede9a84..eb3ed068 100644 --- a/backend/app/api/v1/module_system/auth/schema.py +++ b/backend/app/api/v1/module_system/auth/schema.py @@ -21,7 +21,6 @@ class JWTPayloadSchema(BaseModel): sub: str = Field(..., description='用户登录信息') is_refresh: bool = Field(default=False, description='是否刷新token') exp: datetime | int = Field(..., description='过期时间') - tenant_id: int | None = Field(default=None, description='租户ID') @model_validator(mode='after') def validate_fields(self): diff --git a/backend/app/api/v1/module_system/auth/service.py b/backend/app/api/v1/module_system/auth/service.py index 024cdc78..be4e4ad8 100644 --- a/backend/app/api/v1/module_system/auth/service.py +++ b/backend/app/api/v1/module_system/auth/service.py @@ -2,7 +2,7 @@ import json import uuid -from typing import Dict, Union, NewType +from typing import NewType from fastapi import Request from redis.asyncio.client import Redis from sqlalchemy.ext.asyncio import AsyncSession @@ -140,7 +140,7 @@ class LoginService: now = datetime.now() # 记录租户信息到日志 - log.info(f"用户ID: {user.id}, 用户名: {user.username}, 租户ID: {user.tenant_id} 正在生成JWT令牌") + log.info(f"用户ID: {user.id}, 用户名: {user.username} 正在生成JWT令牌") # 生成会话信息 session_info=OnlineOutSchema( @@ -148,7 +148,6 @@ class LoginService: user_id=user.id, name=user.name, user_name=user.username, - tenant_name=user.tenant.name if user.tenant else None, # 显式包含租户名称 ipaddr=request_ip, login_location=login_location, os=user_agent.os.family, @@ -224,7 +223,7 @@ class LoginService: raise CustomException(msg="刷新token失败,用户不存在") # 记录刷新令牌时的租户信息 - log.info(f"用户ID: {user.id}, 用户名: {user.username}, 租户ID: {user.tenant_id} 正在刷新JWT令牌") + log.info(f"用户ID: {user.id}, 用户名: {user.username} 正在刷新JWT令牌") # 设置新的 token access_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) @@ -236,15 +235,13 @@ class LoginService: access_token = create_access_token(payload=JWTPayloadSchema( sub=session_info_json, is_refresh=False, - exp=now + access_expires, - tenant_id=user.tenant_id + exp=now + access_expires )) refresh_token_new = create_access_token(payload=JWTPayloadSchema( sub=session_info_json, is_refresh=True, - exp=now + refresh_expires, - tenant_id=user.tenant_id + exp=now + refresh_expires )) # 覆盖写入 Redis @@ -302,7 +299,7 @@ class CaptchaService: """验证码服务""" @classmethod - async def get_captcha_service(cls, redis: Redis) -> Dict[str, Union[CaptchaKey, CaptchaBase64]]: + async def get_captcha_service(cls, redis: Redis) -> dict[str, CaptchaKey | CaptchaBase64]: """ 获取验证码 @@ -310,7 +307,7 @@ class CaptchaService: - redis (Redis): Redis客户端对象 返回: - - Dict[str, Union[CaptchaKey, CaptchaBase64]]: 包含验证码key和base64图片的字典 + - dict[str, CaptchaKey | CaptchaBase64]: 包含验证码key和base64图片的字典 异常: - CustomException: 验证码服务未启用时抛出异常 diff --git a/backend/app/api/v1/module_system/customer/__init__.py b/backend/app/api/v1/module_system/customer/__init__.py deleted file mode 100644 index 633f8661..00000000 --- a/backend/app/api/v1/module_system/customer/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- - diff --git a/backend/app/api/v1/module_system/customer/controller.py b/backend/app/api/v1/module_system/customer/controller.py deleted file mode 100644 index a74813f4..00000000 --- a/backend/app/api/v1/module_system/customer/controller.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- - -from fastapi import APIRouter, Body, Depends, Path, UploadFile -from fastapi.responses import JSONResponse, StreamingResponse -import urllib.parse - -from app.common.response import StreamResponse, SuccessResponse -from app.utils.common_util import bytes2file_response -from app.core.base_params import PaginationQueryParam -from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute -from app.core.base_schema import BatchSetAvailable -from app.core.logger import log - -from app.api.v1.module_system.auth.schema import AuthSchema -from .service import CustomerService -from .schema import ( - CustomerCreateSchema, - CustomerUpdateSchema, - CustomerQueryParam -) - - -CustomerRouter = APIRouter(route_class=OperationLogRoute, prefix="/customer", tags=["客户模块"]) - -@CustomerRouter.get("/detail/{id}", summary="获取客户详情", description="获取客户详情") -async def get_obj_detail_controller( - id: int = Path(..., description="客户ID"), - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:query"])) -) -> JSONResponse: - """ - 获取客户详情 - - 参数: - - id (int): 客户ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含客户详情的JSON响应 - """ - result_dict = await CustomerService.detail_service(id=id, auth=auth) - log.info(f"获取客户详情成功 {id}") - return SuccessResponse(data=result_dict, msg="获取客户详情成功") - -@CustomerRouter.get("/list", summary="查询客户列表", description="查询客户列表") -async def get_obj_list_controller( - page: PaginationQueryParam = Depends(), - search: CustomerQueryParam = Depends(), - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:query"])) -) -> JSONResponse: - """ - 查询客户列表 - - 参数: - - page (PaginationQueryParam): 分页查询参数 - - search (CustomerQueryParam): 查询参数 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含客户列表分页信息的JSON响应 - """ - # 使用数据库分页而不是应用层分页 - result_dict = await CustomerService.page_service( - auth=auth, - page_no=page.page_no if page.page_no is not None else 1, - page_size=page.page_size if page.page_size is not None else 10, - search=search, - order_by=page.order_by - ) - log.info("查询客户列表成功") - return SuccessResponse(data=result_dict, msg="查询客户列表成功") - -@CustomerRouter.post("/create", summary="创建客户", description="创建客户") -async def create_obj_controller( - data: CustomerCreateSchema, - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:create"])) -) -> JSONResponse: - """ - 创建客户 - - 参数: - - data (CustomerCreateSchema): 客户创建模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含创建客户详情的JSON响应 - """ - result_dict = await CustomerService.create_service(auth=auth, data=data) - log.info(f"创建客户成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="创建客户成功") - -@CustomerRouter.put("/update/{id}", summary="修改客户", description="修改客户") -async def update_obj_controller( - data: CustomerUpdateSchema, - id: int = Path(..., description="客户ID"), - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:update"])) -) -> JSONResponse: - """ - 修改客户 - - 参数: - - data (CustomerUpdateSchema): 客户更新模型 - - id (int): 客户ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含修改客户详情的JSON响应 - """ - result_dict = await CustomerService.update_service(auth=auth, id=id, data=data) - log.info(f"修改客户成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="修改客户成功") - -@CustomerRouter.delete("/delete", summary="删除客户", description="删除客户") -async def delete_obj_controller( - ids: list[int] = Body(..., description="ID列表"), - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:delete"])) -) -> JSONResponse: - """ - 删除客户 - - 参数: - - ids (list[int]): 客户ID列表 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含删除客户详情的JSON响应 - """ - await CustomerService.delete_service(auth=auth, ids=ids) - log.info(f"删除客户成功: {ids}") - return SuccessResponse(msg="删除客户成功") - -@CustomerRouter.patch("/available/setting", summary="批量修改客户状态", description="批量修改客户状态") -async def batch_set_available_obj_controller( - data: BatchSetAvailable, - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:patch"])) -) -> JSONResponse: - """ - 批量修改客户状态 - - 参数: - - data (BatchSetAvailable): 批量修改客户状态模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含批量修改客户状态详情的JSON响应 - """ - await CustomerService.set_available_service(auth=auth, data=data) - log.info(f"批量修改客户状态成功: {data.ids}") - return SuccessResponse(msg="批量修改客户状态成功") - -@CustomerRouter.post('/export', summary="导出客户", description="导出客户") -async def export_obj_list_controller( - search: CustomerQueryParam = Depends(), - auth: AuthSchema = Depends(AuthPermission(["module_system:customer:export"])) -) -> StreamingResponse: - """ - 导出客户 - - 参数: - - search (CustomerQueryParam): 查询参数 - - auth (AuthSchema): 认证信息模型 - - 返回: - - StreamingResponse: 包含客户列表的Excel文件流响应 - """ - result_dict_list = await CustomerService.list_service(search=search, auth=auth) - export_result = await CustomerService.batch_export_service(obj_list=result_dict_list) - log.info('导出客户成功') - - return StreamResponse( - data=bytes2file_response(export_result), - media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers={ - 'Content-Disposition': 'attachment; filename=example.xlsx' - } - ) - -@CustomerRouter.post('/import', summary="导入客户", description="导入客户") -async def import_obj_list_controller( - file: UploadFile, - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:import"])) -) -> JSONResponse: - """ - 导入租户 - - 参数: - - file (UploadFile): 导入的Excel文件 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含导入客户详情的JSON响应 - """ - batch_import_result = await CustomerService.batch_import_service(file=file, auth=auth, update_support=True) - log.info(f"导入客户成功: {batch_import_result}") - return SuccessResponse(data=batch_import_result, msg="导入租户成功") - -@CustomerRouter.post('/download/template', summary="获取客户导入模板", description="获取客户导入模板", dependencies=[Depends(AuthPermission(["module_system:customer:download"]))]) -async def export_obj_template_controller() -> StreamingResponse: - """ - 获取租户导入模板 - - 返回: - - StreamingResponse: 包含租户导入模板的Excel文件流响应 - """ - example_import_template_result = await CustomerService.import_template_download_service() - log.info('获取客户导入模板成功') - - return StreamResponse( - data=bytes2file_response(example_import_template_result), - media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers={ - 'Content-Disposition': f'attachment; filename={urllib.parse.quote("客户导入模板.xlsx")}', - 'Access-Control-Expose-Headers': 'Content-Disposition' - } - ) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/customer/crud.py b/backend/app/api/v1/module_system/customer/crud.py deleted file mode 100644 index c32112c7..00000000 --- a/backend/app/api/v1/module_system/customer/crud.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Dict, List, Optional, Sequence, Union, Any - -from app.core.base_crud import CRUDBase - -from app.api.v1.module_system.auth.schema import AuthSchema -from .model import CustomerModel -from .schema import CustomerCreateSchema, CustomerUpdateSchema, CustomerOutSchema - - -class CustomerCRUD(CRUDBase[CustomerModel, CustomerCreateSchema, CustomerUpdateSchema]): - """客户数据层""" - - def __init__(self, auth: AuthSchema) -> None: - """ - 初始化CRUD数据层 - - 参数: - - auth (AuthSchema): 认证信息模型 - """ - super().__init__(model=CustomerModel, auth=auth) - - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[CustomerModel]: - """ - 详情 - - 参数: - - id (int): 客户ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Optional[CustomerModel]: 客户模型实例或None - """ - return await self.get(id=id, preload=preload) - - async def list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[CustomerModel]: - """ - 列表查询 - - 参数: - - search (Optional[Dict]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Sequence[TenantModel]: 租户模型实例序列 - """ - return await self.list(search=search, order_by=order_by, preload=preload) - - async def create_crud(self, data: CustomerCreateSchema) -> Optional[CustomerModel]: - """ - 创建 - - 参数: - - data (CustomerCreateSchema): 客户创建模型 - - 返回: - - Optional[CustomerModel]: 客户模型实例或None - """ - return await self.create(data=data) - - async def update_crud(self, id: int, data: CustomerUpdateSchema) -> Optional[CustomerModel]: - """ - 更新 - - 参数: - - id (int): 客户ID - - data (CustomerUpdateSchema): 客户更新模型 - - 返回: - - Optional[CustomerModel]: 客户模型实例或None - """ - return await self.update(id=id, data=data) - - async def delete_crud(self, ids: List[int]) -> None: - """ - 批量删除 - - 参数: - - ids (List[int]): 客户ID列表 - - 返回: - - None - """ - return await self.delete(ids=ids) - - async def set_available_crud(self, ids: List[int], status: str) -> None: - """ - 批量设置可用状态 - - 参数: - - ids (List[int]): 客户ID列表 - - status (bool): 可用状态 - - 返回: - - None - """ - return await self.set(ids=ids, status=status) - - async def page_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict: - """ - 分页查询 - - 参数: - - offset (int): 偏移量 - - limit (int): 每页数量 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - search (Optional[Dict]): 查询参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Dict: 分页数据 - """ - order_by_list = order_by or [{'id': 'asc'}] - search_dict = search or {} - - return await self.page( - offset=offset, - limit=limit, - order_by=order_by_list, - search=search_dict, - out_schema=CustomerOutSchema, - preload=preload - ) diff --git a/backend/app/api/v1/module_system/customer/model.py b/backend/app/api/v1/module_system/customer/model.py deleted file mode 100644 index cfa399e2..00000000 --- a/backend/app/api/v1/module_system/customer/model.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import TYPE_CHECKING -from sqlalchemy import String -from sqlalchemy.orm import Mapped, mapped_column, relationship, validates - -from app.core.base_model import ModelMixin, UserMixin, TenantMixin -if TYPE_CHECKING: - from app.api.v1.module_system.user.model import UserModel - - -class CustomerModel(ModelMixin, UserMixin, TenantMixin): - """ - 客户表 - - 客户是租户下的业务实体,用于业务数据的二级隔离: - - 一个租户可以有多个客户 - - 客户用户(user.customer_id>0)只能访问其所属客户的数据 - - 客户表需要tenant_id(属于哪个租户) - - 客户表不需要customer_id(客户不属于客户,避免循环) - - 典型场景: - - 代理商系统: 租户=总公司, 客户=各地代理商 - - SaaS平台: 租户=企业, 客户=企业下的子公司/部门 - """ - __tablename__: str = 'sys_customer' - __table_args__: dict[str, str] = ({'comment': '客户表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant"] - - name: Mapped[str] = mapped_column(String(64), nullable=False, comment='客户名称') - code: Mapped[str] = mapped_column(String(20), nullable=False, index=True, comment='客户编码') - - users: Mapped[list["UserModel"]] = relationship( - back_populates="customer", - foreign_keys="UserModel.customer_id", - lazy="selectin" - ) - - - @validates('name') - def validate_name(self, key: str, name: str) -> str: - """验证名称不为空""" - if not name or not name.strip(): - raise ValueError('名称不能为空') - return name - - @validates('code') - def validate_code(self, key: str, code: str) -> str: - """验证编码格式校验""" - if not code or not code.strip(): - raise ValueError('编码不能为空') - if not code.isalnum(): - raise ValueError('编码只能包含字母和数字') - return code diff --git a/backend/app/api/v1/module_system/customer/schema.py b/backend/app/api/v1/module_system/customer/schema.py deleted file mode 100644 index e01d9b62..00000000 --- a/backend/app/api/v1/module_system/customer/schema.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator -from fastapi import Query - -from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema - - -class CustomerCreateSchema(BaseModel): - """新增模型""" - name: str = Field(..., max_length=64, description='客户名称') - code: Optional[str] = Field(default=None, max_length=20, description='客户编码') - status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") - - @field_validator('name') - @classmethod - def _validate_name(cls, v: str) -> str: - v = v.strip() - if not v: - raise ValueError('名称不能为空') - return v - - @model_validator(mode='after') - def _after_validation(self): - """ - 核心业务规则校验 - """ - # 长度校验:名称最小长度 - if len(self.name) < 2 or len(self.name) > 64: - raise ValueError('名称长度必须在2-50个字符之间') - # 格式校验:名称只能包含字母、数字、下划线和中划线 - if not self.name.isalnum() and not all(c in '-_' for c in self.name): - raise ValueError('名称只能包含字母、数字、下划线和中划线') - - return self - - -class CustomerUpdateSchema(CustomerCreateSchema): - """更新模型""" - ... - - -class CustomerOutSchema(CustomerCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): - """响应模型""" - model_config = ConfigDict(from_attributes=True) - - -class CustomerQueryParam: - """客户查询参数""" - - def __init__( - self, - name: Optional[str] = Query(None, description="客户名称"), - status: Optional[str] = Query(None, description="状态用(True:启用 False:禁用)"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), - ) -> None: - - # 模糊查询字段 - self.name = ("like", name) - - # 精确查询字段 - self.created_id = created_id - self.status = status - - # 时间范围查询 - if created_time and len(created_time) == 2: - self.created_time = ("between", (created_time[0], created_time[1])) diff --git a/backend/app/api/v1/module_system/customer/service.py b/backend/app/api/v1/module_system/customer/service.py deleted file mode 100644 index 70f0372f..00000000 --- a/backend/app/api/v1/module_system/customer/service.py +++ /dev/null @@ -1,312 +0,0 @@ -# -*- coding: utf-8 -*- - -import io -from typing import Any, List, Dict, Optional -from fastapi import UploadFile -import pandas as pd - -from app.api.v1.module_system.tenant.crud import TenantCRUD -from app.core.base_schema import BatchSetAvailable -from app.core.exceptions import CustomException -from app.utils.excel_util import ExcelUtil -from app.core.logger import log - -from app.api.v1.module_system.auth.schema import AuthSchema -from .schema import CustomerCreateSchema, CustomerUpdateSchema, CustomerOutSchema, CustomerQueryParam -from .crud import CustomerCRUD - - -class CustomerService: - """ - 客户管理模块服务层 - """ - - @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> Dict: - """ - 详情 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 客户ID - - 返回: - - Dict: 客户模型实例字典 - """ - obj = await CustomerCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg="该数据不存在") - return CustomerOutSchema.model_validate(obj).model_dump() - - @classmethod - async def list_service(cls, auth: AuthSchema, search: Optional[CustomerQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: - """ - 列表查询 - - 参数: - - auth (AuthSchema): 认证信息模型 - - search (Optional[CustomerQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - 返回: - - List[Dict]: 客户模型实例字典列表 - """ - search_dict = search.__dict__ if search else None - obj_list = await CustomerCRUD(auth).list_crud(search=search_dict, order_by=order_by) - return [CustomerOutSchema.model_validate(obj).model_dump() for obj in obj_list] - - @classmethod - async def page_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[CustomerQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict: - """ - 分页查询 - - 参数: - - auth (AuthSchema): 认证信息模型 - - page_no (int): 页码 - - page_size (int): 每页数量 - - search (Optional[CustomerQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - 返回: - - Dict: 分页数据 - """ - search_dict = search.__dict__ if search else {} - order_by_list = order_by or [{'id': 'asc'}] - offset = (page_no - 1) * page_size - - result = await CustomerCRUD(auth).page_crud( - offset=offset, - limit=page_size, - order_by=order_by_list, - search=search_dict - ) - return result - - @classmethod - async def create_service(cls, auth: AuthSchema, data: CustomerCreateSchema) -> Dict: - """ - 创建 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (CustomerCreateSchema): 客户创建模型 - - 返回: - - Dict: 客户模型实例字典 - """ - obj = await CustomerCRUD(auth).get(name=data.name) - if obj: - raise CustomException(msg='创建失败,名称已存在') - obj = await CustomerCRUD(auth).get(code=data.code) - if obj: - raise CustomException(msg='创建失败,编码已存在') - obj = await CustomerCRUD(auth).create_crud(data=data) - return CustomerOutSchema.model_validate(obj).model_dump() - - @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: CustomerUpdateSchema) -> Dict: - """ - 更新 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 客户ID - - data (CustomerUpdateSchema): 客户更新模型 - - 返回: - - Dict: 客户模型实例字典 - """ - # 检查数据是否存在 - obj = await CustomerCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg='更新失败,该数据不存在') - - # 检查名称是否重复 - exist_obj = await CustomerCRUD(auth).get(name=data.name) - if exist_obj and exist_obj.id != id: - raise CustomException(msg='更新失败,名称重复') - - obj = await CustomerCRUD(auth).update_crud(id=id, data=data) - return CustomerOutSchema.model_validate(obj).model_dump() - - @classmethod - async def delete_service(cls, auth: AuthSchema, ids: List[int]) -> None: - """ - 删除 - - 参数: - - auth (AuthSchema): 认证信息模型 - - ids (List[int]): 客户ID列表 - - 返回: - - None - """ - if len(ids) < 1: - raise CustomException(msg='删除失败,删除对象不能为空') - - # 检查所有要删除的数据是否存在 - for id in ids: - obj = await CustomerCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg=f'删除失败,ID为{id}的数据不存在') - - await CustomerCRUD(auth).delete_crud(ids=ids) - - @classmethod - async def set_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None: - """ - 批量设置状态 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (BatchSetAvailable): 批量设置状态模型 - - 返回: - - None - """ - await CustomerCRUD(auth).set_available_crud(ids=data.ids, status=data.status) - - @classmethod - async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes: - """ - 批量导出 - - 参数: - - obj_list (List[Dict[str, Any]]): 客户模型实例字典列表 - - 返回: - - bytes: Excel文件字节流 - """ - mapping_dict = { - 'id': '编号', - 'name': '名称', - 'code': '编码', - 'status': '状态', - 'description': '备注', - 'created_time': '创建时间', - 'updated_time': '更新时间', - 'creator': '创建者', - 'code': '编码', - } - - # 复制数据并转换状态 - data = obj_list.copy() - for item in data: - # 处理状态 - item['status'] = '启用' if item.get('status') == '0' 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=data, mapping_dict=mapping_dict) - - @classmethod - async def batch_import_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str: - """ - 批量导入 - - 参数: - - auth (AuthSchema): 认证信息模型 - - file (UploadFile): 上传的Excel文件 - - update_support (bool): 是否支持更新存在数据 - - 返回: - - str: 导入结果信息 - """ - - header_dict = { - '名称': 'name', - '状态': 'status', - '描述': 'description' - } - - try: - # 读取Excel文件 - contents = await file.read() - df = pd.read_excel(io.BytesIO(contents)) - await file.close() - - if df.empty: - raise CustomException(msg="导入文件为空") - - # 检查表头是否完整 - missing_headers = [header for header in header_dict.keys() if header not in df.columns] - if missing_headers: - raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}") - - # 重命名列名 - df.rename(columns=header_dict, inplace=True) - - # 验证必填字段 - required_fields = ['name', 'status'] - for field in required_fields: - missing_rows = df[df[field].isnull()].index.tolist() - raise CustomException(msg=f"{[k for k,v in header_dict.items() if v == field][0]}不能为空,第{[i+1 for i in missing_rows]}行") - - error_msgs = [] - success_count = 0 - count = 0 - - # 处理每一行数据 - for index, row in df.iterrows(): - count += 1 - try: - # 数据转换前的类型检查 - try: - status = True if row['status'] == '正常' else False - except ValueError: - error_msgs.append(f"第{count}行: 状态必须是'正常'或'停用'") - continue - - # 构建用户数据 - data = { - "name": str(row['name']), - "status": status, - "description": str(row['description']), - } - - # 处理用户导入 - exists_obj = await TenantCRUD(auth).get(name=data["name"]) - if exists_obj: - if update_support: - await TenantCRUD(auth).update(id=exists_obj.id, data=data) - success_count += 1 - else: - error_msgs.append(f"第{count}行: 对象 {data['name']} 已存在") - else: - await TenantCRUD(auth).create(data=data) - success_count += 1 - - except Exception as e: - error_msgs.append(f"第{count}行: {str(e)}") - continue - - # 返回详细的导入结果 - result = f"成功导入 {success_count} 条数据" - if error_msgs: - result += "\n错误信息:\n" + "\n".join(error_msgs) - return result - - except Exception as e: - log.error(f"批量导入用户失败: {str(e)}") - raise CustomException(msg=f"导入失败: {str(e)}") - - @classmethod - async def import_template_download_service(cls) -> bytes: - """ - 下载导入模板 - - 返回: - - bytes: Excel文件字节流 - """ - header_list = ['名称', '状态', '描述'] - selector_header_list = ['状态'] - option_list = [{'状态': ['正常', '停用']}] - return ExcelUtil.get_excel_template( - header_list=header_list, - selector_header_list=selector_header_list, - option_list=option_list - ) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/dept/controller.py b/backend/app/api/v1/module_system/dept/controller.py index 2789f1b0..e6d48e54 100644 --- a/backend/app/api/v1/module_system/dept/controller.py +++ b/backend/app/api/v1/module_system/dept/controller.py @@ -4,10 +4,10 @@ from fastapi import APIRouter, Body, Depends, Path from fastapi.responses import JSONResponse from app.common.response import SuccessResponse -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 log +from app.core.router_class import OperationLogRoute from ..auth.schema import AuthSchema from .service import DeptService diff --git a/backend/app/api/v1/module_system/dept/crud.py b/backend/app/api/v1/module_system/dept/crud.py index b84c318d..b1446521 100644 --- a/backend/app/api/v1/module_system/dept/crud.py +++ b/backend/app/api/v1/module_system/dept/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase @@ -17,13 +17,13 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]): self.auth = auth super().__init__(model=DeptModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DeptModel]: + async def get_by_id_crud(self, id: int, preload: list | None = None) -> DeptModel | None: """ 根据 id 获取部门信息。 参数: - id (int): 部门 ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - DeptModel | None: 部门信息,未找到返回 None。 @@ -33,48 +33,48 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]): return None return obj - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DeptModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[DeptModel]: """ 获取部门列表。 参数: - - search (Dict | None): 搜索条件。 - - order_by (List[Dict[str, str]] | None): 排序字段列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 搜索条件。 + - order_by (list[dict] | None): 排序字段列表。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[DeptModel]: 部门列表。 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def get_tree_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DeptModel]: + async def get_tree_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[DeptModel]: """ 获取部门树形列表。 参数: - - search (Dict | None): 搜索条件。 - - order_by (List[Dict[str, str]] | None): 排序字段列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 搜索条件。 + - order_by (list[dict] | None): 排序字段列表。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[DeptModel]: 部门树形列表。 """ return await self.tree_list(search=search, order_by=order_by, children_attr='children', preload=preload) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置部门可用状态。 参数: - - ids (List[int]): 部门 ID 列表。 - - status (bool): 可用状态。 + - ids (list[int]): 部门 ID 列表。 + - status (str): 可用状态。 返回: - None """ await self.set(ids=ids, status=status) - async def get_name_crud(self, id: int) -> Optional[str]: + async def get_name_crud(self, id: int) -> str | None: """ 根据 id 获取部门名称。 diff --git a/backend/app/api/v1/module_system/dept/model.py b/backend/app/api/v1/module_system/dept/model.py index 4b774694..34db9048 100644 --- a/backend/app/api/v1/module_system/dept/model.py +++ b/backend/app/api/v1/module_system/dept/model.py @@ -4,25 +4,19 @@ from typing import TYPE_CHECKING from sqlalchemy import String, Integer, ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column -from app.core.base_model import ModelMixin, TenantMixin +from app.core.base_model import ModelMixin if TYPE_CHECKING: from app.api.v1.module_system.role.model import RoleModel from app.api.v1.module_system.user.model import UserModel -class DeptModel(ModelMixin, TenantMixin): +class DeptModel(ModelMixin): """ 部门模型 - - 部门是租户级别的组织架构,用于实现数据权限控制: - - 部门属于租户(tenant_id必填) - - 部门不属于客户(customer_id不需要) - - 支持无限层级嵌套的树形结构 """ __tablename__: str = "sys_dept" __table_args__: dict[str, str] = ({'comment': '部门表'}) - __loader_options__: list[str] = ["tenant"] name: Mapped[str] = mapped_column(String(40), nullable=False, comment="部门名称") order: Mapped[int] = mapped_column(Integer, nullable=False, default=999, comment="显示排序") @@ -39,7 +33,7 @@ class DeptModel(ModelMixin, TenantMixin): index=True, comment="父级部门ID" ) - # 关联关系 (继承自UserMixin和TenantMixin) + # 关联关系 parent: Mapped["DeptModel | None"] = relationship( back_populates='children', remote_side="DeptModel.id", diff --git a/backend/app/api/v1/module_system/dept/schema.py b/backend/app/api/v1/module_system/dept/schema.py index c173834e..0b8ef77e 100644 --- a/backend/app/api/v1/module_system/dept/schema.py +++ b/backend/app/api/v1/module_system/dept/schema.py @@ -1,24 +1,23 @@ # -*- coding: utf-8 -*- -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, TenantSchema +from app.core.base_schema import BaseSchema class DeptCreateSchema(BaseModel): """部门创建模型""" name: str = Field(..., max_length=40, description="部门名称") order: int = Field(default=1, ge=0, description="显示顺序") - code: Optional[str] = Field(default=None, max_length=60, description="部门编码") - leader: Optional[str] = Field(default=None, max_length=20, description="部门负责人") - phone: Optional[str] = Field(default=None, max_length=11, description="手机") - email: Optional[str] = Field(default=None, max_length=64, description="邮箱") - parent_id: Optional[int] = Field(default=None, ge=0, description="父部门ID") + code: str | None = Field(default=None, max_length=60, description="部门编码") + leader: str | None = Field(default=None, max_length=20, description="部门负责人") + phone: str | None = Field(default=None, max_length=11, description="手机") + email: str | None = Field(default=None, max_length=64, description="邮箱") + parent_id: int | None = Field(default=None, ge=0, description="父部门ID") status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="备注说明") + description: str | None = Field(default=None, max_length=255, description="备注说明") @field_validator('name') @classmethod @@ -30,7 +29,7 @@ class DeptCreateSchema(BaseModel): @field_validator('code') @classmethod - def validate_code(cls, value: Optional[str]): + def validate_code(cls, value: str | None): if value is None: return value v = value.strip() @@ -47,11 +46,11 @@ class DeptUpdateSchema(DeptCreateSchema): ... -class DeptOutSchema(DeptCreateSchema, BaseSchema, TenantSchema): +class DeptOutSchema(DeptCreateSchema, BaseSchema): """部门响应模型""" model_config = ConfigDict(from_attributes=True) - parent_name: Optional[str] = Field(default=None, max_length=40, description="父部门名称") + parent_name: str | None = Field(default=None, max_length=64, description="父部门名称") class DeptQueryParam: @@ -59,9 +58,10 @@ class DeptQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="部门名称"), - status: Optional[str] = Query(None, description="部门状态(True正常 False停用)"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="部门名称"), + status: str | None = Query(None, description="部门状态(True正常 False停用)"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: # 模糊查询字段 @@ -73,3 +73,5 @@ class DeptQueryParam: # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/dept/service.py b/backend/app/api/v1/module_system/dept/service.py index 87263092..9940c4ff 100644 --- a/backend/app/api/v1/module_system/dept/service.py +++ b/backend/app/api/v1/module_system/dept/service.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from typing import List, Dict, Optional - from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException from app.utils.common_util import ( @@ -28,7 +26,7 @@ class DeptService: """ @classmethod - async def get_dept_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_dept_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取部门详情。 @@ -37,7 +35,7 @@ class DeptService: - id (int): 部门 ID。 返回: - - Dict: 部门详情对象。 + - dict: 部门详情对象。 """ dept = await DeptCRUD(auth).get_by_id_crud(id=id) result = DeptOutSchema.model_validate(dept).model_dump() @@ -48,17 +46,17 @@ class DeptService: return result @classmethod - async def get_dept_tree_service(cls, auth: AuthSchema, search: Optional[DeptQueryParam]= None, order_by: Optional[List[Dict]] = None) -> List[Dict]: + async def get_dept_tree_service(cls, auth: AuthSchema, search: DeptQueryParam | None= None, order_by: list[dict] | None = None) -> list[dict]: """ 获取部门树形列表。 参数: - auth (AuthSchema): 认证对象。 - search (DeptQueryParam | None): 查询参数对象。 - - order_by (List[Dict] | None): 排序参数。 + - order_by (list[dict] | None): 排序参数。 返回: - - List[Dict]: 部门树形列表对象。 + - list[dict]: 部门树形列表对象。 """ # 使用树形结构查询,预加载children关系 dept_list = await DeptCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by) @@ -68,7 +66,7 @@ class DeptService: return traversal_to_tree(dept_dict_list) @classmethod - async def create_dept_service(cls, auth: AuthSchema, data: DeptCreateSchema) -> Dict: + async def create_dept_service(cls, auth: AuthSchema, data: DeptCreateSchema) -> dict: """ 创建部门。 @@ -77,7 +75,7 @@ class DeptService: - data (DeptCreateSchema): 部门创建对象。 返回: - - Dict: 新创建的部门对象。 + - dict: 新创建的部门对象。 异常: - CustomException: 当部门已存在时抛出。 @@ -95,7 +93,7 @@ class DeptService: return DeptOutSchema.model_validate(dept).model_dump() @classmethod - async def update_dept_service(cls, auth: AuthSchema, id:int, data: DeptUpdateSchema) -> Dict: + async def update_dept_service(cls, auth: AuthSchema, id:int, data: DeptUpdateSchema) -> dict: """ 更新部门。 @@ -105,7 +103,7 @@ class DeptService: - data (DeptUpdateSchema): 部门更新对象。 返回: - - Dict: 更新后的部门对象。 + - dict: 更新后的部门对象。 异常: - CustomException: 当部门不存在或名称重复时抛出。 @@ -123,14 +121,14 @@ class DeptService: return DeptOutSchema.model_validate(dept).model_dump() @classmethod - async def _check_circular_reference(cls, auth: AuthSchema, parent_id: int, id: Optional[int] = None) -> None: + async def _check_circular_reference(cls, auth: AuthSchema, parent_id: int, id: int | None = None) -> None: """ 检测部门层级循环引用 参数: - auth (AuthSchema): 认证对象 - parent_id (int): 父部门ID - - id (Optional[int]): 当前部门ID(用于更新时检测) + - id (int | None): 当前部门ID(用于更新时检测) 异常: - CustomException: 当检测到循环引用时抛出 diff --git a/backend/app/api/v1/module_system/dict/controller.py b/backend/app/api/v1/module_system/dict/controller.py index fa171e45..5a0f7259 100644 --- a/backend/app/api/v1/module_system/dict/controller.py +++ b/backend/app/api/v1/module_system/dict/controller.py @@ -9,9 +9,9 @@ from app.common.response import StreamResponse, SuccessResponse from app.core.base_params import PaginationQueryParam from app.core.base_schema import BatchSetAvailable from app.core.dependencies import AuthPermission, redis_getter -from app.core.router_class import OperationLogRoute from app.core.logger import log from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from ..auth.schema import AuthSchema diff --git a/backend/app/api/v1/module_system/dict/crud.py b/backend/app/api/v1/module_system/dict/crud.py index 4b13a2d6..5bf79e7e 100644 --- a/backend/app/api/v1/module_system/dict/crud.py +++ b/backend/app/api/v1/module_system/dict/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase from app.api.v1.module_system.dict.model import DictDataModel, DictTypeModel @@ -21,30 +21,30 @@ class DictTypeCRUD(CRUDBase[DictTypeModel, DictTypeCreateSchema, DictTypeUpdateS self.auth = auth super().__init__(model=DictTypeModel, auth=auth) - async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DictTypeModel]: + async def get_obj_by_id_crud(self, id: int, preload: list | None = None) -> DictTypeModel | None: """ 获取数据字典类型详情 参数: - id (int): 数据字典类型ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[DictTypeModel]: 数据字典类型模型,如果不存在则为None + - DictTypeModel | None: 数据字典类型模型,如果不存在则为None """ # 添加默认预加载字典数据关系 if preload is None: preload = [] return await self.get(id=id, preload=preload) - async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DictTypeModel]: + async def get_obj_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[DictTypeModel]: """ 获取数据字典类型列表 参数: - - search (Optional[Dict]): 查询参数,默认值为None - - order_by (Optional[List[Dict[str, str]]]): 排序参数,默认值为None - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数,默认值为None + - order_by (list[dict] | None): 排序参数,默认值为None + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[DictTypeModel]: 数据字典类型模型序列 @@ -54,7 +54,7 @@ class DictTypeCRUD(CRUDBase[DictTypeModel, DictTypeCreateSchema, DictTypeUpdateS preload = [] return await self.list(search=search, order_by=order_by, preload=preload) - async def create_obj_crud(self, data: DictTypeCreateSchema) -> Optional[DictTypeModel]: + async def create_obj_crud(self, data: DictTypeCreateSchema) -> DictTypeModel | None: """ 创建数据字典类型 @@ -62,11 +62,11 @@ class DictTypeCRUD(CRUDBase[DictTypeModel, DictTypeCreateSchema, DictTypeUpdateS - data (DictTypeCreateSchema): 数据字典类型创建模型 返回: - - Optional[DictTypeModel]: 创建的数据字典类型模型,如果创建失败则为None + - DictTypeModel | None: 创建的数据字典类型模型,如果创建失败则为None """ return await self.create(data=data) - async def update_obj_crud(self, id: int, data: DictTypeUpdateSchema) -> Optional[DictTypeModel]: + async def update_obj_crud(self, id: int, data: DictTypeUpdateSchema) -> DictTypeModel | None: """ 更新数据字典类型 @@ -75,36 +75,36 @@ class DictTypeCRUD(CRUDBase[DictTypeModel, DictTypeCreateSchema, DictTypeUpdateS - data (DictTypeUpdateSchema): 数据字典类型更新模型 返回: - - Optional[DictTypeModel]: 更新的数据字典类型模型,如果更新失败则为None + - DictTypeModel | None: 更新的数据字典类型模型,如果更新失败则为None """ return await self.update(id=id, data=data) - async def delete_obj_crud(self, ids: List[int]) -> None: + async def delete_obj_crud(self, ids: list[int]) -> None: """ 删除数据字典类型 参数: - - ids (List[int]): 数据字典类型ID列表 + - ids (list[int]): 数据字典类型ID列表 返回: - None """ return await self.delete(ids=ids) - async def set_obj_available_crud(self, ids: List[int], status: str) -> None: + async def set_obj_available_crud(self, ids: list[int], status: str) -> None: """ 设置数据字典类型的可用状态 参数: - - ids (List[int]): 数据字典类型ID列表 - - status (bool): 可用状态,True表示可用,False表示不可用 + - ids (list[int]): 数据字典类型ID列表 + - status (str): 可用状态,0表示正常,1表示停用 返回: - None """ return await self.set(ids=ids, status=status) - async def batch_delete_obj_crud(self, ids: List[int]) -> int: + async def batch_delete_obj_crud(self, ids: list[int]) -> int: """ 批量删除数据字典类型 @@ -131,30 +131,30 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS self.auth = auth super().__init__(model=DictDataModel, auth=auth) - async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DictDataModel]: + async def get_obj_by_id_crud(self, id: int, preload: list | None = None) -> DictDataModel | None: """ 获取数据字典数据详情 参数: - id (int): 数据字典数据ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[DictDataModel]: 数据字典数据模型,如果不存在则为None + - DictDataModel | None: 数据字典数据模型,如果不存在则为None """ # 添加默认预加载字典类型关系 if preload is None: preload = [] return await self.get(id=id, preload=preload) - async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DictDataModel]: + async def get_obj_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[DictDataModel]: """ 获取数据字典数据列表 参数: - - search (Optional[Dict]): 查询参数,默认值为None - - order_by (Optional[List[Dict[str, str]]]): 排序参数,默认值为None - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数,默认值为None + - order_by (list[dict] | None): 排序参数,默认值为None + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[DictDataModel]: 数据字典数据模型序列 @@ -164,7 +164,7 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS preload = [] return await self.list(search=search, order_by=order_by, preload=preload) - async def create_obj_crud(self, data: DictDataCreateSchema) -> Optional[DictDataModel]: + async def create_obj_crud(self, data: DictDataCreateSchema) -> DictDataModel | None: """ 创建数据字典数据 @@ -172,11 +172,11 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS - data (DictDataCreateSchema): 数据字典数据创建模型 返回: - - Optional[DictDataModel]: 创建的数据字典数据模型,如果创建失败则为None + - DictDataModel | None: 创建的数据字典数据模型,如果创建失败则为None """ return await self.create(data=data) - async def update_obj_crud(self, id: int, data: DictDataUpdateSchema) -> Optional[DictDataModel]: + async def update_obj_crud(self, id: int, data: DictDataUpdateSchema) -> DictDataModel | None: """ 更新数据字典数据 @@ -185,36 +185,36 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS - data (DictDataUpdateSchema): 数据字典数据更新模型 返回: - - Optional[DictDataModel]: 更新的数据字典数据模型,如果更新失败则为None + - DictDataModel | None: 更新的数据字典数据模型,如果更新失败则为None """ return await self.update(id=id, data=data) - async def delete_obj_crud(self, ids: List[int]) -> None: + async def delete_obj_crud(self, ids: list[int]) -> None: """ 删除数据字典数据 参数: - - ids (List[int]): 数据字典数据ID列表 + - ids (list[int]): 数据字典数据ID列表 返回: - None """ return await self.delete(ids=ids) - async def set_obj_available_crud(self, ids: List[int], status: str) -> None: + async def set_obj_available_crud(self, ids: list[int], status: str) -> None: """ 设置数据字典数据的可用状态 参数: - - ids (List[int]): 数据字典数据ID列表 - - status (bool): 可用状态,True表示可用,False表示不可用 + - ids (list[int]): 数据字典数据ID列表 + - status (str): 可用状态,0表示正常,1表示停用 返回: - None """ return await self.set(ids=ids, status=status) - async def batch_delete_obj_crud(self, ids: List[int], exclude_system: bool = True) -> int: + async def batch_delete_obj_crud(self, ids: list[int], exclude_system: bool = True) -> int: """ 批量删除数据字典数据 @@ -239,13 +239,13 @@ class DictDataCRUD(CRUDBase[DictDataModel, DictDataCreateSchema, DictDataUpdateS await self.delete(ids=ids) return len(ids) - async def get_obj_list_by_dict_type_crud(self, dict_type: str, status: Optional[str] = "0") -> Sequence[DictDataModel]: + async def get_obj_list_by_dict_type_crud(self, dict_type: str, status: str | None = "0") -> Sequence[DictDataModel]: """ 根据字典类型获取字典数据列表 参数: - dict_type (str): 字典类型 - - status (Optional[str]): 状态过滤,None表示不过滤 + - status (str | None): 状态过滤,None表示不过滤 返回: - Sequence[DictDataModel]: 数据字典数据模型序列 diff --git a/backend/app/api/v1/module_system/dict/schema.py b/backend/app/api/v1/module_system/dict/schema.py index db04a3f4..8a618aa2 100644 --- a/backend/app/api/v1/module_system/dict/schema.py +++ b/backend/app/api/v1/module_system/dict/schema.py @@ -2,7 +2,6 @@ import re from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator -from typing import Optional from fastapi import Query from app.core.validator import DateTimeStr @@ -14,10 +13,10 @@ class DictTypeCreateSchema(BaseModel): 字典类型表对应pydantic模型 """ - dict_name: str = Field(..., min_length=1, max_length=100, description='字典名称') + dict_name: str = Field(..., min_length=1, max_length=64, description='字典名称') dict_type: str = Field(..., min_length=1, max_length=100, description='字典类型') - status: Optional[str] = Field(default=None, description='状态(1正常 0停用)') - description: Optional[str] = Field(default=None, max_length=255, description="描述") + status: str = Field(default='0', description='状态(0正常 1停用)') + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator('dict_name') def validate_dict_name(cls, value: str): @@ -50,10 +49,11 @@ class DictTypeQueryParam: def __init__( self, - dict_name: Optional[str] = Query(None, description="字典名称", max_length=100, example="用户"), - dict_type: Optional[str] = Query(None, description="字典类型", max_length=100, example="sys_user"), - status: Optional[str] = Query(None, description="状态(1正常 0停用)", example=True), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + dict_name: str | None = Query(default=None, description="字典名称", max_length=100, example="用户"), + dict_type: str | None = Query(default=None, description="字典类型", max_length=100, example="sys_user"), + status: str | None = Query(default=None, description="状态(0正常 1停用)", example=True), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: super().__init__() @@ -67,6 +67,8 @@ class DictTypeQueryParam: # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) class DictDataCreateSchema(BaseModel): @@ -78,11 +80,11 @@ class DictDataCreateSchema(BaseModel): dict_value: str = Field(..., max_length=100, description='字典键值') dict_type: str = Field(..., max_length=100, description='字典类型') dict_type_id: int = Field(..., description='字典类型ID') - css_class: Optional[str] = Field(default=None, max_length=100, description='样式属性(其他样式扩展)') - list_class: Optional[str] = Field(default=None, description='表格回显样式') - is_default: Optional[bool] = Field(default=None, description='是否默认(Y是 N否)') - status: Optional[str] = Field(default=None, description='状态(1正常 0停用)') - description: Optional[str] = Field(default=None, max_length=255, description="描述") + css_class: str | None = Field(default=None, max_length=100, description='样式属性(其他样式扩展)') + list_class: str | None = Field(default=None, description='表格回显样式') + is_default: bool = Field(default=False, description='是否默认(True是 False否)') + status: str = Field(default='0', description='状态(0正常 1停用)') + description: str | None = Field(default=None, max_length=255, description="描述") @model_validator(mode='after') def validate_after(self): @@ -118,11 +120,12 @@ class DictDataQueryParam: def __init__( self, - dict_label: Optional[str] = Query(None, description="字典标签", max_length=100, example="正常"), - dict_type: Optional[str] = Query(None, description="字典类型", max_length=100, example="sys_user_status"), - dict_type_id: Optional[int] = Query(None, description="字典类型ID", example=1), - status: Optional[str] = Query(None, description="状态(1正常 0停用)", example=True), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + dict_label: str | None = Query(default=None, description="字典标签", max_length=100, example="正常"), + dict_type: str | None = Query(default=None, description="字典类型", max_length=100, example="sys_user_status"), + dict_type_id: int | None = Query(default=None, description="字典类型ID", example=1), + status: str | None = Query(default=None, description="状态(0正常 1停用)", example=True), + created_time: list[DateTimeStr] | None = Query(default=None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(default=None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: # 模糊查询字段 @@ -136,3 +139,5 @@ class DictDataQueryParam: # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_system/dict/service.py b/backend/app/api/v1/module_system/dict/service.py index b3ef30ef..aa9eb126 100644 --- a/backend/app/api/v1/module_system/dict/service.py +++ b/backend/app/api/v1/module_system/dict/service.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import json -from typing import Any, List, Dict, Optional from redis.asyncio.client import Redis from app.common.enums import RedisInitKeyConfig @@ -31,7 +30,7 @@ class DictTypeService: """ @classmethod - async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取数据字典类型详情 @@ -40,29 +39,29 @@ class DictTypeService: - id (int): 数据字典类型ID 返回: - - Dict: 数据字典类型详情字典 + - dict: 数据字典类型详情字典 """ obj = await DictTypeCRUD(auth).get_obj_by_id_crud(id=id) return DictTypeOutSchema.model_validate(obj).model_dump() @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[DictTypeQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_obj_list_service(cls, auth: AuthSchema, search: DictTypeQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取数据字典类型列表 参数: - auth (AuthSchema): 认证信息模型 - search (DictTypeQueryParam | None): 搜索条件模型 - - order_by (List[Dict[str, str]] | None): 排序字段列表 + - order_by (list[dict] | None): 排序字段列表 返回: - - List[Dict]: 数据字典类型详情字典列表 + - 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 - async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: DictTypeCreateSchema) -> Dict: + async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: DictTypeCreateSchema) -> dict: """ 创建数据字典类型 @@ -72,7 +71,7 @@ class DictTypeService: - data (DictTypeCreateSchema): 数据字典类型创建模型 返回: - - Dict: 数据字典类型详情字典 + - dict: 数据字典类型详情字典 """ exist_obj = await DictTypeCRUD(auth).get(dict_name=data.dict_name) if exist_obj: @@ -96,7 +95,7 @@ class DictTypeService: return new_obj_dict @classmethod - async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: DictTypeUpdateSchema) -> Dict: + async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: DictTypeUpdateSchema) -> dict: """ 更新数据字典类型 @@ -107,7 +106,7 @@ class DictTypeService: - data (DictTypeUpdateSchema): 数据字典类型更新模型 返回: - - Dict: 数据字典类型详情字典 + - dict: 数据字典类型详情字典 """ exist_obj = await DictTypeCRUD(auth).get_obj_by_id_crud(id=id) if not exist_obj: @@ -210,12 +209,12 @@ class DictTypeService: await DictTypeCRUD(auth).set_obj_available_crud(ids=data.ids, status=data.status) @classmethod - async def export_obj_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_obj_service(cls, data_list: list[dict]) -> bytes: """ 导出数据字典类型列表 参数: - - data_list (List[Dict[str, Any]]): 数据字典类型列表 + - data_list (list[dict]): 数据字典类型列表 返回: - bytes: Excel文件字节流 @@ -229,7 +228,7 @@ class DictTypeService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 @@ -248,7 +247,7 @@ class DictDataService: """ @classmethod - async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取数据字典数据详情 @@ -257,23 +256,23 @@ class DictDataService: - id (int): 数据字典数据ID 返回: - - Dict: 数据字典数据详情字典 + - dict: 数据字典数据详情字典 """ obj = await DictDataCRUD(auth).get_obj_by_id_crud(id=id) return DictDataOutSchema.model_validate(obj).model_dump() @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[DictDataQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_obj_list_service(cls, auth: AuthSchema, search: DictDataQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取数据字典数据列表 参数: - auth (AuthSchema): 认证信息模型 - search (DictDataQueryParam | None): 搜索条件模型 - - order_by (List[Dict[str, str]] | None): 排序字段列表 + - order_by (list[dict] | None): 排序字段列表 返回: - - List[Dict]: 数据字典数据详情字典列表 + - 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] @@ -303,12 +302,10 @@ class DictDataService: fail_count = 0 for obj in obj_list: + dict_type = obj.dict_type try: - dict_type = obj.dict_type dict_data_list = await DictDataCRUD(auth).get_obj_list_crud(search={'dict_type': dict_type}) - dict_data = [DictDataOutSchema.model_validate(row).model_dump() for row in dict_data_list if row] - # 保存到Redis并设置过期时间 redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}" value = json.dumps(dict_data, ensure_ascii=False) @@ -332,7 +329,7 @@ class DictDataService: raise CustomException(msg=f"字典数据初始化失败: {str(e)}") @classmethod - async def get_init_dict_service(cls, redis: Redis, dict_type: str)->List[Dict]: + async def get_init_dict_service(cls, redis: Redis, dict_type: str)->list[dict]: """ 从缓存获取字典数据列表信息service @@ -341,7 +338,7 @@ class DictDataService: - dict_type (str): 字典类型 返回: - - List[Dict]: 字典数据列表 + - list[dict]: 字典数据列表 """ try: redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type}" @@ -377,7 +374,7 @@ class DictDataService: raise CustomException(msg=f"获取字典数据失败: {str(e)}") @classmethod - async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: DictDataCreateSchema) -> Dict: + async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: DictDataCreateSchema) -> dict: """ 创建数据字典数据 @@ -387,7 +384,7 @@ class DictDataService: - data (DictDataCreateSchema): 数据字典数据创建模型 返回: - - Dict: 数据字典数据详情字典 + - dict: 数据字典数据详情字典 """ exist_obj = await DictDataCRUD(auth).get(dict_label=data.dict_label) if exist_obj: @@ -413,7 +410,7 @@ class DictDataService: return DictDataOutSchema.model_validate(obj).model_dump() @classmethod - async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: DictDataUpdateSchema) -> Dict: + async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: DictDataUpdateSchema) -> dict: """ 更新数据字典数据 @@ -537,12 +534,12 @@ class DictDataService: await DictDataCRUD(auth).set_obj_available_crud(ids=data.ids, status=data.status) @classmethod - async def export_obj_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_obj_service(cls, data_list: list[dict]) -> bytes: """ 导出数据字典数据列表 参数: - - data_list (List[Dict[str, Any]]): 数据字典数据列表 + - data_list (list[dict]): 数据字典数据列表 返回: - bytes: Excel文件字节流 @@ -561,7 +558,7 @@ class DictDataService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 diff --git a/backend/app/api/v1/module_system/log/controller.py b/backend/app/api/v1/module_system/log/controller.py index 0a455081..5274bb37 100644 --- a/backend/app/api/v1/module_system/log/controller.py +++ b/backend/app/api/v1/module_system/log/controller.py @@ -5,8 +5,8 @@ from fastapi.responses import JSONResponse, StreamingResponse from app.common.request import PaginationService from app.common.response import SuccessResponse, StreamResponse -from app.utils.common_util import bytes2file_response from app.core.router_class import OperationLogRoute +from app.utils.common_util import bytes2file_response from app.core.dependencies import AuthPermission from app.core.base_params import PaginationQueryParam from app.core.logger import log diff --git a/backend/app/api/v1/module_system/log/crud.py b/backend/app/api/v1/module_system/log/crud.py index fa5cec3f..668882b5 100644 --- a/backend/app/api/v1/module_system/log/crud.py +++ b/backend/app/api/v1/module_system/log/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase @@ -21,7 +21,7 @@ class OperationLogCRUD(CRUDBase[OperationLogModel, OperationLogCreateSchema, Ope self.auth = auth super().__init__(model=OperationLogModel, auth=auth) - async def create_crud(self, data: OperationLogCreateSchema) -> Optional[OperationLogModel]: + async def create_crud(self, data: OperationLogCreateSchema) -> OperationLogModel | None: """ 创建操作日志记录。 @@ -33,20 +33,20 @@ class OperationLogCRUD(CRUDBase[OperationLogModel, OperationLogCreateSchema, Ope """ return await self.create(data=data) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[OperationLogModel]: + async def get_by_id_crud(self, id: int, preload: list | None = None) -> OperationLogModel | None: """ 根据ID获取操作日志详情。 参数: - id (int): 操作日志ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - OperationLogModel | None: 操作日志记录。 """ return await self.get(id=id, preload=preload) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[OperationLogModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list | None = None, preload: list | None = None) -> Sequence[OperationLogModel]: """ 获取操作日志列表。 diff --git a/backend/app/api/v1/module_system/log/model.py b/backend/app/api/v1/module_system/log/model.py index 2b3f59d6..0bf8bcd3 100644 --- a/backend/app/api/v1/module_system/log/model.py +++ b/backend/app/api/v1/module_system/log/model.py @@ -3,31 +3,19 @@ from sqlalchemy import String, Integer, Text from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class OperationLogModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class OperationLogModel(ModelMixin, UserMixin): """ 系统日志模型 - - 日志隔离策略: - =========== - - 日志记录操作人的实际租户和客户信息 - - tenant_id: 记录操作人所属租户(必填) - - customer_id: 记录操作人所属客户(如果是客户用户) - - 用于审计和追踪: - - 系统用户操作: tenant_id=1, customer_id=NULL - - 租户用户操作: tenant_id>1, customer_id=NULL - - 客户用户操作: tenant_id>1, customer_id>1 - 日志类型: - 1: 登录日志 - 2: 操作日志 """ __tablename__: str = "sys_log" __table_args__: dict[str, str] = ({'comment': '系统日志表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] type: Mapped[int] = mapped_column(Integer, comment="日志类型(1登录日志 2操作日志)") request_path: Mapped[str] = mapped_column(String(255), comment="请求路径") diff --git a/backend/app/api/v1/module_system/log/schema.py b/backend/app/api/v1/module_system/log/schema.py index ec1bff9d..32a0cdbb 100644 --- a/backend/app/api/v1/module_system/log/schema.py +++ b/backend/app/api/v1/module_system/log/schema.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- import re -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema class OperationLogCreateSchema(BaseModel): @@ -22,16 +21,14 @@ class OperationLogCreateSchema(BaseModel): response_code: int | None = Field(default=None, description="响应状态码") response_json: str | None = Field(default=None, description="响应 JSON 数据") process_time: str | None = Field(default=None, description="处理时间") - status: str | None = Field(default="0", description="是否成功") + status: str = Field(default="0", description="是否成功") description: str | None = Field(default=None, max_length=255, description="描述") created_id: int | None = Field(default=None, description="创建人ID") updated_id: int | None = Field(default=None, description="更新人ID") - tenant_id: int | None = Field(default=None, description="租户ID") - customer_id: int | None = Field(default=None, description="客户ID") @field_validator("type") @classmethod - def _validate_type(cls, value: Optional[int]): + def _validate_type(cls, value: int): if value is None: return value if value not in {1, 2}: @@ -40,7 +37,7 @@ class OperationLogCreateSchema(BaseModel): @field_validator("request_method") @classmethod - def _validate_method(cls, value: Optional[str]): + def _validate_method(cls, value: str): if value is None: return value allowed = {"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"} @@ -50,7 +47,7 @@ class OperationLogCreateSchema(BaseModel): @field_validator("request_ip") @classmethod - def _validate_ip(cls, value: Optional[str]): + def _validate_ip(cls, value: str | None): if value is None or value == "": return value ipv4 = r"^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$" @@ -60,7 +57,7 @@ class OperationLogCreateSchema(BaseModel): return value -class OperationLogOutSchema(OperationLogCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class OperationLogOutSchema(OperationLogCreateSchema, BaseSchema, UserBySchema): """日志响应模型""" model_config = ConfigDict(from_attributes=True) @@ -75,8 +72,10 @@ class OperationLogQueryParam: request_method: str | None = Query(None, description="请求方法"), request_ip: str | None = Query(None, description="请求IP"), response_code: int | None = Query(None, description="响应状态码"), - created_id: int | None = Query(None, description="创建人"), created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -84,6 +83,7 @@ class OperationLogQueryParam: # 精确查询字段 self.created_id = created_id + self.updated_id = updated_id self.request_method = request_method self.request_ip = request_ip self.response_code = response_code @@ -92,3 +92,6 @@ class OperationLogQueryParam: # 时间范围查询 - 增加对单个时间参数的处理 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) + \ 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 7c95460e..583e7ae8 100644 --- a/backend/app/api/v1/module_system/log/service.py +++ b/backend/app/api/v1/module_system/log/service.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List, Optional - from app.core.exceptions import CustomException from app.utils.excel_util import ExcelUtil @@ -20,7 +18,7 @@ class OperationLogService: """ @classmethod - async def get_log_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_log_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取日志详情 @@ -29,24 +27,24 @@ class OperationLogService: - id (int): 日志 ID 返回: - - Dict: 日志详情字典 + - dict: 日志详情字典 """ log = await OperationLogCRUD(auth).get_by_id_crud(id=id) log_dict = OperationLogOutSchema.model_validate(log).model_dump() return log_dict @classmethod - async def get_log_list_service(cls, auth: AuthSchema, search: Optional[OperationLogQueryParam], order_by: Optional[List[Dict]] = None) -> List[Dict]: + async def get_log_list_service(cls, auth: AuthSchema, search: OperationLogQueryParam | None = None, order_by: list | None = None) -> list[dict]: """ 获取日志列表 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[OperationLogQueryParam]): 日志查询参数模型 - - order_by (Optional[List[Dict]]): 排序字段列表 + - search (OperationLogQueryParam | None): 日志查询参数模型 + - order_by (list | None): 排序字段列表 返回: - - List[Dict]: 日志详情字典列表 + - list[dict]: 日志详情字典列表 """ log_list = await OperationLogCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) @@ -54,7 +52,7 @@ class OperationLogService: return log_dict_list @classmethod - async def create_log_service(cls, auth: AuthSchema, data: OperationLogCreateSchema) -> Dict: + async def create_log_service(cls, auth: AuthSchema, data: OperationLogCreateSchema) -> dict: """ 创建日志 @@ -63,7 +61,7 @@ class OperationLogService: - data (OperationLogCreateSchema): 日志创建模型 返回: - - Dict: 日志详情字典 + - dict: 日志详情字典 """ new_log = await OperationLogCRUD(auth).create(data=data) new_log_dict = OperationLogOutSchema.model_validate(new_log).model_dump() @@ -86,12 +84,12 @@ class OperationLogService: await OperationLogCRUD(auth).delete(ids=ids) @classmethod - async def export_log_list_service(cls, operation_log_list: List[Dict[str, Any]]) -> bytes: + async def export_log_list_service(cls, operation_log_list: list[dict]) -> bytes: """ 导出日志信息 参数: - - operation_log_list (List[Dict[str, Any]]): 操作日志信息列表 + - operation_log_list (list[dict]): 操作日志信息列表 返回: - bytes: 操作日志信息excel的二进制数据 @@ -114,7 +112,7 @@ class OperationLogService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 处理数据 diff --git a/backend/app/api/v1/module_system/menu/controller.py b/backend/app/api/v1/module_system/menu/controller.py index a7856e13..65f3c13d 100644 --- a/backend/app/api/v1/module_system/menu/controller.py +++ b/backend/app/api/v1/module_system/menu/controller.py @@ -6,8 +6,8 @@ from fastapi.responses import JSONResponse from app.common.response import SuccessResponse from app.core.dependencies import AuthPermission from app.core.base_schema import BatchSetAvailable -from app.core.router_class import OperationLogRoute from app.core.logger import log +from app.core.router_class import OperationLogRoute from ..auth.schema import AuthSchema from .service import MenuService diff --git a/backend/app/api/v1/module_system/menu/crud.py b/backend/app/api/v1/module_system/menu/crud.py index 4b3beafa..a90132f0 100644 --- a/backend/app/api/v1/module_system/menu/crud.py +++ b/backend/app/api/v1/module_system/menu/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase @@ -17,13 +17,13 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]): self.auth = auth super().__init__(model=MenuModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[MenuModel]: + async def get_by_id_crud(self, id: int, preload: list[str] | None = None) -> MenuModel | None: """ 根据 id 获取菜单信息。 参数: - id (int): 菜单 ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - MenuModel | None: 菜单信息,未找到返回 None。 @@ -33,41 +33,41 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]): return None return obj - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[MenuModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list[str] | None = None) -> Sequence[MenuModel]: """ 获取菜单列表。 参数: - - search (Dict | None): 搜索条件。 - - order_by (List[Dict[str, str]] | None): 排序字段列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 搜索条件。 + - order_by (list[dict] | None): 排序字段列表。 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[MenuModel]: 菜单列表。 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def get_tree_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[MenuModel]: + async def get_tree_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list[str] | None = None) -> Sequence[MenuModel]: """ 获取菜单树形列表。 参数: - - search (Dict | None): 搜索条件。 - - order_by (List[Dict[str, str]] | None): 排序字段列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 搜索条件。 + - order_by (list[dict] | None): 排序字段列表。 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[MenuModel]: 菜单树形列表。 """ return await self.tree_list(search=search, order_by=order_by, children_attr='children', preload=preload) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置菜单可用状态。 参数: - - ids (List[int]): 菜单 ID 列表。 - - status (bool): 可用状态。 + - ids (list[int]): 菜单 ID 列表。 + - status (str): 可用状态。 返回: - None diff --git a/backend/app/api/v1/module_system/menu/model.py b/backend/app/api/v1/module_system/menu/model.py index 2f61efd4..5d3c0653 100644 --- a/backend/app/api/v1/module_system/menu/model.py +++ b/backend/app/api/v1/module_system/menu/model.py @@ -19,23 +19,6 @@ class MenuModel(ModelMixin): - 2: 菜单(二级菜单) - 3: 按钮/权限(页面内按钮权限) - 4: 外部链接 - - 菜单隔离策略: - =========== - - 系统级菜单(tenant_id=1或NULL): - * 平台基础菜单,所有租户共享 - * 如:系统管理、用户管理等核心功能 - - - 租户级菜单(tenant_id>1): - * 租户自定义菜单,仅本租户可见 - * 如:租户特有的业务模块 - * 租户管理员可以为本租户添加自定义菜单 - - - 不需要customer_id: - * 菜单是租户级资源,不属于客户 - * 客户用户的菜单权限通过role控制 - - 支持树形结构(通过parent_id自关联) """ __tablename__: str = "sys_menu" __table_args__: dict[str, str] = ({'comment': '菜单表'}) @@ -66,7 +49,7 @@ class MenuModel(ModelMixin): comment='父菜单ID' ) - # 关联关系 (继承自UserMixin和TenantMixin) + # 关联关系 parent: Mapped["MenuModel | None"] = relationship( back_populates='children', remote_side="MenuModel.id", diff --git a/backend/app/api/v1/module_system/menu/schema.py b/backend/app/api/v1/module_system/menu/schema.py index 4878ca59..080fcdce 100644 --- a/backend/app/api/v1/module_system/menu/schema.py +++ b/backend/app/api/v1/module_system/menu/schema.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Optional, Literal +from typing import Literal from pydantic import BaseModel, ConfigDict, Field, model_validator from fastapi import Query @@ -14,21 +14,21 @@ class MenuCreateSchema(BaseModel): name: str = Field(..., max_length=50, description="菜单名称") type: int = Field(..., ge=1, le=4, description="菜单类型(1:目录 2:菜单 3:按钮 4:外链)") order: int = Field(..., ge=1, description="显示顺序") - permission: Optional[str] = Field(default=None, max_length=100, description="权限标识") - icon: Optional[str] = Field(default=None, max_length=100, description="菜单图标") - route_name: Optional[str] = Field(default=None, max_length=100, description="路由名称") - route_path: Optional[str] = Field(default=None, max_length=200, description="路由地址") - component_path: Optional[str] = Field(default=None, max_length=255, description="组件路径") - redirect: Optional[str] = Field(default=None, max_length=200, description="重定向地址") + permission: str | None = Field(default=None, max_length=100, description="权限标识") + icon: str | None = Field(default=None, max_length=100, description="菜单图标") + route_name: str | None = Field(default=None, max_length=100, description="路由名称") + route_path: str | None = Field(default=None, max_length=200, description="路由地址") + component_path: str | None = Field(default=None, max_length=255, description="组件路径") + redirect: str | None = Field(default=None, max_length=200, description="重定向地址") hidden: bool = Field(default=False, description="是否隐藏(True:是 False:否)") keep_alive: bool = Field(default=True, description="是否缓存(True:是 False:否)") always_show: bool = Field(default=False, description="是否始终显示(True:是 False:否)") - title: Optional[str] = Field(default=None, max_length=50, description="菜单标题") - params: Optional[list[dict[str, str]]] = Field(default=None, description="路由参数,格式为[{key: string, value: string}]") + title: str | None = Field(default=None, max_length=50, description="菜单标题") + params: list[dict[str, str]] | None = Field(default=None, description="路由参数,格式为[{key: string, value: string}]") affix: bool = Field(default=False, description="是否固定标签页(True:是 False:否)") - parent_id: Optional[int] = Field(default=None, ge=1, description="父菜单ID") + parent_id: int | None = Field(default=None, ge=1, description="父菜单ID") status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + description: str | None = Field(default=None, max_length=255, description="描述") @model_validator(mode='before') @classmethod @@ -38,10 +38,6 @@ class MenuCreateSchema(BaseModel): for k in ["name", "icon", "permission", "route_name", "route_path", "component_path", "redirect", "title", "description"]: if k in values and isinstance(values[k], str): values[k] = values[k].strip() or None if values[k].strip() == "" else values[k].strip() - # 布尔兼容 - for k in ["status", "keep_alive", "hidden", "always_show", "affix"]: - if k in values and isinstance(values[k], str): - values[k] = values[k].strip().lower() in {"true", "1", "yes", "y"} # 父ID转整型 if "parent_id" in values and isinstance(values["parent_id"], str): try: @@ -67,14 +63,14 @@ class MenuCreateSchema(BaseModel): class MenuUpdateSchema(MenuCreateSchema): """菜单更新模型""" - parent_name: Optional[str] = Field(default=None, max_length=50, description="父菜单名称") + parent_name: str | None = Field(default=None, max_length=50, description="父菜单名称") class MenuOutSchema(MenuCreateSchema, BaseSchema): """菜单响应模型""" model_config = ConfigDict(from_attributes=True) - parent_name: Optional[str] = Field(default=None, max_length=50, description="父菜单名称") + parent_name: str | None = Field(default=None, max_length=50, description="父菜单名称") class MenuQueryParam: @@ -82,13 +78,13 @@ class MenuQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="菜单名称"), - route_path: Optional[str] = Query(None, description="路由地址"), - component_path: Optional[str] = Query(None, description="组件路径"), - type: Optional[Literal['M', 'C', 'F']] = Query(None, description="菜单类型(M目录 C菜单 F按钮)"), - permission: Optional[str] = Query(None, description="权限标识"), - status: Optional[str] = Query(None, description="菜单状态(True正常 False停用)"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="菜单名称"), + route_path: str | None = Query(None, description="路由地址"), + component_path: str | None = Query(None, description="组件路径"), + type: Literal[1,2,3,4] | None = Query(None, description="菜单类型(1:目录 2:菜单 3:按钮 4:外链)"), + permission: str | None = Query(None, description="权限标识"), + status: str | None = Query(None, description="菜单状态(0:启用 1:禁用)"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: # 模糊查询字段 diff --git a/backend/app/api/v1/module_system/menu/service.py b/backend/app/api/v1/module_system/menu/service.py index 04a7e87c..81e8aa90 100644 --- a/backend/app/api/v1/module_system/menu/service.py +++ b/backend/app/api/v1/module_system/menu/service.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from typing import List, Dict, Optional - from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException from app.utils.common_util import ( @@ -28,7 +26,7 @@ class MenuService: """ @classmethod - async def get_menu_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_menu_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取菜单详情。 @@ -37,7 +35,7 @@ class MenuService: - id (int): 菜单ID。 返回: - - Dict: 菜单详情对象。 + - dict: 菜单详情对象。 """ menu = await MenuCRUD(auth).get_by_id_crud(id=id) # 创建实例后再设置parent_name属性 @@ -50,17 +48,17 @@ class MenuService: return menu_out.model_dump() @classmethod - async def get_menu_tree_service(cls, auth: AuthSchema, search: Optional[MenuQueryParam] = None, order_by: Optional[List[Dict]] = None) -> List[Dict]: + async def get_menu_tree_service(cls, auth: AuthSchema, search: MenuQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取菜单树形列表。 参数: - auth (AuthSchema): 认证对象。 - search (MenuQueryParam | None): 查询参数对象。 - - order_by (List[Dict] | None): 排序参数列表。 + - order_by (list[dict] | None): 排序参数列表。 返回: - - List[Dict]: 菜单树形列表对象。 + - list[dict]: 菜单树形列表对象。 """ # 使用树形结构查询,预加载children关系 menu_list = await MenuCRUD(auth).get_tree_list_crud(search=search.__dict__, order_by=order_by) @@ -70,7 +68,7 @@ class MenuService: return traversal_to_tree(menu_dict_list) @classmethod - async def create_menu_service(cls, auth: AuthSchema, data: MenuCreateSchema) -> Dict: + async def create_menu_service(cls, auth: AuthSchema, data: MenuCreateSchema) -> dict: """ 创建菜单。 @@ -79,7 +77,7 @@ class MenuService: - data (MenuCreateSchema): 创建参数对象。 返回: - - Dict: 创建的菜单对象。 + - dict: 创建的菜单对象。 """ menu = await MenuCRUD(auth).get(name=data.name) if menu: @@ -90,7 +88,7 @@ class MenuService: return new_menu_dict @classmethod - async def update_menu_service(cls, auth: AuthSchema,id:int, data: MenuUpdateSchema) -> Dict: + async def update_menu_service(cls, auth: AuthSchema,id:int, data: MenuUpdateSchema) -> dict: """ 更新菜单。 @@ -100,7 +98,7 @@ class MenuService: - data (MenuUpdateSchema): 更新参数对象。 返回: - - Dict: 更新的菜单对象。 + - dict: 更新的菜单对象。 """ menu = await MenuCRUD(auth).get_by_id_crud(id=id) if not menu: diff --git a/backend/app/api/v1/module_system/notice/controller.py b/backend/app/api/v1/module_system/notice/controller.py index c67c4d9f..caa8ed83 100644 --- a/backend/app/api/v1/module_system/notice/controller.py +++ b/backend/app/api/v1/module_system/notice/controller.py @@ -6,10 +6,10 @@ from fastapi.responses import JSONResponse, StreamingResponse from app.common.response import StreamResponse, SuccessResponse from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission, get_current_user -from app.core.router_class import OperationLogRoute from app.core.base_schema import BatchSetAvailable from app.core.logger import log from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from ..auth.schema import AuthSchema diff --git a/backend/app/api/v1/module_system/notice/crud.py b/backend/app/api/v1/module_system/notice/crud.py index 024f8d0a..4a6ab1c4 100644 --- a/backend/app/api/v1/module_system/notice/crud.py +++ b/backend/app/api/v1/module_system/notice/crud.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase - from ..auth.schema import AuthSchema from .model import NoticeModel from .schema import NoticeCreateSchema, NoticeUpdateSchema @@ -22,34 +21,34 @@ class NoticeCRUD(CRUDBase[NoticeModel, NoticeCreateSchema, NoticeUpdateSchema]): self.auth = auth super().__init__(model=NoticeModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[NoticeModel]: + async def get_by_id_crud(self, id: int, preload: list | None = None) -> NoticeModel | None: """ 根据ID获取公告详情。 参数: - id (int): 公告ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[NoticeModel]: 公告模型实例。 + - NoticeModel | None: 公告模型实例。 """ return await self.get(id=id, preload=preload) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[NoticeModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[NoticeModel]: """ 获取公告列表。 参数: - - search (Optional[Dict]): 查询参数。 - - order_by (Optional[List[Dict[str, str]]]): 排序参数。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数。 + - order_by (list[dict] | None): 排序参数。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[NoticeModel]: 公告模型实例列表。 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_crud(self, data: NoticeCreateSchema) -> Optional[NoticeModel]: + async def create_crud(self, data: NoticeCreateSchema) -> NoticeModel | None: """ 创建公告。 @@ -57,11 +56,11 @@ class NoticeCRUD(CRUDBase[NoticeModel, NoticeCreateSchema, NoticeUpdateSchema]): - data (NoticeCreateSchema): 公告创建模型。 返回: - - Optional[NoticeModel]: 公告模型实例。 + - NoticeModel | None: 公告模型实例。 """ return await self.create(data=data) - async def update_crud(self, id: int, data: NoticeUpdateSchema) -> Optional[NoticeModel]: + async def update_crud(self, id: int, data: NoticeUpdateSchema) -> NoticeModel | None: """ 更新公告。 @@ -70,29 +69,29 @@ class NoticeCRUD(CRUDBase[NoticeModel, NoticeCreateSchema, NoticeUpdateSchema]): - data (NoticeUpdateSchema): 公告更新模型。 返回: - - Optional[NoticeModel]: 公告模型实例。 + - NoticeModel | None: 公告模型实例。 """ return await self.update(id=id, data=data) - async def delete_crud(self, ids: List[int]) -> None: + async def delete_crud(self, ids: list[int]) -> None: """ 删除公告。 参数: - - ids (List[int]): 公告ID列表。 + - ids (list[int]): 公告ID列表。 返回: - None """ return await self.delete(ids=ids) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 设置公告的可用状态。 参数: - - ids (List[int]): 公告ID列表。 - - status (bool): 可用状态。 + - ids (list[int]): 公告ID列表。 + - status (str): 可用状态。 返回: - None diff --git a/backend/app/api/v1/module_system/notice/model.py b/backend/app/api/v1/module_system/notice/model.py index 2b773b39..1b7c80ea 100644 --- a/backend/app/api/v1/module_system/notice/model.py +++ b/backend/app/api/v1/module_system/notice/model.py @@ -3,35 +3,16 @@ from sqlalchemy import String, Text from sqlalchemy.orm import Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import ModelMixin, UserMixin -class NoticeModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class NoticeModel(ModelMixin, UserMixin): """ 通知公告表 - - 通知公告隔离策略: - ============== - - 系统通知(tenant_id=1, customer_id=NULL): - * 平台级通知,发送给所有租户 - * 如:系统维护公告、版本更新通知 - - - 租户通知(tenant_id>1, customer_id=NULL): - * 租户级通知,发送给本租户所有用户 - * 如:租户内部公告、政策通知 - - - 客户通知(tenant_id>1, customer_id>1): - * 客户级通知,仅发送给特定客户的用户 - * 如:针对某个客户的专属通知 - - 用于存储系统通知公告信息,包括: - - 通知标题、内容、类型和状态 - - 创建人和创建时间 - - 通知的可见范围和发布状态 """ __tablename__: str = "sys_notice" __table_args__: dict[str, str] = ({'comment': '通知公告表'}) - __loader_options__: list[str] = ["created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["created_by", "updated_by"] notice_title: Mapped[str] = mapped_column(String(50), nullable=False, comment='公告标题') notice_type: Mapped[str] = mapped_column(String(50), nullable=False, comment='公告类型(1通知 2公告)') diff --git a/backend/app/api/v1/module_system/notice/schema.py b/backend/app/api/v1/module_system/notice/schema.py index 408efd38..335aa57a 100644 --- a/backend/app/api/v1/module_system/notice/schema.py +++ b/backend/app/api/v1/module_system/notice/schema.py @@ -1,12 +1,10 @@ # -*- coding: utf-8 -*- -from datetime import datetime -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, UserBySchema class NoticeCreateSchema(BaseModel): @@ -15,7 +13,7 @@ class NoticeCreateSchema(BaseModel): notice_type: str = Field(..., description='公告类型(1通知 2公告)') notice_content: str = Field(..., description='公告内容') status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator("notice_type") @classmethod @@ -38,7 +36,7 @@ class NoticeUpdateSchema(NoticeCreateSchema): ... -class NoticeOutSchema(NoticeCreateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class NoticeOutSchema(NoticeCreateSchema, BaseSchema, UserBySchema): """公告通知响应模型""" model_config = ConfigDict(from_attributes=True) @@ -48,11 +46,13 @@ class NoticeQueryParam: def __init__( self, - notice_title: Optional[str] = Query(None, description="公告标题"), - notice_type: Optional[str] = Query(None, description="公告类型"), - status: Optional[str] = Query(None, description="是否可用"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + notice_title: str | None = Query(None, description="公告标题"), + notice_type: str | None = Query(None, description="公告类型"), + status: str | None = Query(None, description="是否可用"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -60,9 +60,13 @@ class NoticeQueryParam: # 精确查询字段 self.created_id = created_id + self.updated_id = updated_id self.status = status self.notice_type = notice_type # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) + diff --git a/backend/app/api/v1/module_system/notice/service.py b/backend/app/api/v1/module_system/notice/service.py index bcdfde34..fc8d7849 100644 --- a/backend/app/api/v1/module_system/notice/service.py +++ b/backend/app/api/v1/module_system/notice/service.py @@ -1,9 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import datetime -from typing import Any, List, Dict, Optional - - from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException from app.utils.excel_util import ExcelUtil @@ -19,7 +15,7 @@ class NoticeService: """ @classmethod - async def get_notice_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_notice_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取公告详情。 @@ -32,9 +28,9 @@ class NoticeService: """ notice_obj = await NoticeCRUD(auth).get_by_id_crud(id=id) return NoticeOutSchema.model_validate(notice_obj).model_dump() - + @classmethod - async def get_notice_list_available_service(cls, auth: AuthSchema) -> List[Dict]: + async def get_notice_list_available_service(cls, auth: AuthSchema) -> list[dict]: """ 获取可用的公告列表。 @@ -42,7 +38,7 @@ class NoticeService: - auth (AuthSchema): 认证信息模型。 返回: - - List[Dict]: 可用公告详情字典列表。 + - list[dict]: 可用公告详情字典列表。 """ search = { 'status': True, @@ -52,23 +48,23 @@ 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: Optional[NoticeQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_notice_list_service(cls, auth: AuthSchema, search: NoticeQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取公告列表。 参数: - auth (AuthSchema): 认证信息模型。 - - search (Optional[NoticeQueryParam]): 查询参数模型。 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表。 + - search (NoticeQueryParam | None): 查询参数模型。 + - order_by (list[dict] | None): 排序参数列表。 返回: - - List[Dict]: 公告详情字典列表。 + - 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] @classmethod - async def create_notice_service(cls, auth: AuthSchema, data: NoticeCreateSchema) -> Dict: + async def create_notice_service(cls, auth: AuthSchema, data: NoticeCreateSchema) -> dict: """ 创建公告。 @@ -77,7 +73,7 @@ class NoticeService: - data (NoticeCreateSchema): 创建公告负载模型。 返回: - - Dict: 创建的公告详情字典。 + - dict: 创建的公告详情字典。 异常: - CustomException: 创建失败,该公告通知已存在。 @@ -89,7 +85,7 @@ class NoticeService: return NoticeOutSchema.model_validate(notice_obj).model_dump() @classmethod - async def update_notice_service(cls, auth: AuthSchema, id: int, data: NoticeUpdateSchema) -> Dict: + async def update_notice_service(cls, auth: AuthSchema, id: int, data: NoticeUpdateSchema) -> dict: """ 更新公告。 @@ -99,7 +95,7 @@ class NoticeService: - data (NoticeUpdateSchema): 更新公告负载模型。 返回: - - Dict: 更新的公告详情字典。 + - dict: 更新的公告详情字典。 异常: - CustomException: 更新失败,该公告通知不存在或公告通知标题重复。 @@ -148,12 +144,12 @@ class NoticeService: await NoticeCRUD(auth).set_available_crud(ids=data.ids, status=data.status) @classmethod - async def export_notice_service(cls, notice_list: List[Dict[str, Any]]) -> bytes: + async def export_notice_service(cls, notice_list: list[dict]) -> bytes: """ 导出公告列表。 参数: - - notice_list (List[Dict[str, Any]]): 公告详情字典列表。 + - notice_list (list[dict]): 公告详情字典列表。 返回: - bytes: Excel 文件的字节流。 @@ -168,7 +164,7 @@ class NoticeService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 diff --git a/backend/app/api/v1/module_system/params/controller.py b/backend/app/api/v1/module_system/params/controller.py index c2dc7489..c100f6e0 100644 --- a/backend/app/api/v1/module_system/params/controller.py +++ b/backend/app/api/v1/module_system/params/controller.py @@ -6,10 +6,10 @@ from redis.asyncio.client import Redis from app.common.request import PaginationService from app.common.response import StreamResponse, SuccessResponse +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission, redis_getter -from app.core.router_class import OperationLogRoute from app.core.logger import log from ..auth.schema import AuthSchema diff --git a/backend/app/api/v1/module_system/params/crud.py b/backend/app/api/v1/module_system/params/crud.py index ff6d7f0f..0a940810 100644 --- a/backend/app/api/v1/module_system/params/crud.py +++ b/backend/app/api/v1/module_system/params/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase @@ -22,47 +22,47 @@ class ParamsCRUD(CRUDBase[ParamsModel, ParamsCreateSchema, ParamsUpdateSchema]): self.auth = auth super().__init__(model=ParamsModel, auth=auth) - async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ParamsModel]: + async def get_obj_by_id_crud(self, id: int, preload: list | None = None) -> ParamsModel | None: """ 获取配置管理型详情 参数: - id (int): 配置管理型ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[ParamsModel]: 配置管理型模型实例 + - ParamsModel | None: 配置管理型模型实例 """ return await self.get(id=id, preload=preload) - async def get_obj_by_key_crud(self, key: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ParamsModel]: + async def get_obj_by_key_crud(self, key: str, preload: list | None = None) -> ParamsModel | None: """ 根据key获取配置管理型详情 参数: - key (str): 配置管理型key - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[ParamsModel]: 配置管理型模型实例 + - ParamsModel | None: 配置管理型模型实例 """ return await self.get(config_key=key, preload=preload) - async def get_obj_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[ParamsModel]: + async def get_obj_list_crud(self, search: dict | None = None, order_by: list | None = None, preload: list | None = None) -> Sequence[ParamsModel]: """ 获取配置管理型列表 参数: - - search (Dict | None): 查询参数对象。 - - order_by (List[Dict[str, str]] | None): 排序参数列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数对象。 + - order_by (list | None): 排序参数列表。 + - preload (list | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[ParamsModel]: 配置管理型模型实例列表 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def create_obj_crud(self, data: ParamsCreateSchema) -> Optional[ParamsModel]: + async def create_obj_crud(self, data: ParamsCreateSchema) -> ParamsModel | None: """ 创建配置管理型 @@ -70,11 +70,11 @@ class ParamsCRUD(CRUDBase[ParamsModel, ParamsCreateSchema, ParamsUpdateSchema]): - data (ParamsCreateSchema): 创建配置管理型负载模型 返回: - - Optional[ParamsModel]: 配置管理型模型实例 + - ParamsModel | None: 配置管理型模型实例 """ return await self.create(data=data) - async def update_obj_crud(self, id: int, data: ParamsUpdateSchema) -> Optional[ParamsModel]: + async def update_obj_crud(self, id: int, data: ParamsUpdateSchema) -> ParamsModel | None: """ 更新配置管理型 @@ -83,16 +83,16 @@ class ParamsCRUD(CRUDBase[ParamsModel, ParamsCreateSchema, ParamsUpdateSchema]): - data (ParamsUpdateSchema): 更新配置管理型负载模型 返回: - - Optional[ParamsModel]: 配置管理型模型实例 + - ParamsModel | None: 配置管理型模型实例 """ return await self.update(id=id, data=data) - async def delete_obj_crud(self, ids: List[int]) -> None: + async def delete_obj_crud(self, ids: list[int]) -> None: """ 删除配置管理型 参数: - - ids (List[int]): 配置管理型ID列表 + - ids (list[int]): 配置管理型ID列表 返回: - None diff --git a/backend/app/api/v1/module_system/params/model.py b/backend/app/api/v1/module_system/params/model.py index c9d5b196..fb79bfa7 100644 --- a/backend/app/api/v1/module_system/params/model.py +++ b/backend/app/api/v1/module_system/params/model.py @@ -9,26 +9,6 @@ from app.core.base_model import ModelMixin class ParamsModel(ModelMixin): """ 参数配置表 - - 参数配置隔离策略: - ============= - - 系统级参数(tenant_id=NULL): - * 平台级配置参数,影响所有租户 - * config_type=True表示系统内置,不可删除 - * 如:系统名称、Logo、邮件配置等 - - - 租户级参数(tenant_id>1): - * 租户自定义配置参数,仅本租户生效 - * config_type=False表示可自定义 - * 如:租户LOGO、业务配置等 - - - 不需要customer_id: - * 参数是租户级配置,不属于客户 - - 用于存储系统配置参数,包括: - - 系统级别参数 - - 租户级别参数 - - 配置项的名称、键、值和类型等信息 """ __tablename__: str = "sys_param" __table_args__: dict[str, str] = ({'comment': '系统参数表'}) diff --git a/backend/app/api/v1/module_system/params/schema.py b/backend/app/api/v1/module_system/params/schema.py index 55f0f5da..8b5347a0 100644 --- a/backend/app/api/v1/module_system/params/schema.py +++ b/backend/app/api/v1/module_system/params/schema.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Optional from pydantic import BaseModel, ConfigDict, Field, field_validator from fastapi import Query @@ -10,13 +9,12 @@ from app.core.base_schema import BaseSchema class ParamsCreateSchema(BaseModel): """配置创建模型""" - config_name: str = Field(..., max_length=500, description="参数名称") + config_name: str = Field(..., max_length=64, description="参数名称") config_key: str = Field(..., max_length=500, description="参数键名") - config_value: Optional[str] = Field(default=None, description="参数键值") + config_value: str | None = Field(default=None, description="参数键值") config_type: bool = Field(default=False, description="系统内置(True:是 False:否)") status: str = Field(default="0", description="状态(True:正常 False:停用)") - description: Optional[str] = Field(default=None, max_length=500, description="描述") - + description: str | None = Field(default=None, max_length=500, description="描述") @field_validator('config_key') @classmethod @@ -43,10 +41,11 @@ class ParamsQueryParam: def __init__( self, - config_name: Optional[str] = Query(None, description="配置名称"), - config_key: Optional[str] = Query(None, description="配置键名"), - config_type: Optional[bool] = Query(None, description="系统内置((True:是 False:否))"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + config_name: str | None = Query(None, description="配置名称"), + config_key: str | None = Query(None, description="配置键名"), + config_type: bool | None = Query(None, description="系统内置((True:是 False:否))"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), ) -> None: # 模糊查询字段 @@ -59,5 +58,6 @@ class ParamsQueryParam: # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) - + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) diff --git a/backend/app/api/v1/module_system/params/service.py b/backend/app/api/v1/module_system/params/service.py index 4dff5998..65e88aba 100644 --- a/backend/app/api/v1/module_system/params/service.py +++ b/backend/app/api/v1/module_system/params/service.py @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- import json -from typing import Any, Dict, List, Optional - from redis.asyncio.client import Redis from fastapi import UploadFile from redis.asyncio.client import Redis @@ -26,7 +24,7 @@ class ParamsService: 配置管理模块服务层 """ @classmethod - async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_obj_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取配置详情 @@ -35,13 +33,13 @@ class ParamsService: - id (int): 配置管理型ID 返回: - - Dict: 配置管理型模型实例字典表示 + - dict: 配置管理型模型实例字典表示 """ obj = await ParamsCRUD(auth).get_obj_by_id_crud(id=id) return ParamsOutSchema.model_validate(obj).model_dump() @classmethod - async def get_obj_by_key_service(cls, auth: AuthSchema, config_key: str) -> Dict: + async def get_obj_by_key_service(cls, auth: AuthSchema, config_key: str) -> dict: """ 根据配置键获取配置详情 @@ -75,17 +73,17 @@ class ParamsService: return obj.config_value @classmethod - async def get_obj_list_service(cls, auth: AuthSchema, search: Optional[ParamsQueryParam] = None, order_by: Optional[List[Dict[str, str]]]= None) -> List[Dict]: + async def get_obj_list_service(cls, auth: AuthSchema, search: ParamsQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取配置管理型列表 参数: - auth (AuthSchema): 认证信息模型 - search (ParamsQueryParam | None): 查询参数对象 - - order_by (List[Dict[str, str]] | None): 排序参数列表 + - order_by (list[dict] | None): 排序参数列表 返回: - - List[Dict]: 配置管理型模型实例字典列表表示 + - list[dict]: 配置管理型模型实例字典列表表示 """ obj_list = None if search: @@ -95,7 +93,7 @@ class ParamsService: return [ParamsOutSchema.model_validate(obj).model_dump() for obj in obj_list] @classmethod - async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: ParamsCreateSchema) -> Dict: + async def create_obj_service(cls, auth: AuthSchema, redis: Redis, data: ParamsCreateSchema) -> dict: """ 创建配置管理型 @@ -105,7 +103,7 @@ class ParamsService: - data (ParamsCreateSchema): 配置管理型创建模型 返回: - - Dict: 新创建的配置管理型模型实例字典表示 + - dict: 新创建的配置管理型模型实例字典表示 """ exist_obj = await ParamsCRUD(auth).get(config_key=data.config_key) if exist_obj: @@ -131,7 +129,7 @@ class ParamsService: return new_obj_dict @classmethod - async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: ParamsUpdateSchema) -> Dict: + async def update_obj_service(cls, auth: AuthSchema, redis: Redis, id:int, data: ParamsUpdateSchema) -> dict: """ 更新配置管理型 @@ -212,12 +210,12 @@ class ParamsService: raise CustomException(msg="删除字典类型失败") @classmethod - async def export_obj_service(cls, data_list: List[Dict[str, Any]]) -> bytes: + async def export_obj_service(cls, data_list: list[dict]) -> bytes: """ 导出系统配置列表 参数: - - data_list (List[Dict[str, Any]]): 系统配置模型实例字典列表表示 + - data_list (list[dict]): 系统配置模型实例字典列表表示 返回: - bytes: Excel文件二进制数据 @@ -232,7 +230,7 @@ class ParamsService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 @@ -245,7 +243,7 @@ class ParamsService: return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict) @classmethod - async def upload_service(cls, base_url: str, file: UploadFile) -> Dict: + async def upload_service(cls, base_url: str, file: UploadFile) -> dict: """ 上传文件 @@ -254,7 +252,7 @@ class ParamsService: - file (UploadFile): 上传的文件对象 返回: - - Dict: 上传文件的响应模型实例字典表示 + - dict: 上传文件的响应模型实例字典表示 """ filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url) @@ -301,7 +299,7 @@ class ParamsService: raise CustomException(msg="初始化系统配置失败") @classmethod - async def get_init_config_service(cls, redis: Redis) -> List[Dict]: + async def get_init_config_service(cls, redis: Redis) -> list[dict]: """ 获取系统配置 @@ -309,7 +307,7 @@ class ParamsService: - redis (Redis): Redis 客户端实例 返回: - - List[Dict]: 系统配置模型实例字典列表表示 + - list[dict]: 系统配置模型实例字典列表表示 """ redis_keys = await RedisCURD(redis).get_keys(f"{RedisInitKeyConfig.SYSTEM_CONFIG.key}:*") redis_configs = await RedisCURD(redis).mget(redis_keys) @@ -327,7 +325,7 @@ class ParamsService: return configs @classmethod - async def get_system_config_for_middleware(cls, redis: Redis) -> Dict[str, Any]: + async def get_system_config_for_middleware(cls, redis: Redis) -> dict: """ 获取中间件所需的系统配置 @@ -335,7 +333,7 @@ class ParamsService: - redis (Redis): Redis 客户端实例 返回: - - Dict[str, Any]: 包含演示模式、IP白名单、API白名单和IP黑名单的配置字典 + - dict: 包含演示模式、IP白名单、API白名单和IP黑名单的配置字典 """ # 定义需要获取的配置键 config_keys = [ diff --git a/backend/app/api/v1/module_system/position/controller.py b/backend/app/api/v1/module_system/position/controller.py index 2ddb5eec..52883837 100644 --- a/backend/app/api/v1/module_system/position/controller.py +++ b/backend/app/api/v1/module_system/position/controller.py @@ -1,13 +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, StreamingResponse from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService +from app.core.router_class import OperationLogRoute from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam -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 log diff --git a/backend/app/api/v1/module_system/position/crud.py b/backend/app/api/v1/module_system/position/crud.py index d7ba38ff..da1368e9 100644 --- a/backend/app/api/v1/module_system/position/crud.py +++ b/backend/app/api/v1/module_system/position/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence, Any from app.core.base_crud import CRUDBase from ..auth.schema import AuthSchema @@ -21,39 +21,39 @@ class PositionCRUD(CRUDBase[PositionModel, PositionCreateSchema, PositionUpdateS self.auth = auth super().__init__(model=PositionModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[PositionModel]: + async def get_by_id_crud(self, id: int, preload: list[str] | None = None) -> PositionModel | None: """ 根据 id 获取岗位信息。 参数: - id (int): 岗位 ID。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - PositionModel | None: 岗位信息,未找到返回 None。 """ return await self.get(id=id, preload=preload) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[PositionModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict[str, Any]] | None = None, preload: list[str] | None = None) -> Sequence[PositionModel]: """ 获取岗位列表。 参数: - - search (Dict | None): 搜索条件。 - - order_by (List[Dict[str, str]] | None): 排序字段列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 搜索条件。 + - order_by (list[dict[str, Any]] | None): 排序字段列表。 + - preload (list[str] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[PositionModel]: 岗位列表。 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置岗位可用状态。 参数: - - ids (List[int]): 岗位 ID 列表。 + - ids (list[int]): 岗位 ID 列表。 - status (bool): 可用状态。 返回: @@ -61,15 +61,15 @@ class PositionCRUD(CRUDBase[PositionModel, PositionCreateSchema, PositionUpdateS """ await self.set(ids=ids, status=status) - async def get_name_crud(self, ids: List[int]) -> List[str]: + async def get_name_crud(self, ids: list[int]) -> list[str]: """ 根据 id 列表获取岗位名称。 参数: - - ids (List[int]): 岗位 ID 列表。 + - ids (list[int]): 岗位 ID 列表。 返回: - - List[str]: 岗位名称列表。 + - list[str]: 岗位名称列表。 """ position_names = [] for id in ids: diff --git a/backend/app/api/v1/module_system/position/model.py b/backend/app/api/v1/module_system/position/model.py index a8391db6..c6f95e63 100644 --- a/backend/app/api/v1/module_system/position/model.py +++ b/backend/app/api/v1/module_system/position/model.py @@ -4,35 +4,25 @@ from typing import TYPE_CHECKING from sqlalchemy import String, Integer from sqlalchemy.orm import relationship, Mapped, mapped_column -from app.core.base_model import ModelMixin, UserMixin, TenantMixin +from app.core.base_model import ModelMixin, UserMixin + if TYPE_CHECKING: from app.api.v1.module_system.user.model import UserModel -class PositionModel(ModelMixin, UserMixin, TenantMixin): +class PositionModel(ModelMixin, UserMixin): """ 岗位模型 - - 岗位是租户级别的职位定义: - - 岗位属于租户(tenant_id必填) - - 岗位不属于客户(customer_id不需要) - - 用于定义租户内的各种职位(如:总监、经理、专员等) - - 用户可以同时拥有多个岗位 - - 使用场景: - - 职位管理: 定义公司组织架构中的各类职位 - - 权限控制: 可以根据岗位分配特定权限 - - 业务流程: 某些审批流可以指定特定岗位处理 """ __tablename__: str = "sys_position" __table_args__: dict[str, str] = ({'comment': '岗位表'}) - __loader_options__: list[str] = ["users", "created_by", "updated_by", "tenant"] + __loader_options__: list[str] = ["users", "created_by", "updated_by"] name: Mapped[str] = mapped_column(String(40), nullable=False, comment="岗位名称") order: Mapped[int] = mapped_column(Integer, nullable=False, default=1, comment="显示排序") - # 关联关系 (继承自UserMixin和TenantMixin) + # 关联关系 users: Mapped[list["UserModel"]] = relationship( secondary="sys_user_positions", back_populates="positions", diff --git a/backend/app/api/v1/module_system/position/schema.py b/backend/app/api/v1/module_system/position/schema.py index c9dafb0e..5b569278 100644 --- a/backend/app/api/v1/module_system/position/schema.py +++ b/backend/app/api/v1/module_system/position/schema.py @@ -5,15 +5,15 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator from fastapi import Query from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema +from app.core.base_schema import BaseSchema, UserBySchema class PositionCreateSchema(BaseModel): """岗位创建模型""" - name: str = Field(..., max_length=40, description="岗位名称") - order: Optional[int] = Field(default=1, ge=1, description='显示排序') + name: str = Field(..., max_length=64, description="岗位名称") + order: int = Field(default=1, ge=1, description='显示排序') status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator('name') @classmethod @@ -29,7 +29,7 @@ class PositionUpdateSchema(PositionCreateSchema): ... -class PositionOutSchema(PositionCreateSchema, BaseSchema, UserBySchema, TenantSchema): +class PositionOutSchema(PositionCreateSchema, BaseSchema, UserBySchema): """岗位信息响应模型""" model_config = ConfigDict(from_attributes=True) ... @@ -42,8 +42,10 @@ class PositionQueryParam: self, name: Optional[str] = Query(None, description="岗位名称"), status: Optional[str] = Query(None, description="是否可用"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -51,8 +53,12 @@ class PositionQueryParam: # 精确查询字段 self.created_id = created_id + self.updated_id = updated_id self.status = status # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) + \ 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 edec6b10..285c1c95 100644 --- a/backend/app/api/v1/module_system/position/service.py +++ b/backend/app/api/v1/module_system/position/service.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List, Optional - from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException from app.utils.excel_util import ExcelUtil @@ -20,7 +18,7 @@ class PositionService: """岗位模块服务层""" @classmethod - async def get_position_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_position_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取岗位详情 @@ -35,23 +33,23 @@ class PositionService: return PositionOutSchema.model_validate(position).model_dump() @classmethod - async def get_position_list_service(cls, auth: AuthSchema, search: Optional[PositionQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_position_list_service(cls, auth: AuthSchema, search: PositionQueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: """ 获取岗位列表 参数: - auth (AuthSchema): 认证信息模型 - search (PositionQueryParam | None): 查询参数对象 - - order_by (List[Dict[str, str]] | None): 排序参数列表 + - order_by (list[dict] | None): 排序参数列表 返回: - - List[Dict]: 岗位列表对象 + - list[dict]: 岗位列表对象 """ 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] @classmethod - async def create_position_service(cls, auth: AuthSchema, data: PositionCreateSchema) -> Dict: + async def create_position_service(cls, auth: AuthSchema, data: PositionCreateSchema) -> dict: """ 创建岗位 @@ -69,7 +67,7 @@ class PositionService: return PositionOutSchema.model_validate(new_position).model_dump() @classmethod - async def update_position_service(cls, auth: AuthSchema, id:int, data: PositionUpdateSchema) -> Dict: + async def update_position_service(cls, auth: AuthSchema, id:int, data: PositionUpdateSchema) -> dict: """ 更新岗位 @@ -79,7 +77,7 @@ class PositionService: - data (PositionUpdateSchema): 岗位更新模型 返回: - - Dict: 更新的岗位对象 + - dict: 更新的岗位对象 """ position = await PositionCRUD(auth).get_by_id_crud(id=id) if not position: @@ -125,12 +123,12 @@ class PositionService: await PositionCRUD(auth).set_available_crud(ids=data.ids, status=data.status) @classmethod - async def export_position_list_service(cls, position_list: List[Dict[str, Any]]) -> bytes: + async def export_position_list_service(cls, position_list: list[dict]) -> bytes: """ 导出岗位列表 参数: - - position_list (List[Dict[str, Any]]): 岗位列表对象 + - position_list (list[dict]): 岗位列表对象 返回: - bytes: 导出的Excel文件字节流 @@ -144,7 +142,7 @@ class PositionService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换状态 diff --git a/backend/app/api/v1/module_system/role/controller.py b/backend/app/api/v1/module_system/role/controller.py index 45cc70bd..e573b6c3 100644 --- a/backend/app/api/v1/module_system/role/controller.py +++ b/backend/app/api/v1/module_system/role/controller.py @@ -5,8 +5,8 @@ from fastapi.responses import JSONResponse, StreamingResponse from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService -from app.utils.common_util import bytes2file_response from app.core.router_class import OperationLogRoute +from app.utils.common_util import bytes2file_response from app.core.base_params import PaginationQueryParam from app.core.dependencies import AuthPermission from app.core.base_schema import BatchSetAvailable diff --git a/backend/app/api/v1/module_system/role/crud.py b/backend/app/api/v1/module_system/role/crud.py index c5d6bddf..08b2e3f2 100644 --- a/backend/app/api/v1/module_system/role/crud.py +++ b/backend/app/api/v1/module_system/role/crud.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Sequence, Optional, Union, Any +from typing import Sequence from app.core.base_crud import CRUDBase @@ -24,34 +24,34 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]): self.auth = auth super().__init__(model=RoleModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[RoleModel]: + async def get_by_id_crud(self, id: int, preload: list | None = None) -> RoleModel | None: """ 根据id获取角色信息 参数: - id (int): 角色ID - - preload (Optional[List[Union[str, Any]]]): 预加载选项 + - preload (list | None): 预加载选项 返回: - - Optional[RoleModel]: 角色模型对象 + - RoleModel | None: 角色模型对象 """ return await self.get(id=id, preload=preload) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[RoleModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list | None = None, preload: list | None = None) -> Sequence[RoleModel]: """ 获取角色列表 参数: - - search (Optional[Dict]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - preload (Optional[List[Union[str, Any]]]): 预加载选项 + - search (dict | None): 查询参数 + - order_by (list | None): 排序参数 + - preload (list | None): 预加载选项 返回: - Sequence[RoleModel]: 角色模型对象列表 """ return await self.list(search=search, order_by=order_by, preload=preload) - async def set_role_menus_crud(self, role_ids: List[int], menu_ids: List[int]) -> None: + async def set_role_menus_crud(self, role_ids: list[int], menu_ids: list[int]) -> None: """ 设置角色的菜单权限 @@ -69,14 +69,14 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]): relationship = obj.menus relationship.clear() relationship.extend(menus) - await self.db.flush() + await self.auth.db.flush() - async def set_role_data_scope_crud(self, role_ids: List[int], data_scope: int) -> None: + async def set_role_data_scope_crud(self, role_ids: list[int], data_scope: int) -> None: """ 设置角色的数据范围 参数: - - role_ids (List[int]): 角色ID列表 + - role_ids (list[int]): 角色ID列表 - data_scope (int): 数据范围 返回: @@ -84,13 +84,13 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]): """ await self.set(ids=role_ids, data_scope=data_scope) - async def set_role_depts_crud(self, role_ids: List[int], dept_ids: List[int]) -> None: + async def set_role_depts_crud(self, role_ids: list[int], dept_ids: list[int]) -> None: """ 设置角色的部门权限 参数: - - role_ids (List[int]): 角色ID列表 - - dept_ids (List[int]): 部门ID列表 + - role_ids (list[int]): 角色ID列表 + - dept_ids (list[int]): 部门ID列表 返回: - None @@ -102,15 +102,15 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]): relationship = obj.depts relationship.clear() relationship.extend(depts) - await self.db.flush() + await self.auth.db.flush() - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 设置角色的可用状态 参数: - - ids (List[int]): 角色ID列表 - - status (bool): 可用状态 + - ids (list[int]): 角色ID列表 + - status (str): 可用状态 返回: - None diff --git a/backend/app/api/v1/module_system/role/model.py b/backend/app/api/v1/module_system/role/model.py index f367f61d..832d41a1 100644 --- a/backend/app/api/v1/module_system/role/model.py +++ b/backend/app/api/v1/module_system/role/model.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING from sqlalchemy import String, Integer, ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column -from app.core.base_model import MappedBase, ModelMixin, UserMixin, TenantMixin +from app.core.base_model import MappedBase, ModelMixin, UserMixin if TYPE_CHECKING: from app.api.v1.module_system.menu.model import MenuModel @@ -59,59 +59,20 @@ class RoleDeptsModel(MappedBase): ) -class RoleModel(ModelMixin, UserMixin, TenantMixin): +class RoleModel(ModelMixin, UserMixin): """ 角色模型 - - 角色是租户级别的权限管理单元: - - 角色属于租户(tenant_id必填) - - 角色不属于客户(customer_id不需要) - - 通过角色分配菜单权限和数据权限 - - 数据权限实现(data_scope字段): - =========================== - - 1: 仅本人数据权限 - * 实现: WHERE created_id = current_user.id - * 场景: 普通员工只能看自己创建的数据 - - - 2: 本部门数据权限 - * 实现: WHERE user.dept_id = current_user.dept_id - * 场景: 部门经理看本部门所有人的数据 - - - 3: 本部门及以下数据权限 - * 实现: WHERE dept.tree_path LIKE 'current_user.dept.tree_path%' - * 场景: 总监看本部门及所有下级部门的数据 - - - 4: 全部数据权限 - * 实现: WHERE tenant_id = current_user.tenant_id - * 场景: 租户管理员看租户内所有数据 - * 注意: 客户用户即使有此权限也只能看本客户数据 - - - 5: 自定义数据权限 - * 实现: WHERE dept_id IN (SELECT dept_id FROM sys_role_depts WHERE role_id IN current_user.role_ids) - * 场景: 跨部门权限,如人事可以看多个指定部门 - * 使用: 通过role_depts关联表指定可访问的部门列表 - - 权限叠加规则: - - 一个用户可以有多个角色 - - 取所有角色data_scope的最大值(4>3>2>5>1) - - 5(自定义)需要合并所有角色关联的部门 """ __tablename__: str = "sys_role" __table_args__: dict[str, str] = ({'comment': '角色表'}) - __loader_options__: list[str] = ["menus", "depts", "created_by", "updated_by", "tenant"] + __loader_options__: list[str] = ["menus", "depts", "created_by", "updated_by"] name: Mapped[str] = mapped_column(String(40), nullable=False, comment="角色名称") code: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True, comment="角色编码") order: Mapped[int] = mapped_column(Integer, nullable=False, default=999, comment="显示排序") - data_scope: Mapped[str | None] = mapped_column( - String(10), - default='1', - nullable=True, - comment="数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)" - ) + data_scope: Mapped[int] = mapped_column(Integer, default=1, nullable=False, comment="数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)") - # 关联关系 (继承自UserMixin和TenantMixin) + # 关联关系 (继承自UserMixin) menus: Mapped[list["MenuModel"]] = relationship( secondary="sys_role_menus", back_populates="roles", diff --git a/backend/app/api/v1/module_system/role/schema.py b/backend/app/api/v1/module_system/role/schema.py index d7d759d8..1fb0349d 100644 --- a/backend/app/api/v1/module_system/role/schema.py +++ b/backend/app/api/v1/module_system/role/schema.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -from typing import List, Optional from fastapi import Query from pydantic import BaseModel, ConfigDict, Field, model_validator, field_validator from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema, UserBySchema, TenantSchema +from app.core.base_schema import BaseSchema, UserBySchema from app.core.validator import role_permission_request_validator from ..dept.schema import DeptOutSchema @@ -15,15 +14,15 @@ from ..menu.schema import MenuOutSchema class RoleCreateSchema(BaseModel): """角色创建模型""" name: str = Field(..., max_length=40, description="角色名称") - code: Optional[str] = Field(default=None, max_length=40, description="角色编码") - order: Optional[int] = Field(default=1, ge=1, description='显示排序') - data_scope: Optional[int] = Field(default=1, ge=1, le=5, description='数据权限范围') + code: str | None = Field(default=None, max_length=40, description="角色编码") + order: int | None = Field(default=1, ge=1, description='显示排序') + data_scope: int | None = Field(default=1, description='数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)') status: str = Field(default="0", description="是否启用") - description: Optional[str] = Field(default=None, max_length=255, description="描述") + description: str | None = Field(default=None, max_length=255, description="描述") @field_validator("code") @classmethod - def validate_code(cls, value: Optional[str]): + def validate_code(cls, value: str | None): if value is None: return value import re @@ -35,10 +34,10 @@ class RoleCreateSchema(BaseModel): class RolePermissionSettingSchema(BaseModel): """角色权限配置模型""" - data_scope: int = Field(default=1, ge=1, le=5, description='数据权限范围') - role_ids: List[int] = Field(default_factory=list, description='角色ID列表') - menu_ids: List[int] = Field(default_factory=list, description='菜单ID列表') - dept_ids: List[int] = Field(default_factory=list, description='部门ID列表') + data_scope: int = Field(default=1, description='数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)') + role_ids: list[int] = Field(default_factory=list, description='角色ID列表') + menu_ids: list[int] = Field(default_factory=list, description='菜单ID列表') + dept_ids: list[int] = Field(default_factory=list, description='部门ID列表') @model_validator(mode='after') def validate_fields(self): @@ -51,12 +50,12 @@ class RoleUpdateSchema(RoleCreateSchema): ... -class RoleOutSchema(RoleCreateSchema, BaseSchema, UserBySchema, TenantSchema): +class RoleOutSchema(RoleCreateSchema, BaseSchema, UserBySchema): """角色信息响应模型""" model_config = ConfigDict(from_attributes=True) - menus: List[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表') - depts: List[DeptOutSchema] = Field(default_factory=list, description='角色部门列表') + menus: list[MenuOutSchema] = Field(default_factory=list, description='角色菜单列表') + depts: list[DeptOutSchema] = Field(default_factory=list, description='角色部门列表') class RoleQueryParam: @@ -64,10 +63,12 @@ class RoleQueryParam: def __init__( self, - name: Optional[str] = Query(None, description="角色名称"), - status: Optional[str] = Query(None, description="是否可用"), - created_id: Optional[int] = Query(None, description="创建人"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + name: str | None = Query(None, description="角色名称"), + status: str | None = Query(None, description="是否可用"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -75,8 +76,13 @@ class RoleQueryParam: # 精确查询字段 self.created_id = created_id + self.updated_id = updated_id self.status = status # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) + diff --git a/backend/app/api/v1/module_system/role/service.py b/backend/app/api/v1/module_system/role/service.py index 22fa8788..f63889fd 100644 --- a/backend/app/api/v1/module_system/role/service.py +++ b/backend/app/api/v1/module_system/role/service.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List, Optional - +from typing import Any from app.core.base_schema import BatchSetAvailable from app.core.exceptions import CustomException from app.utils.excel_util import ExcelUtil @@ -21,7 +20,7 @@ class RoleService: """角色模块服务层""" @classmethod - async def get_role_detail_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_role_detail_service(cls, auth: AuthSchema, id: int) -> dict: """ 获取角色详情 @@ -30,29 +29,29 @@ class RoleService: - id (int): 角色ID 返回: - - Dict: 角色详情字典 + - dict: 角色详情字典 """ role = await RoleCRUD(auth).get_by_id_crud(id=id) return RoleOutSchema.model_validate(role).model_dump() @classmethod - async def get_role_list_service(cls, auth: AuthSchema, search: Optional[RoleQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_role_list_service(cls, auth: AuthSchema, search: RoleQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]: """ 获取角色列表 参数: - auth (AuthSchema): 认证信息模型 - - search (Optional[RoleQueryParam]): 查询参数模型 - - order_by (Optional[List[Dict[str, str]]]): 排序参数列表 + - search (RoleQueryParam | None): 查询参数模型 + - order_by (list[dict[str, str]] | None): 排序参数列表 返回: - - List[Dict]: 角色详情字典列表 + - list[dict]: 角色详情字典列表 """ 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] @classmethod - async def create_role_service(cls, auth: AuthSchema, data: RoleCreateSchema) -> Dict: + async def create_role_service(cls, auth: AuthSchema, data: RoleCreateSchema) -> dict: """ 创建角色 @@ -61,7 +60,7 @@ class RoleService: - data (RoleCreateSchema): 创建角色模型 返回: - - Dict: 新创建的角色详情字典 + - dict: 新创建的角色详情字典 """ role = await RoleCRUD(auth).get(name=data.name) if role: @@ -73,7 +72,7 @@ class RoleService: return RoleOutSchema.model_validate(new_role).model_dump() @classmethod - async def update_role_service(cls, auth: AuthSchema, id: int, data: RoleUpdateSchema) -> Dict: + async def update_role_service(cls, auth: AuthSchema, id: int, data: RoleUpdateSchema) -> dict: """ 更新角色 @@ -83,7 +82,7 @@ class RoleService: - data (RoleUpdateSchema): 更新角色模型 返回: - - Dict: 更新后的角色详情字典 + - dict: 更新后的角色详情字典 """ role = await RoleCRUD(auth).get_by_id_crud(id=id) if not role: @@ -153,12 +152,12 @@ class RoleService: await RoleCRUD(auth).set_available_crud(ids=data.ids, status=data.status) @classmethod - async def export_role_list_service(cls, role_list: List[Dict[str, Any]]) -> bytes: + async def export_role_list_service(cls, role_list: list[dict[str, Any]]) -> bytes: """ 导出角色列表 参数: - - role_list (List[Dict[str, Any]]): 角色详情字典列表 + - role_list (list[dict[str, Any]]): 角色详情字典列表 返回: - bytes: Excel文件字节流 @@ -174,7 +173,7 @@ class RoleService: 'created_time': '创建时间', 'updated_time': '更新时间', 'created_id': '创建者ID', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 数据权限映射 diff --git a/backend/app/api/v1/module_system/tenant/__init__.py b/backend/app/api/v1/module_system/tenant/__init__.py deleted file mode 100644 index 633f8661..00000000 --- a/backend/app/api/v1/module_system/tenant/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- - diff --git a/backend/app/api/v1/module_system/tenant/controller.py b/backend/app/api/v1/module_system/tenant/controller.py deleted file mode 100644 index e7eccf23..00000000 --- a/backend/app/api/v1/module_system/tenant/controller.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- - -from fastapi import APIRouter, Body, Depends, Path, UploadFile -from fastapi.responses import JSONResponse, StreamingResponse -import urllib.parse - -from app.common.response import StreamResponse, SuccessResponse -from app.utils.common_util import bytes2file_response -from app.core.base_params import PaginationQueryParam -from app.core.dependencies import AuthPermission -from app.core.router_class import OperationLogRoute -from app.core.base_schema import BatchSetAvailable -from app.core.logger import log - -from app.api.v1.module_system.auth.schema import AuthSchema -from .service import TenantService -from .schema import ( - TenantCreateSchema, - TenantUpdateSchema, - TenantQueryParam -) - - -TenantRouter = APIRouter(route_class=OperationLogRoute, prefix="/tenant", tags=["租户模块"]) - -@TenantRouter.get("/detail/{id}", summary="获取租户详情", description="获取租户详情") -async def get_obj_detail_controller( - id: int = Path(..., description="租户ID"), - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:query"])) -) -> JSONResponse: - """ - 获取租户详情 - - 参数: - - id (int): 租户ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含租户详情的JSON响应 - """ - result_dict = await TenantService.detail_service(id=id, auth=auth) - log.info(f"获取租户详情成功 {id}") - return SuccessResponse(data=result_dict, msg="获取租户详情成功") - -@TenantRouter.get("/list", summary="查询租户列表", description="查询租户列表") -async def get_obj_list_controller( - page: PaginationQueryParam = Depends(), - search: TenantQueryParam = Depends(), - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:query"])) -) -> JSONResponse: - """ - 查询租户列表 - - 参数: - - page (PaginationQueryParam): 分页查询参数 - - search (TenantQueryParam): 查询参数 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含租户列表分页信息的JSON响应 - """ - # 使用数据库分页而不是应用层分页 - result_dict = await TenantService.page_service( - auth=auth, - page_no=page.page_no if page.page_no is not None else 1, - page_size=page.page_size if page.page_size is not None else 10, - search=search, - order_by=page.order_by - ) - log.info("查询租户列表成功") - return SuccessResponse(data=result_dict, msg="查询租户列表成功") - -@TenantRouter.post("/create", summary="创建租户", description="创建租户") -async def create_obj_controller( - data: TenantCreateSchema, - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:create"])) -) -> JSONResponse: - """ - 创建租户 - - 参数: - - data (TenantCreateSchema): 租户创建模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含创建租户详情的JSON响应 - """ - result_dict = await TenantService.create_service(auth=auth, data=data) - log.info(f"创建租户成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="创建租户成功") - -@TenantRouter.put("/update/{id}", summary="修改租户", description="修改租户") -async def update_obj_controller( - data: TenantUpdateSchema, - id: int = Path(..., description="租户ID"), - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:update"])) -) -> JSONResponse: - """ - 修改租户 - - 参数: - - data (TenantUpdateSchema): 租户更新模型 - - id (int): 租户ID - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含修改租户详情的JSON响应 - """ - result_dict = await TenantService.update_service(auth=auth, id=id, data=data) - log.info(f"修改租户成功: {result_dict.get('name')}") - return SuccessResponse(data=result_dict, msg="修改租户成功") - -@TenantRouter.delete("/delete", summary="删除租户", description="删除租户") -async def delete_obj_controller( - ids: list[int] = Body(..., description="ID列表"), - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:delete"])) -) -> JSONResponse: - """ - 删除租户 - - 参数: - - ids (list[int]): 租户ID列表 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含删除租户详情的JSON响应 - """ - await TenantService.delete_service(auth=auth, ids=ids) - log.info(f"删除租户成功: {ids}") - return SuccessResponse(msg="删除租户成功") - -@TenantRouter.patch("/available/setting", summary="批量修改租户状态", description="批量修改租户状态") -async def batch_set_available_obj_controller( - data: BatchSetAvailable, - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:patch"])) -) -> JSONResponse: - """ - 批量修改租户状态 - - 参数: - - data (BatchSetAvailable): 批量修改租户状态模型 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含批量修改租户状态详情的JSON响应 - """ - await TenantService.set_available_service(auth=auth, data=data) - log.info(f"批量修改租户状态成功: {data.ids}") - return SuccessResponse(msg="批量修改租户状态成功") - -@TenantRouter.post('/export', summary="导出租户", description="导出租户") -async def export_obj_list_controller( - search: TenantQueryParam = Depends(), - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:export"])) -) -> StreamingResponse: - """ - 导出租户 - - 参数: - - search (TenantQueryParam): 查询参数 - - auth (AuthSchema): 认证信息模型 - - 返回: - - StreamingResponse: 包含租户列表的Excel文件流响应 - """ - result_dict_list = await TenantService.list_service(search=search, auth=auth) - export_result = await TenantService.batch_export_service(obj_list=result_dict_list) - log.info('导出租户成功') - - return StreamResponse( - data=bytes2file_response(export_result), - media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers={ - 'Content-Disposition': 'attachment; filename=example.xlsx' - } - ) - -@TenantRouter.post('/import', summary="导入租户", description="导入租户") -async def import_obj_list_controller( - file: UploadFile, - auth: AuthSchema = Depends(AuthPermission(["module_system:tenant:import"])) -) -> JSONResponse: - """ - 导入租户 - - 参数: - - file (UploadFile): 导入的Excel文件 - - auth (AuthSchema): 认证信息模型 - - 返回: - - JSONResponse: 包含导入租户详情的JSON响应 - """ - batch_import_result = await TenantService.batch_import_service(file=file, auth=auth, update_support=True) - log.info(f"导入租户成功: {batch_import_result}") - return SuccessResponse(data=batch_import_result, msg="导入租户成功") - -@TenantRouter.post('/download/template', summary="获取租户导入模板", description="获取租户导入模板", dependencies=[Depends(AuthPermission(["module_system:tenant:download"]))]) -async def export_obj_template_controller() -> StreamingResponse: - """ - 获取租户导入模板 - - 返回: - - StreamingResponse: 包含租户导入模板的Excel文件流响应 - """ - example_import_template_result = await TenantService.import_template_download_service() - log.info('获取租户导入模板成功') - - return StreamResponse( - data=bytes2file_response(example_import_template_result), - media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - headers={ - 'Content-Disposition': f'attachment; filename={urllib.parse.quote("租户导入模板.xlsx")}', - 'Access-Control-Expose-Headers': 'Content-Disposition' - } - ) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/tenant/crud.py b/backend/app/api/v1/module_system/tenant/crud.py deleted file mode 100644 index 269104e0..00000000 --- a/backend/app/api/v1/module_system/tenant/crud.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Dict, List, Optional, Sequence, Union, Any - -from app.core.base_crud import CRUDBase - -from app.api.v1.module_system.auth.schema import AuthSchema -from .model import TenantModel -from .schema import TenantCreateSchema, TenantUpdateSchema, TenantOutSchema - - -class TenantCRUD(CRUDBase[TenantModel, TenantCreateSchema, TenantUpdateSchema]): - """租户数据层""" - - def __init__(self, auth: AuthSchema) -> None: - """ - 初始化CRUD数据层 - - 参数: - - auth (AuthSchema): 认证信息模型 - """ - super().__init__(model=TenantModel, auth=auth) - - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[TenantModel]: - """ - 详情 - - 参数: - - id (int): 租户ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Optional[TenantModel]: 租户模型实例或None - """ - return await self.get(id=id, preload=preload) - - async def list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[TenantModel]: - """ - 列表查询 - - 参数: - - search (Optional[Dict]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Sequence[TenantModel]: 租户模型实例序列 - """ - return await self.list(search=search, order_by=order_by, preload=preload) - - async def create_crud(self, data: TenantCreateSchema) -> Optional[TenantModel]: - """ - 创建 - - 参数: - - data (TenantCreateSchema): 租户创建模型 - - 返回: - - Optional[TenantModel]: 租户模型实例或None - """ - return await self.create(data=data) - - async def update_crud(self, id: int, data: TenantUpdateSchema) -> Optional[TenantModel]: - """ - 更新 - - 参数: - - id (int): 租户ID - - data (TenantUpdateSchema): 租户更新模型 - - 返回: - - Optional[TenantModel]: 租户模型实例或None - """ - return await self.update(id=id, data=data) - - async def delete_crud(self, ids: List[int]) -> None: - """ - 批量删除 - - 参数: - - ids (List[int]): 租户ID列表 - - 返回: - - None - """ - return await self.delete(ids=ids) - - async def set_available_crud(self, ids: List[int], status: str) -> None: - """ - 批量设置可用状态 - - 参数: - - ids (List[int]): 租户ID列表 - - status (bool): 可用状态 - - 返回: - - None - """ - return await self.set(ids=ids, status=status) - - async def page_crud(self, offset: int, limit: int, order_by: Optional[List[Dict[str, str]]] = None, search: Optional[Dict] = None, preload: Optional[List[Union[str, Any]]] = None) -> Dict: - """ - 分页查询 - - 参数: - - offset (int): 偏移量 - - limit (int): 每页数量 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - search (Optional[Dict]): 查询参数 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 - - 返回: - - Dict: 分页数据 - """ - order_by_list = order_by or [{'id': 'asc'}] - search_dict = search or {} - - return await self.page( - offset=offset, - limit=limit, - order_by=order_by_list, - search=search_dict, - out_schema=TenantOutSchema, - preload=preload - ) diff --git a/backend/app/api/v1/module_system/tenant/model.py b/backend/app/api/v1/module_system/tenant/model.py deleted file mode 100644 index 57d7e900..00000000 --- a/backend/app/api/v1/module_system/tenant/model.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- - -from datetime import datetime -from sqlalchemy import DateTime, String -from sqlalchemy.orm import Mapped, mapped_column, validates - -from app.core.base_model import ModelMixin - - -class TenantModel(ModelMixin): - """ - 租户模型 - - 核心数据隔离模型: - - 系统租户(id=1):管理所有租户和系统配置,由平台超管管理 - - 普通租户(id>1):拥有自己的用户、部门、角色、客户等数据,租户间完全隔离 - - 所有业务表通过tenant_id字段关联到租户,实现租户间数据隔离 - - 注意: - - 租户表本身不需要tenant_id字段(租户不属于租户) - - 租户表不需要customer_id字段(租户不属于客户) - - 但需要created_id/updated_id用于审计追踪 - """ - __tablename__: str = 'sys_tenant' - __table_args__: dict[str, str] = {'comment': '租户表'} - - name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment='租户名称') - code: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment='租户编码') - start_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None, comment='开始时间') - end_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None, comment='结束时间') - - @validates('name') - def validate_name(self, key: str, name: str) -> str: - """验证名称不为空""" - if not name or not name.strip(): - raise ValueError('名称不能为空') - return name - - @validates('code') - def validate_code(self, key: str, code: str) -> str: - """验证编码格式校验""" - if not code or not code.strip(): - raise ValueError('编码不能为空') - if not code.isalnum(): - raise ValueError('编码只能包含字母和数字') - return code diff --git a/backend/app/api/v1/module_system/tenant/schema.py b/backend/app/api/v1/module_system/tenant/schema.py deleted file mode 100644 index 3dfffee8..00000000 --- a/backend/app/api/v1/module_system/tenant/schema.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- - -from typing import Optional -from fastapi import Query -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator - -from app.core.validator import DateTimeStr -from app.core.base_schema import BaseSchema -from app.core.validator import DateTimeStr - - -class TenantCreateSchema(BaseModel): - """新增模型""" - name: str = Field(..., max_length=64, description='租户名称') - code: Optional[str] = Field(default=None, max_length=20, description='租户编码') - status: str = Field(default="0", description="是否启用(0:启用 1:禁用)") - description: Optional[str] = Field(default=None, max_length=255, description="描述") - start_time: Optional[DateTimeStr] = Field(default=None, description="开始时间") - end_time: Optional[DateTimeStr] = Field(default=None, description="结束时间") - - @field_validator('name') - @classmethod - def _validate_name(cls, v: str) -> str: - v = v.strip() - if not v: - raise ValueError('名称不能为空') - return v - - @model_validator(mode='after') - def _after_validation(self): - """ - 核心业务规则校验 - """ - # 长度校验:名称最小长度 - if len(self.name) < 2 or len(self.name) > 64: - raise ValueError('名称长度必须在2-50个字符之间') - # 格式校验:名称只能包含字母、数字、下划线和中划线 - if not self.name.isalnum() and not all(c in '-_' for c in self.name): - raise ValueError('名称只能包含字母、数字、下划线和中划线') - - return self - - -class TenantUpdateSchema(TenantCreateSchema): - """更新模型""" - name: Optional[str] = Field(None, max_length=64, description='租户名称') - - -class TenantOutSchema(TenantCreateSchema, BaseSchema): - """响应模型""" - model_config = ConfigDict(from_attributes=True) - - -class TenantQueryParam: - """租户查询参数""" - - def __init__( - self, - name: Optional[str] = Query(None, description="租户名称"), - status: Optional[str] = Query(None, description="状态用(True:启用 False:禁用)"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), - ) -> None: - - # 模糊查询字段 - self.name = ("like", name) - - # 精确查询字段 - self.status = status - - # 时间范围查询 - if created_time and len(created_time) == 2: - self.created_time = ("between", (created_time[0], created_time[1])) - diff --git a/backend/app/api/v1/module_system/tenant/service.py b/backend/app/api/v1/module_system/tenant/service.py deleted file mode 100644 index c600c2b2..00000000 --- a/backend/app/api/v1/module_system/tenant/service.py +++ /dev/null @@ -1,480 +0,0 @@ -# -*- coding: utf-8 -*- - -import io -import random -import string -import time -from typing import Any, List, Dict, Optional -from fastapi import UploadFile -import pandas as pd - -from app.core.base_schema import BatchSetAvailable -from app.core.exceptions import CustomException -from app.utils.excel_util import ExcelUtil -from app.core.logger import log -from app.utils.hash_bcrpy_util import PwdUtil - -from app.api.v1.module_system.auth.schema import AuthSchema -from app.api.v1.module_system.user.crud import UserCRUD -from .schema import TenantCreateSchema, TenantUpdateSchema, TenantOutSchema, TenantQueryParam -from .crud import TenantCRUD - - -class TenantService: - """ - 租户管理模块服务层 - """ - - @classmethod - async def detail_service(cls, auth: AuthSchema, id: int) -> Dict: - """ - 详情 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 租户ID - - 返回: - - Dict: 租户模型实例字典 - """ - obj = await TenantCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg="该数据不存在") - - # 获取租户详情基础数据 - result = TenantOutSchema.model_validate(obj).model_dump() - - return result - - @classmethod - async def list_service(cls, auth: AuthSchema, search: Optional[TenantQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: - """ - 列表查询 - - 参数: - - auth (AuthSchema): 认证信息模型 - - search (Optional[TenantQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - 返回: - - List[Dict]: 租户模型实例字典列表 - """ - search_dict = search.__dict__ if search else None - obj_list = await TenantCRUD(auth).list_crud(search=search_dict, order_by=order_by) - return [TenantOutSchema.model_validate(obj).model_dump() for obj in obj_list] - - @classmethod - async def page_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: Optional[TenantQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Dict: - """ - 分页查询 - - 参数: - - auth (AuthSchema): 认证信息模型 - - page_no (int): 页码 - - page_size (int): 每页数量 - - search (Optional[TenantQueryParam]): 查询参数 - - order_by (Optional[List[Dict[str, str]]]): 排序参数 - - 返回: - - Dict: 分页数据 - """ - search_dict = search.__dict__ if search else {} - order_by_list = order_by or [{'id': 'asc'}] - offset = (page_no - 1) * page_size - - result = await TenantCRUD(auth).page_crud( - offset=offset, - limit=page_size, - order_by=order_by_list, - search=search_dict - ) - return result - - @classmethod - async def create_service(cls, auth: AuthSchema, data: TenantCreateSchema) -> Dict: - """ - 创建 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (TenantCreateSchema): 租户创建模型 - - 返回: - - Dict: 租户模型实例字典 - """ - obj = await TenantCRUD(auth).get(name=data.name) - if obj: - raise CustomException(msg='创建失败,名称已存在') - obj = await TenantCRUD(auth).get(code=data.code) - if obj: - raise CustomException(msg='创建失败,编码已存在') - - # 创建租户 - tenant_obj = await TenantCRUD(auth).create_crud(data=data) - - # 自动创建租户初始管理员用户 - await cls._create_tenant_admin_user(auth, tenant_obj) - - return TenantOutSchema.model_validate(tenant_obj).model_dump() - - @classmethod - async def _create_tenant_admin_user(cls, auth: AuthSchema, tenant_obj) -> None: - """ - 为新创建的租户自动创建初始管理员用户 - - 参数: - - auth (AuthSchema): 认证信息模型 - - tenant_obj: 租户对象 - - 返回: - - None - """ - try: - # 生成初始管理员用户名(使用租户编码) - username = f"{tenant_obj.code}_admin" - - # 生成随机密码 - password_length = 12 - characters = string.ascii_letters + string.digits + "!@#$%^&*" - password = ''.join(random.choice(characters) for _ in range(password_length)) - - # 创建管理员用户数据 - admin_user_data = { - "username": username, - "password": PwdUtil.set_password_hash(password=password), - "name": f"{tenant_obj.name}管理员", - "tenant_id": tenant_obj.id, - "user_type": "1", # 租户管理员类型 - "status": True, - "created_id": auth.user.id if auth.user else None - } - - # 创建用户 - new_user = await UserCRUD(auth).create(data=admin_user_data) - - # 记录日志,包含临时密码信息(仅开发环境记录,生产环境应避免) - log.info(f"为租户[{tenant_obj.name}]创建初始管理员用户成功,用户名: {username},临时密码: {password}") - - except Exception as e: - log.error(f"为租户[{tenant_obj.name}]创建初始管理员用户失败: {str(e)}") - # 不中断租户创建流程,仅记录错误 - pass - - @classmethod - async def update_service(cls, auth: AuthSchema, id: int, data: TenantUpdateSchema) -> Dict: - """ - 更新 - - 参数: - - auth (AuthSchema): 认证信息模型 - - id (int): 租户ID - - data (TenantUpdateSchema): 租户更新模型 - - 返回: - - Dict: 租户模型实例字典 - """ - # 系统租户特殊处理 - if id == 1: - obj = await TenantCRUD(auth).update_crud(id=id, data=data) - log.info(f"系统租户配额设置已更新") - - return TenantOutSchema.model_validate(obj).model_dump() - - # 检查数据是否存在 - obj = await TenantCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg='更新失败,该数据不存在') - - # 检查名称是否重复 - exist_obj = await TenantCRUD(auth).get(name=data.name) - if exist_obj and exist_obj.id != id: - raise CustomException(msg='更新失败,名称重复') - - obj = await TenantCRUD(auth).update_crud(id=id, data=data) - return TenantOutSchema.model_validate(obj).model_dump() - - @classmethod - async def delete_service(cls, auth: AuthSchema, ids: List[int]) -> None: - """ - 删除 - - 参数: - - auth (AuthSchema): 认证信息模型 - - ids (List[int]): 租户ID列表 - - 返回: - - None - """ - if len(ids) < 1: - raise CustomException(msg='删除失败,删除对象不能为空') - - # 系统租户保护:不允许删除系统租户(id=1) - if 1 in ids: - raise CustomException(msg='系统租户不允许删除') - - # 检查所有要删除的数据是否存在 - for id in ids: - obj = await TenantCRUD(auth).get_by_id_crud(id=id) - if not obj: - raise CustomException(msg=f'删除失败,ID为{id}的数据不存在') - - await TenantCRUD(auth).delete_crud(ids=ids) - - @classmethod - async def set_available_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None: - """ - 批量设置状态 - - 参数: - - auth (AuthSchema): 认证信息模型 - - data (BatchSetAvailable): 批量设置状态模型 - - 返回: - - None - """ - # 系统租户保护:不允许禁用系统租户(id=1) - if data.status is False and 1 in data.ids: - raise CustomException(msg='系统租户不允许禁用') - - await TenantCRUD(auth).set_available_crud(ids=data.ids, status=data.status) - - @classmethod - async def batch_export_service(cls, obj_list: List[Dict[str, Any]]) -> bytes: - """ - 批量导出 - - 参数: - - obj_list (List[Dict[str, Any]]): 租户模型实例字典列表 - - 返回: - - bytes: Excel文件字节流 - """ - mapping_dict = { - 'id': '编号', - 'name': '名称', - 'code': '编码', - 'status': '状态', - 'description': '备注', - 'start_time': '开始时间', - 'end_time': '结束时间', - 'created_time': '创建时间', - 'updated_time': '更新时间', - 'created_id': '创建者', - } - - # 复制数据并转换状态 - data = obj_list.copy() - for item in data: - # 系统租户特殊标记 - if item.get('id') == 1: - item['name'] = f"{item.get('name')} [系统租户]" - - # 处理状态 - item['status'] = '启用' if item.get('status') == '0' else '停用' - - # 处理创建者 - creator_info = item.get('created_id') - if isinstance(creator_info, dict): - item['created_id'] = creator_info.get('name', '未知') - else: - item['created_id'] = '未知' - - # 限制导出数量,防止大数据量导出 - max_export_count = 1000 - if len(data) > max_export_count: - data = data[:max_export_count] - log.warning(f'导出数据超过{max_export_count}条限制,仅导出前{max_export_count}条') - - 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: - """ - 批量导入 - - 参数: - - auth (AuthSchema): 认证信息模型 - - file (UploadFile): 上传的Excel文件 - - update_support (bool): 是否支持更新存在数据 - - 返回: - - str: 导入结果信息 - """ - - header_dict = { - '名称': 'name', - '编码': 'code', - '状态': 'status', - '描述': 'description', - '开始时间': 'start_time', - '结束时间': 'end_time' - } - - try: - # 读取Excel文件 - contents = await file.read() - df = pd.read_excel(io.BytesIO(contents)) - await file.close() - - # 验证导入数量限制 - max_import_count = 100 - if len(df) > max_import_count: - raise CustomException(msg=f"单次导入不能超过{max_import_count}条数据") - - if df.empty: - raise CustomException(msg="导入文件为空") - - # 检查表头是否完整 - missing_headers = [header for header in header_dict.keys() if header not in df.columns] - if missing_headers: - raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}") - - # 重命名列名 - df.rename(columns=header_dict, inplace=True) - - # 验证必填字段 - required_fields = ['name', 'code', 'status'] - for field in required_fields: - missing_rows = df[df[field].isnull()].index.tolist() - if missing_rows: - field_name = [k for k,v in header_dict.items() if v == field][0] - error_rows = [i+1 for i in missing_rows] - raise CustomException(msg=f"{field_name}不能为空,第{error_rows}行") - - error_msgs = [] - success_count = 0 - count = 0 - processed_names = set() # 用于检测重复名称 - processed_codes = set() # 用于检测重复编码 - - # 处理每一行数据 - for index, row in df.iterrows(): - count += 1 - try: - # 数据转换前的类型检查 - try: - status = True if str(row['status']).strip() == '正常' else False - except ValueError: - error_msgs.append(f"第{count}行: 状态必须是'正常'或'停用'") - continue - - # 字段格式验证 - name = str(row['name']).strip() - if len(name) < 2 or len(name) > 64: - error_msgs.append(f"第{count}行: 租户名称长度必须在2-64个字符之间") - continue - - # 检查名称是否只包含允许的字符 - if not all(c.isalnum() or c in '-_' for c in name.replace(' ', '')): - error_msgs.append(f"第{count}行: 租户名称只能包含字母、数字、下划线、中划线和空格") - continue - - # 检查导入文件内的重复名称 - if name in processed_names: - error_msgs.append(f"第{count}行: 租户名称 '{name}' 在文件中重复") - continue - processed_names.add(name) - - # 处理编码 - code = str(row['code']).strip() - if code in processed_codes: - error_msgs.append(f"第{count}行: 租户编码 '{code}' 在文件中重复") - continue - processed_codes.add(code) - - # 构建租户数据 - data = { - "name": name, - "code": code, - "status": status, - "description": str(row['description']).strip(), - } - - - # 检查时间有效性 - if 'start_time' in data and 'end_time' in data and data['start_time'] > data['end_time']: - error_msgs.append(f"第{count}行: 开始时间不能晚于结束时间") - continue - - # 处理租户导入 - exists_obj = await TenantCRUD(auth).get(name=data["name"]) - if exists_obj: - # 系统租户保护 - if exists_obj.id == 1: - error_msgs.append(f"第{count}行: 系统租户不允许修改") - continue - - if update_support: - await TenantCRUD(auth).update(id=exists_obj.id, data=data) - success_count += 1 - else: - error_msgs.append(f"第{count}行: 租户 {data['name']} 已存在") - else: - # 检查编码是否已存在 - exists_code = await TenantCRUD(auth).get(code=data["code"]) - if exists_code: - error_msgs.append(f"第{count}行: 租户编码 '{data['code']}' 已存在") - continue - - # 创建租户 - new_tenant = await TenantCRUD(auth).create(data=data) - success_count += 1 - - # 自动创建租户管理员(如果导入数量不是特别大) - if success_count < 10: # 限制自动创建管理员的数量 - await cls._create_tenant_admin_user(auth, new_tenant) - else: - log.info(f"批量导入超过10个租户,跳过自动创建管理员用户") - - except Exception as e: - error_msgs.append(f"第{count}行: {str(e)}") - continue - - # 返回详细的导入结果 - result = f"成功导入 {success_count} 条数据" - if error_msgs: - result += "\n错误信息:\n" + "\n".join(error_msgs) - # 记录错误详情到日志 - log.error(f"租户批量导入错误详情: {error_msgs}") - - log.info(f"租户批量导入完成: 成功{success_count}条, 失败{len(error_msgs)}条") - return result - - except CustomException: - raise - except Exception as e: - log.error(f"批量导入租户失败: {str(e)}") - raise CustomException(msg=f"导入失败: {str(e)}") - - @classmethod - async def import_template_download_service(cls) -> bytes: - """ - 下载导入模板 - - 返回: - - bytes: Excel文件字节流 - """ - header_list = ['名称', '编码', '状态', '描述', '开始时间', '结束时间'] - selector_header_list = ['状态'] - option_list = [{'状态': ['正常', '停用']}] - - # 添加示例数据和说明 - sample_data = [ - ['测试租户1', 'TEST001', '正常', '这是一个测试租户', '', ''], - ['测试租户2', 'TEST002', '正常', '这是另一个测试租户', '', ''] - ] - - # 添加说明文本 - description = """导入说明: -1. 名称和编码为必填项,名称长度2-64个字符 -2. 编码如果不填写,系统会自动生成 -3. 状态只能选择'正常'或'停用' -4. 时间格式:YYYY-MM-DD HH:MM:SS或YYYY-MM-DD -5. 单次导入最多支持100条数据 -""" - - return ExcelUtil.get_excel_template( - header_list=header_list, - selector_header_list=selector_header_list, - option_list=option_list - ) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/user/controller.py b/backend/app/api/v1/module_system/user/controller.py index d49c45b9..f2fc9c21 100644 --- a/backend/app/api/v1/module_system/user/controller.py +++ b/backend/app/api/v1/module_system/user/controller.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- import urllib.parse -from fastapi import APIRouter, Depends, Body, Path, Query, Form, File, UploadFile, Request +from fastapi import APIRouter, Depends, Body, Path, UploadFile, Request from fastapi.responses import JSONResponse, StreamingResponse from sqlalchemy.ext.asyncio import AsyncSession from app.common.response import StreamResponse, SuccessResponse from app.common.request import PaginationService -from app.utils.common_util import bytes2file_response from app.core.router_class import OperationLogRoute +from app.utils.common_util import bytes2file_response from app.core.dependencies import db_getter, get_current_user, AuthPermission from app.core.base_params import PaginationQueryParam from app.core.base_schema import BatchSetAvailable diff --git a/backend/app/api/v1/module_system/user/crud.py b/backend/app/api/v1/module_system/user/crud.py index 56f03f21..f67cf453 100644 --- a/backend/app/api/v1/module_system/user/crud.py +++ b/backend/app/api/v1/module_system/user/crud.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- -from typing import Dict, List, Optional, Sequence, Union, Any +from typing import Sequence, Any from datetime import datetime from app.core.base_crud import CRUDBase - from app.api.v1.module_system.auth.schema import AuthSchema from .model import UserModel from .schema import UserCreateSchema, UserForgetPasswordSchema, UserUpdateSchema @@ -25,67 +24,65 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): self.auth = auth super().__init__(model=UserModel, auth=auth) - async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]: + async def get_by_id_crud(self, id: int, preload: list[str | Any] | None = None) -> UserModel | None: """ 根据id获取用户信息 参数: - id (int): 用户ID - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[UserModel]: 用户信息,如果不存在则为None + - UserModel | None: 用户信息,如果不存在则为None """ return await self.get( preload=preload, id=id, ) - async def get_by_username_crud(self, username: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]: + async def get_by_username_crud(self, username: str, preload: list[str | Any] | None = None) -> UserModel | None: """ 根据用户名获取用户信息 参数: - username (str): 用户名 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[UserModel]: 用户信息,如果不存在则为None + - UserModel | None: 用户信息,如果不存在则为None """ return await self.get( preload=preload, username=username, ) - - - async def get_by_mobile_crud(self, mobile: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]: + async def get_by_mobile_crud(self, mobile: str, preload: list[str | Any] | None = None) -> UserModel | None: """ 根据手机号获取用户信息 参数: - mobile (str): 手机号 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - - Optional[UserModel]: 用户信息,如果不存在则为None + - UserModel | None: 用户信息,如果不存在则为None """ return await self.get( preload=preload, mobile=mobile, ) - async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[UserModel]: + async def get_list_crud(self, search: dict | None = None, order_by: list[dict[str, str]] | None = None, preload: list[str | Any] | None = None) -> Sequence[UserModel]: """ 获取用户列表 参数: - - search (Dict | None): 查询参数对象。 - - order_by (List[Dict[str, str]] | None): 排序参数列表。 - - preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项 + - search (dict | None): 查询参数对象。 + - order_by (list[dict[str, str]] | None): 排序参数列表。 + - preload (list[str | Any] | None): 预加载关系,未提供时使用模型默认项 返回: - Sequence[UserModel]: 用户列表 + - Sequence[UserModel]: 用户列表 """ return await self.list( search=search, @@ -93,7 +90,7 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): preload=preload, ) - async def update_last_login_crud(self, id: int) -> Optional[UserModel]: + async def update_last_login_crud(self, id: int) -> UserModel | None: """ 更新用户最后登录时间 @@ -101,16 +98,16 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): - id (int): 用户ID 返回: - - Optional[UserModel]: 更新后的用户信息 + - UserModel | None: 更新后的用户信息 """ return await self.update(id=id, data={"last_login": datetime.now()}) - async def set_available_crud(self, ids: List[int], status: str) -> None: + async def set_available_crud(self, ids: list[int], status: str) -> None: """ 批量设置用户可用状态 参数: - - ids (List[int]): 用户ID列表 + - ids (list[int]): 用户ID列表 - status (bool): 可用状态 返回: @@ -118,13 +115,13 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): """ await self.set(ids=ids, status=status) - async def set_user_roles_crud(self, user_ids: List[int], role_ids: List[int]) -> None: + async def set_user_roles_crud(self, user_ids: list[int], role_ids: list[int]) -> None: """ 批量设置用户角色 参数: - - user_ids (List[int]): 用户ID列表 - - role_ids (List[int]): 角色ID列表 + - user_ids (list[int]): 用户ID列表 + - role_ids (list[int]): 角色ID列表 返回: - None: @@ -139,16 +136,15 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): relationship = obj.roles relationship.clear() relationship.extend(role_objs) - await self.db.flush() + await self.auth.db.flush() - - async def set_user_positions_crud(self, user_ids: List[int], position_ids: List[int]) -> None: + async def set_user_positions_crud(self, user_ids: list[int], position_ids: list[int]) -> None: """ 批量设置用户岗位 参数: - - user_ids (List[int]): 用户ID列表 - - position_ids (List[int]): 岗位ID列表 + - user_ids (list[int]): 用户ID列表 + - position_ids (list[int]): 岗位ID列表 返回: - None: @@ -163,9 +159,9 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): relationship = obj.positions relationship.clear() relationship.extend(position_objs) - await self.db.flush() + await self.auth.db.flush() - async def change_password_crud(self, id: int, password_hash: str) -> Optional[UserModel]: + async def change_password_crud(self, id: int, password_hash: str) -> UserModel: """ 修改用户密码 @@ -174,13 +170,11 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): - password_hash (str): 密码哈希值 返回: - - Optional[UserModel]: 更新后的用户信息 + - UserModel: 更新后的用户信息 """ return await self.update(id=id, data=UserUpdateSchema(password=password_hash)) - - - async def forget_password_crud(self, id: int, password_hash: str) -> Optional[UserModel]: + async def forget_password_crud(self, id: int, password_hash: str) -> UserModel: """ 重置密码 @@ -189,11 +183,11 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): - password_hash (str): 密码哈希值 返回: - - Optional[UserModel]: 更新后的用户信息 + - UserModel: 更新后的用户信息 """ return await self.update(id=id, data=UserUpdateSchema(password=password_hash)) - async def register_user_crud(self, data: UserForgetPasswordSchema) -> Optional[UserModel]: + async def register_user_crud(self, data: UserForgetPasswordSchema) -> UserModel: """ 用户注册 @@ -201,8 +195,6 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]): - data (UserForgetPasswordSchema): 用户注册信息 返回: - - Optional[UserModel]: 注册成功的用户信息,如果用户名已存在则返回None + - UserModel: 注册成功的用户信息 """ - if await self.get_by_username_crud(username=data.username): - return None return await self.create(data=UserCreateSchema(**data.model_dump())) \ No newline at end of file diff --git a/backend/app/api/v1/module_system/user/model.py b/backend/app/api/v1/module_system/user/model.py index 22b7c31a..0ac6c17d 100644 --- a/backend/app/api/v1/module_system/user/model.py +++ b/backend/app/api/v1/module_system/user/model.py @@ -5,7 +5,7 @@ from datetime import datetime from sqlalchemy import Boolean, String, Integer, DateTime, ForeignKey from sqlalchemy.orm import relationship, Mapped, mapped_column -from app.core.base_model import MappedBase, ModelMixin, UserMixin, TenantMixin, CustomerMixin +from app.core.base_model import MappedBase, ModelMixin, UserMixin if TYPE_CHECKING: from app.api.v1.module_system.dept.model import DeptModel @@ -59,47 +59,13 @@ class UserPositionsModel(MappedBase): ) -class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): +class UserModel(ModelMixin, UserMixin): """ 用户模型 - - 用户类型与数据隔离关系: - ==================== - - 系统用户(user_type=0): - * tenant_id=1(系统租户) - * customer_id=None - * 用于平台管理,可管理所有租户 - - - 租户管理员(user_type=1): - * tenant_id>1 - * customer_id=None - * 可管理本租户内所有数据(包括所有客户) - - - 租户普通用户(user_type=1): - * tenant_id>1 - * customer_id=None - * 数据权限由role.data_scope控制 - - - 客户用户(user_type=2): - * tenant_id>1 - * customer_id>1 - * 只能访问其所属客户的数据 - - 数据权限实现机制: - ================== - 通过角色的data_scope字段和用户-部门-角色关系实现: - - 1(仅本人): WHERE created_id = current_user.id - - 2(本部门): WHERE user.dept_id = current_user.dept_id - - 3(本部门及以下): WHERE dept.tree_path LIKE 'current_user.dept.tree_path%' - - 4(全部数据): WHERE tenant_id = current_user.tenant_id (AND customer_id IS NULL OR customer_id = current_user.customer_id) - - 5(自定义): WHERE dept_id IN (SELECT dept_id FROM role_depts WHERE role_id IN current_user.role_ids) - - 客户用户额外限制: - - 无论data_scope如何,都必须加上: AND customer_id = current_user.customer_id """ __tablename__: str = "sys_user" __table_args__: dict[str, str] = ({'comment': '用户表'}) - __loader_options__: list[str] = ["dept", "roles", "positions", "created_by", "updated_by", "tenant", "customer"] + __loader_options__: list[str] = ["dept", "roles", "positions", "created_by", "updated_by"] username: Mapped[str] = mapped_column(String(32), nullable=False, unique=True, comment="用户名/登录账号") password: Mapped[str] = mapped_column(String(255), nullable=False, comment="密码哈希") @@ -115,8 +81,6 @@ class UserModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): github_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="Github登录") wx_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="微信登录") qq_login: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="QQ登录") - user_type: Mapped[str] = mapped_column(String(32), nullable=False, default="0", comment="用户类型(0:系统用户 1:租户用户 2:客户用户)") - salt: Mapped[str | None] = mapped_column(String(255), nullable=True, comment="加密盐") dept_id: Mapped[int | None] = mapped_column( Integer, diff --git a/backend/app/api/v1/module_system/user/schema.py b/backend/app/api/v1/module_system/user/schema.py index 6ab3bef5..973e2826 100644 --- a/backend/app/api/v1/module_system/user/schema.py +++ b/backend/app/api/v1/module_system/user/schema.py @@ -1,32 +1,32 @@ # -*- coding: utf-8 -*- -from typing import Optional, List from fastapi import Query from pydantic import BaseModel, ConfigDict, Field, EmailStr, field_validator from urllib.parse import urlparse from app.core.validator import DateTimeStr, mobile_validator -from app.core.base_schema import BaseSchema, CommonSchema, UserBySchema, TenantSchema, CustomerSchema +from app.core.base_schema import BaseSchema, CommonSchema, UserBySchema from app.core.validator import DateTimeStr +from app.api.v1.module_system.menu.schema import MenuOutSchema from app.api.v1.module_system.role.schema import RoleOutSchema class CurrentUserUpdateSchema(BaseModel): """基础用户信息""" - name: Optional[str] = Field(default=None, max_length=32, description="名称") - mobile: Optional[str] = Field(default=None, description="手机号") - email: Optional[EmailStr] = Field(default=None, description="邮箱") - gender: Optional[str] = Field(default=None, description="性别") - avatar: Optional[str] = Field(default=None, description="头像") + name: str | None = Field(default=None, max_length=32, description="名称") + mobile: str | None = Field(default=None, description="手机号") + email: EmailStr | None = Field(default=None, description="邮箱") + gender: str | None = Field(default=None, description="性别") + avatar: str | None = Field(default=None, description="头像") @field_validator("mobile") @classmethod - def validate_mobile(cls, value: Optional[str]): + def validate_mobile(cls, value: str | None): return mobile_validator(value) @field_validator("avatar") @classmethod - def validate_avatar(cls, value: Optional[str]): + def validate_avatar(cls, value: str | None): if not value: return value parsed = urlparse(value) @@ -37,18 +37,17 @@ class CurrentUserUpdateSchema(BaseModel): class UserRegisterSchema(BaseModel): """注册""" - name: Optional[str] = Field(default=None, max_length=32, description="名称") - mobile: Optional[str] = Field(default=None, description="手机号") + name: str | None = Field(default=None, max_length=32, description="名称") + mobile: str | None = Field(default=None, description="手机号") username: str = Field(..., max_length=32, description="账号") password: str = Field(..., max_length=128, description="密码哈希值") - role_ids: Optional[List[int]] = Field(default=[1], description='角色ID') - created_id: Optional[int] = Field(default=1, description='创建人ID') - description: Optional[str] = Field(default=None, max_length=255, description="备注") - user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型") + role_ids: list[int] | None = Field(default=[1], description='角色ID') + created_id: int | None = Field(default=1, description='创建人ID') + description: str | None = Field(default=None, max_length=255, description="备注") @field_validator("mobile") @classmethod - def validate_mobile(cls, value: Optional[str]): + def validate_mobile(cls, value: str | None): return mobile_validator(value) @field_validator("username") @@ -68,11 +67,11 @@ class UserForgetPasswordSchema(BaseModel): """忘记密码""" username: str = Field(..., max_length=32, description="用户名") new_password: str = Field(..., max_length=128, description="新密码") - mobile: Optional[str] = Field(default=None, description="手机号") + mobile: str | None = Field(default=None, description="手机号") @field_validator("mobile") @classmethod - def validate_mobile(cls, value: Optional[str]): + def validate_mobile(cls, value: str | None): return mobile_validator(value) @@ -92,54 +91,50 @@ class UserCreateSchema(CurrentUserUpdateSchema): """新增""" model_config = ConfigDict(from_attributes=True) - username: Optional[str] = Field(default=None, max_length=32, description="用户名") - password: Optional[str] = Field(default=None, max_length=128, description="密码哈希值") + username: str | None = Field(default=None, max_length=32, description="用户名") + password: str | None = Field(default=None, max_length=128, description="密码哈希值") status: str = Field(default="0", description="是否可用") - description: Optional[str] = Field(default=None, max_length=255, description="备注") - user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型") - is_superuser: Optional[bool] = Field(default=False, description="是否超管") - tenant_id: Optional[int] = Field(default=None, description='租户ID') - dept_id: Optional[int] = Field(default=None, description='部门ID') - role_ids: Optional[List[int]] = Field(default=[], description='角色ID') - position_ids: Optional[List[int]] = Field(default=[], description='岗位ID') - + description: str | None = Field(default=None, max_length=255, description="备注") + is_superuser: bool | None = Field(default=False, description="是否超管") + dept_id: int | None = Field(default=None, description='部门ID') + role_ids: list[int] | None = Field(default=[], description='角色ID') + position_ids: list[int] | None = Field(default=[], description='岗位ID') class UserUpdateSchema(UserCreateSchema): """更新""" model_config = ConfigDict(from_attributes=True) - last_login: Optional[DateTimeStr] = Field(default=None, description="最后登录时间") + last_login: DateTimeStr | None = Field(default=None, description="最后登录时间") -class UserOutSchema(UserUpdateSchema, BaseSchema, UserBySchema, TenantSchema, CustomerSchema): +class UserOutSchema(UserUpdateSchema, BaseSchema, UserBySchema): """响应""" model_config = ConfigDict(arbitrary_types_allowed=True, from_attributes=True) - is_superuser: bool = Field(default=False, description="是否超管") - gitee_login: Optional[str] = Field(default=None, max_length=32, description="Gitee登录") - github_login: Optional[str] = Field(default=None, max_length=32, description="Github登录") - wx_login: Optional[str] = Field(default=None, max_length=32, description="微信登录") - qq_login: Optional[str] = Field(default=None, max_length=32, description="QQ登录") - user_type: Optional[str] = Field(default="0", max_length=32, description="用户类型") - salt: Optional[str] = Field(default=None, max_length=255, description="加密盐") - dept_name: Optional[str] = Field(default=None, description='部门名称') - dept: Optional[CommonSchema] = Field(default=None, description='部门') - roles: Optional[List[RoleOutSchema]] = Field(default=[], description='角色') - positions: Optional[List[CommonSchema]] = Field(default=[], description='岗位') - + gitee_login: str | None = Field(default=None, max_length=32, description="Gitee登录") + github_login: str | None = Field(default=None, max_length=32, description="Github登录") + wx_login: str | None = Field(default=None, max_length=32, description="微信登录") + qq_login: str | None = Field(default=None, max_length=32, description="QQ登录") + dept_name: str | None = Field(default=None, description='部门名称') + dept: CommonSchema | None = Field(default=None, description='部门') + positions: list[CommonSchema] | None = Field(default=[], description='岗位') + roles: list[RoleOutSchema] | None = Field(default=[], description='角色') + menus: list[MenuOutSchema] | None = Field(default=[], description='菜单') class UserQueryParam: """用户管理查询参数""" def __init__( self, - username: Optional[str] = Query(None, description="用户名"), - name: Optional[str] = Query(None, description="名称"), - mobile: Optional[str] = Query(None, description="手机号", pattern=r'^1[3-9]\d{9}$'), - 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[str] = Query(None, description="是否可用"), - created_time: Optional[list[DateTimeStr]] = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), - created_id: Optional[int] = Query(None, description="创建人"), + username: str | None = Query(None, description="用户名"), + name: str | None = Query(None, description="名称"), + mobile: str | None = Query(None, description="手机号", pattern=r'^1[3-9]\d{9}$'), + email: str | None = Query(None, description="邮箱", pattern=r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'), + dept_id: int | None = Query(None, description="部门ID"), + status: str | None = Query(None, description="是否可用"), + created_time: list[DateTimeStr] | None = Query(None, description="创建时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + updated_time: list[DateTimeStr] | None = Query(None, description="更新时间范围", example=["2025-01-01 00:00:00", "2025-12-31 23:59:59"]), + created_id: int | None = Query(None, description="创建人"), + updated_id: int | None = Query(None, description="更新人"), ) -> None: # 模糊查询字段 @@ -151,8 +146,12 @@ class UserQueryParam: # 精确查询字段 self.dept_id = dept_id self.created_id = created_id + self.updated_id = updated_id self.status = status # 时间范围查询 if created_time and len(created_time) == 2: self.created_time = ("between", (created_time[0], created_time[1])) + if updated_time and len(updated_time) == 2: + self.updated_time = ("between", (updated_time[0], updated_time[1])) + \ No newline at end of file diff --git a/backend/app/api/v1/module_system/user/service.py b/backend/app/api/v1/module_system/user/service.py index 8ec7ad7a..75653047 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, Optional +from typing import Any from fastapi import UploadFile import pandas as pd @@ -19,7 +19,6 @@ from ..menu.crud import MenuCRUD from ..dept.crud import DeptCRUD from ..auth.schema import AuthSchema from ..menu.schema import MenuOutSchema -from ..tenant.service import TenantService from .crud import UserCRUD from .schema import ( CurrentUserUpdateSchema, @@ -38,7 +37,7 @@ class UserService: """用户模块服务层""" @classmethod - async def get_detail_by_id_service(cls, auth: AuthSchema, id: int) -> Dict: + async def get_detail_by_id_service(cls, auth: AuthSchema, id: int) -> dict: """ 根据ID获取用户详情 @@ -47,7 +46,7 @@ class UserService: - id (int): 用户ID 返回: - - Dict: 用户详情字典 + - dict: 用户详情字典 """ user = await UserCRUD(auth).get_by_id_crud(id=id) if not user: @@ -59,22 +58,21 @@ class UserService: UserOutSchema.dept_name = dept.name if dept else None else: UserOutSchema.dept_name = None - - + return UserOutSchema.model_validate(user).model_dump() @classmethod - async def get_user_list_service(cls, auth: AuthSchema, search: Optional[UserQueryParam] = None, order_by: Optional[List[Dict[str, str]]] = None) -> List[Dict]: + async def get_user_list_service(cls, auth: AuthSchema, search: UserQueryParam | None = None, order_by: list[dict[str, str]] | None = None) -> list[dict]: """ 获取用户列表 参数: - auth (AuthSchema): 认证信息模型 - search (UserQueryParam | None): 查询参数对象。 - - order_by (List[Dict[str, str]] | None): 排序参数列表。 + - order_by (list[dict[str, str]] | None): 排序参数列表。 返回: - - List[Dict]: 用户详情字典列表 + - list[dict]: 用户详情字典列表 """ user_list = await UserCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by) user_dict_list = [] @@ -85,7 +83,7 @@ class UserService: return user_dict_list @classmethod - async def create_user_service(cls, data: UserCreateSchema, auth: AuthSchema) -> Dict: + async def create_user_service(cls, data: UserCreateSchema, auth: AuthSchema) -> dict: """ 创建用户 @@ -94,7 +92,7 @@ class UserService: - auth (AuthSchema): 认证信息模型 返回: - - Dict: 创建后的用户详情字典 + - dict: 创建后的用户详情字典 """ if not data.username: raise CustomException(msg="用户名不能为空") @@ -111,32 +109,16 @@ class UserService: dept = await DeptCRUD(auth).get_by_id_crud(id=data.dept_id) if not dept: raise CustomException(msg='部门不存在') - # 创建用户 if data.password: data.password = PwdUtil.set_password_hash(password=data.password) user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"}) - - # 多租户隔离:确保用户正确关联到创建者的租户和客户 - # 非系统管理员创建的用户,自动继承创建者的租户和客户信息 - if auth.user and not auth.user.is_superuser: - # 检查是否尝试为其他租户创建用户 - if data.tenant_id and data.tenant_id != auth.user.tenant_id: - raise CustomException(msg='没有权限为其他租户创建用户') - # 自动设置租户ID - user_dict["tenant_id"] = auth.user.tenant_id - # 如果创建者是客户用户,自动设置客户ID - if auth.user.customer_id: - user_dict["customer_id"] = auth.user.customer_id - # 限制用户类型,非系统管理员只能创建普通用户 - user_dict["user_type"] = "0" - # 创建用户 new_user = await UserCRUD(auth).create(data=user_dict) - - # 设置角色和岗位 + # 设置角色 if data.role_ids and len(data.role_ids) > 0: await UserCRUD(auth).set_user_roles_crud(user_ids=[new_user.id], role_ids=data.role_ids) + # 设置岗位 if data.position_ids and len(data.position_ids) > 0: await UserCRUD(auth).set_user_positions_crud(user_ids=[new_user.id], position_ids=data.position_ids) @@ -144,7 +126,7 @@ class UserService: return new_user_dict @classmethod - async def update_user_service(cls, id: int, data: UserUpdateSchema, auth: AuthSchema) -> Dict: + async def update_user_service(cls, id: int, data: UserUpdateSchema, auth: AuthSchema) -> dict: """ 更新用户 @@ -157,10 +139,8 @@ class UserService: - Dict: 更新后的用户详情字典 """ if not data.username: - raise CustomException(msg="用户名不能为空") - # 检查是否是超级管理员 - if data.is_superuser: - raise CustomException(msg='超级管理员系统唯一') + raise CustomException(msg="账号不能为空") + # 检查用户是否存在 user = await UserCRUD(auth).get_by_id_crud(id=id) if not user: @@ -169,16 +149,11 @@ class UserService: # 检查是否尝试修改超级管理员 if user.is_superuser: raise CustomException(msg='超级管理员不允许修改') - - # 多租户权限检查:非系统管理员只能修改同租户的用户 - if auth.user and not auth.user.is_superuser: - if user.tenant_id != auth.user.tenant_id: - raise CustomException(msg='没有权限修改其他租户的用户') # 检查用户名是否重复 exist_user = await UserCRUD(auth).get_by_username_crud(username=data.username) if exist_user and exist_user.id != id: - raise CustomException(msg='已存在相同的用户名') + raise CustomException(msg='已存在相同的账号') # 新增:检查手机号是否重复 if data.mobile: exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile) @@ -203,7 +178,7 @@ class UserService: update_dict['password'] = PwdUtil.set_password_hash(password=data.password) # 更新用户 - 排除不应被修改的字段 - user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids", "last_login", "password", "tenant_id", "customer_id", "user_type"}) + user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids", "last_login", "password"}) user_dict.update(update_dict) new_user = await UserCRUD(auth).update(id=id, data=user_dict) @@ -253,11 +228,6 @@ class UserService: raise CustomException(msg="用户已启用,不能删除") if auth.user and auth.user.id == id: raise CustomException(msg="不能删除当前登陆用户") - - # 多租户权限检查:非系统管理员只能删除同租户的用户 - if auth.user and not auth.user.is_superuser: - if user.tenant_id != auth.user.tenant_id: - raise CustomException(msg='没有权限删除其他租户的用户') # 删除用户角色关联数据 await UserCRUD(auth).set_user_roles_crud(user_ids=ids, role_ids=[]) @@ -268,7 +238,7 @@ class UserService: await UserCRUD(auth).delete(ids=ids) @classmethod - async def get_current_user_info_service(cls, auth: AuthSchema) -> Dict: + async def get_current_user_info_service(cls, auth: AuthSchema) -> dict: """ 获取当前用户信息 @@ -311,7 +281,7 @@ class UserService: return user_dict @classmethod - async def update_current_user_info_service(cls, auth: AuthSchema, data: CurrentUserUpdateSchema) -> Dict: + async def update_current_user_info_service(cls, auth: AuthSchema, data: CurrentUserUpdateSchema) -> dict: """ 更新当前用户信息 @@ -364,7 +334,7 @@ class UserService: await UserCRUD(auth).set_available_crud(ids=data.ids, status=data.status) @classmethod - async def upload_avatar_service(cls, base_url: str, file: UploadFile) -> Dict: + async def upload_avatar_service(cls, base_url: str, file: UploadFile) -> dict: """ 上传用户头像 @@ -375,8 +345,6 @@ class UserService: 返回: - Dict: 上传头像响应字典 """ - if not file: - raise CustomException(msg="请选择要上传的文件") filename, filepath, file_url = await UploadUtil.upload_file(file=file, base_url=base_url) return UploadResponseSchema( @@ -387,7 +355,7 @@ class UserService: ).model_dump() @classmethod - async def change_user_password_service(cls, auth: AuthSchema, data: UserChangePasswordSchema) -> Dict: + async def change_user_password_service(cls, auth: AuthSchema, data: UserChangePasswordSchema) -> dict: """ 修改用户密码 @@ -416,7 +384,7 @@ class UserService: return UserOutSchema.model_validate(new_user).model_dump() @classmethod - async def reset_user_password_service(cls, auth: AuthSchema, data: ResetPasswordSchema) -> Dict: + async def reset_user_password_service(cls, auth: AuthSchema, data: ResetPasswordSchema) -> dict: """ 重置用户密码 @@ -445,7 +413,7 @@ class UserService: return UserOutSchema.model_validate(new_user).model_dump() @classmethod - async def register_user_service(cls, auth: AuthSchema, data: UserRegisterSchema) -> Dict: + async def register_user_service(cls, auth: AuthSchema, data: UserRegisterSchema) -> dict: """ 用户注册 @@ -471,12 +439,6 @@ class UserService: # 设置创建人ID if auth.user and auth.user.id: create_dict["created_id"] = auth.user.id - # 多租户隔离:如果是租户用户注册,自动关联到当前租户 - if auth.user.tenant_id: - create_dict["tenant_id"] = auth.user.tenant_id - # 如果是客户用户注册,自动关联到当前客户 - if auth.user.customer_id: - create_dict["customer_id"] = auth.user.customer_id result = await UserCRUD(auth).create(data=create_dict) if data.role_ids: @@ -484,7 +446,7 @@ class UserService: return UserOutSchema.model_validate(result).model_dump() @classmethod - async def forget_password_service(cls, auth: AuthSchema, data: UserForgetPasswordSchema) -> Dict: + async def forget_password_service(cls, auth: AuthSchema, data: UserForgetPasswordSchema) -> dict: """ 用户忘记密码 @@ -630,7 +592,7 @@ class UserService: ) @classmethod - async def export_user_list_service(cls, user_list: List[Dict[str, Any]]) -> bytes: + async def export_user_list_service(cls, user_list: list[dict[str, Any]]) -> bytes: """ 导出用户列表为Excel文件 @@ -659,14 +621,14 @@ class UserService: 'description': '备注', 'created_time': '创建时间', 'updated_time': '更新时间', - 'creator': '创建者', + 'updated_id': '更新者ID', } # 复制数据并转换 # creator = {'id': 1, 'name': '管理员', 'username': 'admin'} data = user_list.copy() for item in data: - item['status'] = '启用' if item.get('status') else '停用' + item['status'] = '启用' if item.get('status') == "0" else '停用' gender = item.get('gender') item['gender'] = '男' if gender == '1' else ('女' if gender == '2' else '未知') item['is_superuser'] = '是' if item.get('is_superuser') else '否' diff --git a/backend/app/common/dataclasses.py b/backend/app/common/dataclasses.py index dca6ccfa..0fb7d58d 100644 --- a/backend/app/common/dataclasses.py +++ b/backend/app/common/dataclasses.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- import dataclasses - from datetime import datetime - from fastapi import Response diff --git a/backend/app/common/request.py b/backend/app/common/request.py index 33569627..468e1764 100644 --- a/backend/app/common/request.py +++ b/backend/app/common/request.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Dict, List, Optional +from typing import Any from pydantic import ConfigDict, Field, BaseModel from pydantic.alias_generators import to_camel @@ -12,30 +12,30 @@ class PageResultSchema(BaseModel): """分页查询结果模型""" model_config = ConfigDict(alias_generator=to_camel, from_attributes=True) - page_no: Optional[int] = Field(default=None, ge=1, description="页码,默认为1") - page_size: Optional[int] = Field(default=None, ge=1, description="页面大小,默认为10") + page_no: int | None = Field(default=None, ge=1, description="页码,默认为1") + page_size: int | None = Field(default=None, ge=1, description="页面大小,默认为10") total: int = Field(default=0, ge=0, description="总记录数") - has_next: Optional[bool] = Field(default=False, description="是否有下一页") - items: Optional[List[Any]] = Field(default_factory=list, description="分页后的数据列表") + has_next: bool | None = Field(default=False, description="是否有下一页") + items: list[Any] = Field(default_factory=list, description="分页后的数据列表") class PaginationService: """分页服务类""" @staticmethod - async def paginate(data_list: List[Any], page_no: Optional[int] = None, page_size: Optional[int] = None) -> Dict[str, Any]: + async def paginate(data_list: list[Any], page_no: int | None = None, page_size: int | None = None) -> dict[str, Any]: """ 分页数据处理。 输入数据列表和分页信息,返回分页数据列表结果。 未传入 page_no 和 page_size 时,使用默认值进行分页。 参数: - - data_list (List[Any]): 原始数据列表。 + - data_list (list[Any]): 原始数据列表。 - page_no (int | None): 当前页码,默认 None。 - page_size (int | None): 每页数据量,默认 None。 返回: - - Dict[str, Any]: 分页数据对象。 + - dict[str, Any]: 分页数据对象。 异常: - CustomException: 当分页参数不合法时抛出。 diff --git a/backend/app/common/response.py b/backend/app/common/response.py index 13baafc3..01ab3374 100644 --- a/backend/app/common/response.py +++ b/backend/app/common/response.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Mapping, Optional +from typing import Any, Mapping from fastapi import status from fastapi.responses import JSONResponse, StreamingResponse, FileResponse from starlette.background import BackgroundTask @@ -12,7 +12,7 @@ class ResponseSchema(BaseModel): """响应模型""" code: int = Field(default=RET.OK.code, description="业务状态码") msg: str = Field(default=RET.OK.msg, description="响应消息") - data: Optional[Any] = Field(default=None, description="响应数据") + data: Any = Field(default=None, description="响应数据") status_code: int = Field(default=status.HTTP_200_OK, description="HTTP状态码") success: bool = Field(default=True, description='操作是否成功') @@ -22,7 +22,7 @@ class SuccessResponse(JSONResponse): def __init__( self, - data: Optional[Any] = None, + data: Any = None, msg: str = RET.OK.msg, code: int = RET.OK.code, status_code: int = status.HTTP_200_OK, @@ -56,7 +56,7 @@ class ErrorResponse(JSONResponse): def __init__( self, - data: Optional[Any] = None, + data: Any = None, msg: str = RET.ERROR.msg, code: int = RET.ERROR.code, status_code: int = status.HTTP_400_BAD_REQUEST, @@ -66,7 +66,7 @@ class ErrorResponse(JSONResponse): 初始化错误响应类 参数: - - data (Any | None): 响应数据。 + - data (Any): 响应数据。 - msg (str): 响应消息。 - code (int): 业务状态码。 - status_code (int): HTTP 状态码。 @@ -127,8 +127,8 @@ class UploadFileResponse(FileResponse): file_path: str, filename: str, media_type: str = "application/octet-stream", - headers: Optional[Mapping[str, str]] = None, - background: Optional[BackgroundTask] = None, + headers: Mapping[str, str] | None = None, + background: BackgroundTask | None = None, status_code: int = 200 ): """ diff --git a/backend/app/core/base_crud.py b/backend/app/core/base_crud.py index a0390ebd..c9e50fa4 100644 --- a/backend/app/core/base_crud.py +++ b/backend/app/core/base_crud.py @@ -35,8 +35,6 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """ self.model = model self.auth = auth - self.db = auth.db - self.current_user = auth.user async def get(self, preload: Optional[List[Union[str, Any]]] = None, **kwargs) -> Optional[ModelType]: """ @@ -61,7 +59,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): sql = await self.__filter_permissions(sql) - result: Result = await self.db.execute(sql) + result: Result = await self.auth.db.execute(sql) obj = result.scalars().first() return obj except Exception as e: @@ -90,7 +88,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): for opt in self.__loader_options(preload): sql = sql.options(opt) sql = await self.__filter_permissions(sql) - result: Result = await self.db.execute(sql) + result: Result = await self.auth.db.execute(sql) return result.scalars().all() except Exception as e: raise CustomException(msg=f"列表查询失败: {str(e)}") @@ -130,7 +128,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): sql = sql.options(opt) sql = await self.__filter_permissions(sql) - result: Result = await self.db.execute(sql) + result: Result = await self.auth.db.execute(sql) return result.scalars().all() except Exception as e: raise CustomException(msg=f"树形列表查询失败: {str(e)}") @@ -168,10 +166,10 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): count_sql = count_sql.where(*conditions) count_sql = await self.__filter_permissions(count_sql) - total_result = await self.db.execute(count_sql) + total_result = await self.auth.db.execute(count_sql) total = total_result.scalar() or 0 - result: Result = await self.db.execute(sql.offset(offset).limit(limit)) + result: Result = await self.auth.db.execute(sql.offset(offset).limit(limit)) objs = result.scalars().all() return { @@ -202,19 +200,15 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): obj = self.model(**obj_dict) # 设置字段值(只检查一次current_user) - if self.current_user: + if self.auth.user: if hasattr(obj, "created_id"): - setattr(obj, "created_id", self.current_user.id) + setattr(obj, "created_id", self.auth.user.id) if hasattr(obj, "updated_id"): - setattr(obj, "updated_id", self.current_user.id) - if hasattr(obj, "tenant_id"): - setattr(obj, "tenant_id", self.current_user.tenant_id) - if hasattr(obj, "customer_id"): - setattr(obj, "customer_id", self.current_user.customer_id) + setattr(obj, "updated_id", self.auth.user.id) - self.db.add(obj) - await self.db.flush() - await self.db.refresh(obj) + self.auth.db.add(obj) + await self.auth.db.flush() + await self.auth.db.refresh(obj) return obj except Exception as e: raise CustomException(msg=f"创建失败: {str(e)}") @@ -240,16 +234,16 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): raise CustomException(msg="更新对象不存在") # 设置字段值(只检查一次current_user) - if self.current_user: + if self.auth.user: if hasattr(obj, "updated_id"): - setattr(obj, "updated_id", self.current_user.id) + setattr(obj, "updated_id", self.auth.user.id) for key, value in obj_dict.items(): if hasattr(obj, key): setattr(obj, key, value) - await self.db.flush() - await self.db.refresh(obj) + await self.auth.db.flush() + await self.auth.db.refresh(obj) return obj except Exception as e: raise CustomException(msg=f"更新失败: {str(e)}") @@ -272,8 +266,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): if len(pk_cols) > 1: raise CustomException(msg="暂不支持复合主键的批量删除") sql = delete(self.model).where(pk_cols[0].in_(ids)) - await self.db.execute(sql) - await self.db.flush() + await self.auth.db.execute(sql) + await self.auth.db.flush() except Exception as e: raise CustomException(msg=f"删除失败: {str(e)}") @@ -286,8 +280,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """ try: sql = delete(self.model) - await self.db.execute(sql) - await self.db.flush() + await self.auth.db.execute(sql) + await self.auth.db.flush() except Exception as e: raise CustomException(msg=f"清空失败: {str(e)}") @@ -310,8 +304,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): if len(pk_cols) > 1: raise CustomException(msg="暂不支持复合主键的批量更新") sql = update(self.model).where(pk_cols[0].in_(ids)).values(**kwargs) - await self.db.execute(sql) - await self.db.flush() + await self.auth.db.execute(sql) + await self.auth.db.flush() except Exception as e: raise CustomException(msg=f"批量更新失败: {str(e)}") @@ -319,16 +313,8 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """ 过滤数据权限(仅用于Select)。 """ - # 在初始化场景下,如果不需要检查数据权限,则直接返回原始查询 - if not self.auth.check_data_scope: - return sql - # 在正常业务场景下,需要确保用户存在 - # if not self.current_user: - # raise sql filter = Permission( - db=self.db, model=self.model, - current_user=self.current_user, auth=self.auth ) return await filter.filter_query(sql) diff --git a/backend/app/core/base_model.py b/backend/app/core/base_model.py index 2e4dfafd..ec614c2e 100644 --- a/backend/app/core/base_model.py +++ b/backend/app/core/base_model.py @@ -7,8 +7,6 @@ from sqlalchemy.ext.asyncio import AsyncAttrs from typing import TYPE_CHECKING if TYPE_CHECKING: - from app.api.v1.module_system.customer.model import CustomerModel - from app.api.v1.module_system.tenant.model import TenantModel from app.api.v1.module_system.user.model import UserModel from app.utils.common_util import uuid4_str @@ -38,29 +36,13 @@ class ModelMixin(MappedBase): 数据隔离设计原则: ================== - - 1. 租户隔离 (tenant_id): - - 用于实现多租户SaaS架构的核心隔离 - - 系统租户(tenant_id=1): 管理平台级数据 - - 普通租户(tenant_id>1): 各自独立的业务数据 - - 大部分业务表都需要tenant_id字段 - - 2. 客户隔离 (customer_id): - - 用于租户内部的二级隔离 - - 仅部分表需要,如用户、日志等 - - 客户属于租户,实现租户下的业务单元隔离 - - 3. 数据权限 (created_id/updated_id): + 数据权限 (created_id/updated_id): - 配合角色的data_scope字段实现精细化权限控制 - - 1:仅本人 → WHERE created_id = current_user_id - - 2:本部门 → WHERE user.dept_id = current_user.dept_id - - 3:本部门及以下 → WHERE dept.tree_path LIKE 'current_dept_path%' - - 4:全部数据 → WHERE tenant_id = current_tenant_id - - 5:自定义 → WHERE dept_id IN (role_depts) - - 继承规则: - - 需要租户隔离的业务表继承此类 - - 不需要隔离的表(如租户表本身)只继承MappedBase + - 1:仅本人 + - 2:本部门 + - 3:本部门及以下 + - 4:全部数据 + - 5:自定义 SQLAlchemy加载策略说明: - select(默认): 延迟加载,访问时单独查询 @@ -137,70 +119,3 @@ class UserMixin(MappedBase): viewonly=True, uselist=False ) - - -class TenantMixin(MappedBase): - """ - 租户字段 Mixin - - 用于实现多租户SaaS架构的核心隔离 - """ - __abstract__: bool = True - - tenant_id: Mapped[int | None] = mapped_column( - Integer, - ForeignKey('sys_tenant.id', ondelete="CASCADE", onupdate="CASCADE"), - nullable=False, - index=True, - comment="所属租户ID" - ) - - @declared_attr - def tenant(cls) -> Mapped["TenantModel"]: - """ - 租户关联关系(延迟加载,避免循环依赖) - """ - return relationship( - "TenantModel", - primaryjoin=f"{cls.__name__}.tenant_id == TenantModel.id", - lazy="selectin", - foreign_keys=lambda: [cls.tenant_id], - viewonly=True, - uselist=False - ) - - -class CustomerMixin(MappedBase): - """ - 客户隔离字段 Mixin - - 用于租户内部的二级数据隔离 - 仅部分表需要此字段: - - 客户用户 (必填) - - 客户业务数据 (根据业务需求) - - 客户专属通知/日志 (可选) - """ - __abstract__: bool = True - - customer_id: Mapped[int | None] = mapped_column( - Integer, - ForeignKey('sys_customer.id', ondelete="CASCADE", onupdate="CASCADE"), - default=None, - nullable=True, - index=True, - comment="所属客户ID(NULL表示租户级数据,>0表示客户级数据)" - ) - - @declared_attr - def customer(cls) -> Mapped["CustomerModel"]: - """ - 客户关联关系(延迟加载,避免循环依赖) - """ - return relationship( - "CustomerModel", - primaryjoin=f"{cls.__name__}.customer_id == CustomerModel.id", - lazy="selectin", - foreign_keys=lambda: [cls.customer_id], - viewonly=True, - uselist=False - ) diff --git a/backend/app/core/base_schema.py b/backend/app/core/base_schema.py index 29bac3e4..24ba16c3 100644 --- a/backend/app/core/base_schema.py +++ b/backend/app/core/base_schema.py @@ -28,11 +28,12 @@ class BaseSchema(BaseModel): id: int | None = Field(default=None, description="主键ID") uuid: str | None = Field(default=None, description="UUID") - status: str | None = Field(default=None, description="状态") + status: str = Field(default="0", description="状态") description: str | None = Field(default=None, description="描述") created_time: DateTimeStr | None = Field(default=None, description="创建时间") updated_time: DateTimeStr | None = Field(default=None, description="更新时间") + class UserBySchema(BaseModel): """通用创建模型,包含基础字段和审计字段""" model_config = ConfigDict(from_attributes=True) @@ -42,21 +43,6 @@ class UserBySchema(BaseModel): updated_id: int | None = Field(default=None, description="更新人ID") updated_by: UserInfoSchema | None = Field(default=None, description="更新人信息") -class TenantSchema(BaseModel): - """租户模型""" - model_config = ConfigDict(from_attributes=True) - - tenant_id: int | None = Field(default=None, description="所属租户ID") - tenant: CommonSchema | None = Field(default=None, description="租户信息") - - -class CustomerSchema(BaseModel): - """客户模型""" - model_config = ConfigDict(from_attributes=True) - - customer_id: int | None = Field(default=None, description="所属客户ID") - customer: CommonSchema | None = Field(default=None, description="客户信息") - class BatchSetAvailable(BaseModel): """批量设置可用状态的请求模型""" diff --git a/backend/app/core/dependencies.py b/backend/app/core/dependencies.py index 7b58bfd3..b1e8ec1b 100644 --- a/backend/app/core/dependencies.py +++ b/backend/app/core/dependencies.py @@ -106,9 +106,6 @@ async def get_current_user( # 设置请求上下文 request.scope["user_id"] = user.id request.scope["user_username"] = user.username - request.scope["user_tenant_id"] = user.tenant_id - request.scope["user_customer_id"] = user.customer_id - # 过滤可用的角色和职位 if hasattr(user, 'roles'): diff --git a/backend/app/core/discover.py b/backend/app/core/discover.py index 1a322313..52353fcd 100644 --- a/backend/app/core/discover.py +++ b/backend/app/core/discover.py @@ -16,14 +16,11 @@ from __future__ import annotations -from ast import List -from enum import Enum import importlib -import sys +from enum import Enum from pathlib import Path -from typing import Callable, Dict, Iterable, Optional, Set, Tuple, Union, Any +from typing import Callable, Iterable, Any from functools import wraps - from fastapi import APIRouter from app.core.logger import log @@ -66,9 +63,9 @@ class DiscoverRouter: self, module_prefix: str = "module_", base_package: str = "app.api.v1", - prefix_map: Optional[Dict[str, str]] = None, - exclude_dirs: Optional[Set[str]] = None, - exclude_files: Optional[Set[str]] = None, + prefix_map: dict[str, str] | None = None, + exclude_dirs: set[str] | None = None, + exclude_files: set[str] | None = None, auto_discover: bool = True, debug: bool = False ) -> None: @@ -91,8 +88,8 @@ class DiscoverRouter: self.exclude_files = exclude_files or set() self.debug = debug self._router = APIRouter() - self._seen_router_ids: Set[int] = set() - self._discovery_stats: Dict[str, int] = { + self._seen_router_ids: set[int] = set() + self._discovery_stats: dict[str, int] = { "scanned_files": 0, "imported_modules": 0, "included_routers": 0, @@ -109,12 +106,12 @@ class DiscoverRouter: return self._router @property - def discovery_stats(self) -> Dict[str, int]: + def discovery_stats(self) -> dict[str, int]: """获取路由发现统计信息""" return self._discovery_stats.copy() @_log_error_handling - def _get_base_dir_and_pkg(self) -> Tuple[Path, str]: + def _get_base_dir_and_pkg(self) -> tuple[Path, str]: """定位基础包的文件系统路径与包名。 返回: @@ -138,7 +135,7 @@ class DiscoverRouter: log.error(f"❌️ 查找控制器文件失败: {str(e)}") return [] - def _resolve_prefix(self, top_module: str) -> Optional[str]: + def _resolve_prefix(self, top_module: str) -> str | None: """将顶级模块目录名解析为容器前缀。""" if top_module in self.exclude_dirs: if self.debug: @@ -192,12 +189,12 @@ class DiscoverRouter: return added @_log_error_handling - def discover_and_register(self) -> Dict[str, int]: + def discover_and_register(self) -> dict[str, int]: """ 执行路由发现与注册 返回: - - Dict[str, int]: 包含发现统计信息的字典 + - dict[str, int]: 包含发现统计信息的字典 - scanned_files: 扫描的文件数量 - imported_modules: 导入的模块数量 - included_routers: 注册的路由数量 @@ -205,8 +202,8 @@ class DiscoverRouter: """ log.info("🚀 开始路由发现与注册...") base_dir, base_pkg = self._get_base_dir_and_pkg() - containers: Dict[str, APIRouter] = {} - container_counts: Dict[str, int] = {} + containers: dict[str, APIRouter] = {} + container_counts: dict[str, int] = {} scanned_files = 0 imported_modules = 0 @@ -330,7 +327,7 @@ class DiscoverRouter: return self @_log_error_handling - def register_router(self, router: APIRouter, tags: Optional[list[str | Enum]] = None) -> None: + def register_router(self, router: APIRouter, tags: list[str | Enum] | None = None) -> None: """手动注册一个路由实例 参数: diff --git a/backend/app/core/exceptions.py b/backend/app/core/exceptions.py index b5dd11b0..247b46b2 100644 --- a/backend/app/core/exceptions.py +++ b/backend/app/core/exceptions.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Any, Optional, Dict, Union +from typing import Any from fastapi import FastAPI, Request, status from fastapi.exceptions import RequestValidationError, ResponseValidationError from pydantic_validation_decorator import FieldValidationError @@ -23,7 +23,7 @@ class CustomException(Exception): msg: str = RET.EXCEPTION.msg, code: int = RET.EXCEPTION.code, status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR, - data: Optional[Any] = None, + data: Any | None = None, success: bool = False ) -> None: """ diff --git a/backend/app/core/middlewares.py b/backend/app/core/middlewares.py index ae16495d..9496f6ba 100644 --- a/backend/app/core/middlewares.py +++ b/backend/app/core/middlewares.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import time -from typing import Any from starlette.middleware.cors import CORSMiddleware from starlette.types import ASGIApp from starlette.requests import Request @@ -13,7 +12,6 @@ from app.common.response import ErrorResponse from app.config.setting import settings from app.core.logger import log from app.core.exceptions import CustomException - from app.api.v1.module_system.params.service import ParamsService diff --git a/backend/app/core/permission.py b/backend/app/core/permission.py index fb60cc57..f8308bf1 100644 --- a/backend/app/core/permission.py +++ b/backend/app/core/permission.py @@ -1,9 +1,8 @@ # -*- coding: utf-8 -*- -from typing import Optional, Set, List, Dict, Any, Union, Tuple +from typing import Any from sqlalchemy.sql.elements import ColumnElement from sqlalchemy import select, and_ -from sqlalchemy.ext.asyncio import AsyncSession from app.api.v1.module_system.user.model import UserModel from app.api.v1.module_system.dept.model import DeptModel from app.api.v1.module_system.role.model import RoleModel @@ -23,7 +22,7 @@ class Permission: DATA_SCOPE_ALL = 4 # 全部数据 DATA_SCOPE_CUSTOM = 5 # 自定义数据 - def __init__(self, db: AsyncSession, model: Any, current_user: UserModel | None = None, auth: AuthSchema | None = None): + def __init__(self, model: Any, auth: AuthSchema): """ 初始化权限过滤器实例 @@ -33,230 +32,9 @@ class Permission: current_user: 当前用户对象 auth: 认证信息对象 """ - self.db = db self.model = model - self.current_user = current_user self.auth = auth - self.conditions: List[ColumnElement] = [] # 权限条件列表 - - async def get_permission_condition(self) -> Optional[ColumnElement]: - """ - 异步构建权限过滤表达式,返回None表示不限制 - - Returns: - 权限过滤表达式或None - """ - # 初始化条件列表 - self.conditions = [] - - # 无用户时返回空条件 - if not self.current_user: - return None - - # 超级管理员跳过所有过滤 - if self.current_user.is_superuser: - return None - - # 租户级数据隔离 - await self._apply_tenant_isolation() - # 客户级数据隔离 - await self._apply_customer_isolation() - # 数据范围权限隔离 - await self._apply_data_scope_isolation() - - # 组合所有条件 - return and_(*self.conditions) if self.conditions else None - - async def _apply_tenant_isolation(self) -> None: - """ - 应用租户级数据隔离 - 非系统用户只能查看本租户数据 - """ - if (hasattr(self.model, "tenant_id") and - hasattr(self.current_user, "user_type") and - getattr(self.current_user, "user_type", None) != "0"): # 非系统用户 - - user_tenant_id = getattr(self.current_user, "tenant_id", None) - if user_tenant_id is not None: - self.conditions.append(getattr(self.model, "tenant_id") == user_tenant_id) - - async def _apply_customer_isolation(self) -> None: - """ - 应用客户级数据隔离 - 客户用户只能查看自己客户的数据 - """ - if hasattr(self.model, "customer_id"): - user_customer_id = getattr(self.current_user, "customer_id", None) - # 客户用户类型为2 - if (hasattr(self.current_user, "user_type") and - getattr(self.current_user, "user_type", None) == "2" and - user_customer_id is not None): - - self.conditions.append(getattr(self.model, "customer_id") == user_customer_id) - - async def _apply_data_scope_isolation(self) -> None: - """ - 应用数据范围权限隔离 - 基于角色的五种数据权限范围过滤 - 支持五种权限类型: - 1. 仅本人数据权限 - 只能查看自己创建的数据 - 2. 本部门数据权限 - 只能查看同部门的数据 - 3. 本部门及以下数据权限 - 可以查看本部门及所有子部门的数据 - 4. 全部数据权限 - 可以查看所有数据 - 5. 自定义数据权限 - 通过role_dept_relation表定义可访问的部门列表 - """ - # 只有在需要检查数据权限且模型有created_id字段时才应用数据范围过滤 - if (self.auth and self.auth.check_data_scope and - hasattr(self.model, "created_id") and - hasattr(self.current_user, "roles")): - - # 获取用户的权限范围和部门ID集合 - data_scopes, dept_ids = await self._get_user_data_scopes() - - # 如果没有设置任何数据权限,默认只能查看自己的数据 - if not data_scopes: - self._add_self_scope_condition() - return - - # 如果拥有全部数据权限,不需要额外过滤 - if self.DATA_SCOPE_ALL in data_scopes: - # 但仍需处理没有部门或角色的情况 - if not getattr(self.current_user, "dept_id", None) or not getattr(self.current_user, "roles", None): - self._add_self_scope_condition() - return - - # 应用相应的数据范围过滤 - if self.DATA_SCOPE_CUSTOM in data_scopes and dept_ids: - await self._add_custom_scope_condition(dept_ids) - elif self.DATA_SCOPE_SELF in data_scopes: - self._add_self_scope_condition() - elif (self.DATA_SCOPE_DEPT in data_scopes or self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes): - await self._add_dept_scope_condition(data_scopes) - else: - # 默认情况下,用户只能查看自己的数据 - self._add_self_scope_condition() - - async def _get_user_data_scopes(self) -> Tuple[Set[int], Set[int]]: - """ - 获取用户所有角色的权限范围和部门ID集合 - - Returns: - Tuple[Set[int], Set[int]]: (数据范围集合, 部门ID集合) - """ - data_scopes: Set[int] = set() - dept_ids: Set[int] = set() - roles = getattr(self.current_user, "roles", []) or [] - - for role in roles: - # 获取角色关联的部门 - if hasattr(role, 'depts') and role.depts: - for dept in role.depts: - if hasattr(dept, 'id'): - dept_ids.add(dept.id) - - # 获取角色的数据范围 - if hasattr(role, 'data_scope') and role.data_scope: - try: - data_scopes.add(int(role.data_scope)) - except (ValueError, TypeError): - # 数据范围格式错误,忽略此角色的数据范围 - continue - - return data_scopes, dept_ids - - def _add_self_scope_condition(self) -> None: - """ - 添加仅本人数据权限条件 - """ - if hasattr(self.model, "created_id"): - user_id = getattr(self.current_user, "id", None) - if user_id is not None: - self.conditions.append(getattr(self.model, "created_id") == user_id) - - async def _add_custom_scope_condition(self, dept_ids: Set[int]) -> None: - """ - 添加自定义数据权限条件 - - Args: - dept_ids: 允许访问的部门ID集合 - """ - creator_rel = getattr(self.model, "created_id", None) - - if (creator_rel is not None and - hasattr(UserModel, 'dept_id')): - # 通过creator关系过滤部门 - self.conditions.append( - creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids))) - ) - else: - # 无法通过部门过滤时回退到仅本人数据 - self._add_self_scope_condition() - - async def _add_dept_scope_condition(self, data_scopes: Set[int]) -> None: - """ - 添加部门相关的数据权限条件 - - Args: - data_scopes: 用户的数据权限范围集合 - """ - dept_id_val = getattr(self.current_user, "dept_id", None) - - # 无部门时回退到仅本人数据 - if dept_id_val is None: - self._add_self_scope_condition() - return - - # 获取部门ID集合 - dept_ids = {dept_id_val} # 包含本部门 - - # 如果需要包含子部门数据 - if self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes: - child_dept_ids = await self._get_child_dept_ids(dept_id_val) - dept_ids.update(child_dept_ids) - - # 应用部门过滤条件 - creator_rel = getattr(self.model, "creator", None) - - if (creator_rel is not None and - hasattr(UserModel, 'dept_id') and - dept_ids): - self.conditions.append( - creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids))) - ) - else: - # 无法通过部门过滤时回退到仅本人数据 - self._add_self_scope_condition() - - async def _get_child_dept_ids(self, dept_id: int) -> List[int]: - """ - 获取指定部门的所有子部门ID(应用租户隔离) - - Args: - dept_id: 部门ID - - Returns: - List[int]: 子部门ID列表 - """ - try: - # 查询所有部门以构建部门树 - dept_sql = select(DeptModel) - - # 应用租户隔离,确保用户只能看到自己租户的部门 - if (hasattr(self.current_user, "user_type") and - getattr(self.current_user, "user_type", None) != "0"): # 非系统用户 - user_tenant_id = getattr(self.current_user, "tenant_id", None) - if user_tenant_id is not None: - dept_sql = dept_sql.where(DeptModel.tenant_id == user_tenant_id) - - dept_result = await self.db.execute(dept_sql) - dept_objs = dept_result.scalars().all() - - # 构建部门ID映射并递归获取子部门ID - id_map = get_child_id_map(dept_objs) - return get_child_recursion(id=dept_id, id_map=id_map) - except Exception: - # 异常情况下返回空列表,避免权限系统出错 - return [] + self.conditions: list[ColumnElement] = [] # 权限条件列表 async def filter_query(self, query: Any) -> Any: """ @@ -268,5 +46,112 @@ class Permission: Returns: 过滤后的查询对象 """ - condition = await self.get_permission_condition() - return query.where(condition) if condition is not None else query \ No newline at end of file + condition = await self.__permission_condition() + return query.where(condition) if condition is not None else query + + async def __permission_condition(self) -> ColumnElement | None: + """ + 应用数据范围权限隔离 + 基于角色的五种数据权限范围过滤 + 支持五种权限类型: + 1. 仅本人数据权限 - 只能查看自己创建的数据 + 2. 本部门数据权限 - 只能查看同部门的数据 + 3. 本部门及以下数据权限 - 可以查看本部门及所有子部门的数据 + 4. 全部数据权限 - 可以查看所有数据 + 5. 自定义数据权限 - 通过role_dept_relation表定义可访问的部门列表 + 构造权限过滤表达式,返回None表示不限制。 + """ + # 如果不需要检查数据权限,则不限制 + if not self.auth.user: + return None + + # 如果检查数据权限为False,则不限制 + if not self.auth.check_data_scope: + return None + + # 如果模型没有创建人creator_id字段,则不限制 + if not hasattr(self.model, "creator_id"): + return None + + # 超级管理员可以查看所有数据 + if self.auth.user.is_superuser: + return None + + # 如果用户没有部门或角色,则只能查看自己的数据 + if not getattr(self.auth.user, "dept_id", None) or not getattr(self.auth.user, "roles", None): + creator_id_attr = getattr(self.model, "creator_id", None) + if creator_id_attr is not None: + return creator_id_attr == self.auth.user.id + return None + + # 获取用户所有角色的权限范围 + data_scopes = set() + dept_ids = set() + roles = getattr(self.auth.user, "roles", []) or [] + + for role in roles: + # 角色的部门集合 + if hasattr(role, 'depts') and role.depts: + for dept in role.depts: + dept_ids.add(dept.id) + data_scopes.add(role.data_scope) + + # 如果有全部数据权限,直接返回 + if self.DATA_SCOPE_ALL in data_scopes: + # 全部数据权限 + return None + + # 如果有自定义数据权限且部门ID存在,优先处理 + if self.DATA_SCOPE_CUSTOM in data_scopes and dept_ids: + # 自定义数据权限 + creator_rel = getattr(self.model, "creator", None) + if hasattr(UserModel, 'dept_id') and creator_rel is not None: + return creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids))) + else: + creator_id_attr = getattr(self.model, "creator_id", None) + if creator_id_attr is not None: + return creator_id_attr == self.auth.user.id + return None + + # 处理其他数据权限范围 + dept_id_val = getattr(self.auth.user, "dept_id", None) + + if self.DATA_SCOPE_SELF in data_scopes: + # 仅本人数据 + creator_id_attr = getattr(self.model, "creator_id", None) + if creator_id_attr is not None: + return creator_id_attr == self.auth.user.id + return None + + if self.DATA_SCOPE_DEPT in data_scopes and dept_id_val is not None: + # 本部门数据 + dept_ids.add(dept_id_val) + + if self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes and dept_id_val is not None: + # 本部门及以下数据(查询所有部门并递归) + dept_sql = select(DeptModel) + dept_result = await self.auth.db.execute(dept_sql) + dept_objs = dept_result.scalars().all() + id_map = get_child_id_map(dept_objs) + dept_child_ids = get_child_recursion(id=dept_id_val, id_map=id_map) + dept_ids.add(dept_id_val) # 包含本部门 + for child_id in dept_child_ids: + dept_ids.add(child_id) + + # 处理2、3汇总的数据权限 + if (self.DATA_SCOPE_DEPT in data_scopes or self.DATA_SCOPE_DEPT_AND_CHILD in data_scopes) and dept_ids: + # 使用关系creator进行筛选(若存在),否则回退到仅本人数据 + creator_rel = getattr(self.model, "creator", None) + if hasattr(UserModel, 'dept_id') and creator_rel is not None and dept_ids: + return creator_rel.has(getattr(UserModel, 'dept_id').in_(list(dept_ids))) + else: + creator_id_attr = getattr(self.model, "creator_id", None) + if creator_id_attr is not None: + return creator_id_attr == self.auth.user.id + return None + + # 默认情况下,只能查看自己的数据 + creator_id_attr = getattr(self.model, "creator_id", None) + if creator_id_attr is not None: + return creator_id_attr == self.auth.user.id + return None \ No newline at end of file diff --git a/backend/app/core/redis_crud.py b/backend/app/core/redis_crud.py index d12341bb..75c80790 100644 --- a/backend/app/core/redis_crud.py +++ b/backend/app/core/redis_crud.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import pickle -from typing import Any, Awaitable, List, Optional +from typing import Any, Awaitable from redis.asyncio.client import Redis from app.core.logger import log @@ -68,13 +68,13 @@ class RedisCURD: log.error(f"获取缓存失败: {str(e)}") return None - async def set(self, key: str, value: Any, expire: Optional[int] = None) -> bool: + async def set(self, key: str, value: Any, expire: int | None = None) -> bool: """设置缓存 参数: - key (str): 缓存键名 - value (Any): 缓存值 - - expire (Optional[int], optional): 过期时间,单位为秒,默认值为None。 + - expire (int | None, optional): 过期时间,单位为秒,默认值为None。 返回: - bool: 如果设置缓存成功则返回True,否则返回False @@ -234,7 +234,7 @@ class RedisCURD: log.error(f"设置哈希缓存失败: {str(e)}") return False - async def hash_get(self, name: str, keys: list[str]) -> Awaitable[List[Any]] | List[Any]: + async def hash_get(self, name: str, keys: list[str]) -> Awaitable[list[Any]] | list[Any]: """获取哈希缓存 参数: @@ -242,7 +242,7 @@ class RedisCURD: - keys (list[str]): 哈希缓存键名列表 返回: - - Awaitable[List[Any]] | List[Any]: 返回哈希缓存值列表,如果获取失败则返回空列表 + - Awaitable[list[Any]] | list[Any]: 返回哈希缓存值列表,如果获取失败则返回空列表 """ try: data = self.redis.hmget(name=name, keys=keys) diff --git a/backend/app/core/router_class.py b/backend/app/core/router_class.py index 0959ad2d..501b0c4f 100644 --- a/backend/app/core/router_class.py +++ b/backend/app/core/router_class.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- import time +import json from typing import Any, Callable, Coroutine from fastapi import Request, Response from fastapi.routing import APIRoute from user_agents import parse -import json from app.core.database import async_db_session from app.config.setting import settings from app.utils.ip_local_util import IpLocalUtil - from app.api.v1.module_system.auth.schema import AuthSchema from app.api.v1.module_system.log.schema import OperationLogCreateSchema from app.api.v1.module_system.log.service import OperationLogService @@ -92,20 +91,15 @@ class OperationLogRoute(APIRoute): payload = '请求参数过长' response_data = response.body if "application/json" in response.headers.get("Content-Type", "") else b"{}" - process_time = f"{(time.time() - start_time):.4f}s" + process_time = f"{(time.time() - start_time):.2f}s" # 获取当前用户ID,如果是登录接口则为空 log_type = 1 # 1:登录日志 2:操作日志 current_user_id = None - tenant_id = 1 - customer_id = None # 优化:只在操作日志场景下获取current_user_id if "user_id" in request.scope: current_user_id = request.scope.get("user_id") - tenant_id = request.scope.get("user_tenant_id") - customer_id = request.scope.get("user_customer_id") - log_type = 2 request_ip = None @@ -147,8 +141,6 @@ class OperationLogRoute(APIRoute): description = route.summary, created_id = current_user_id, updated_id = current_user_id, - tenant_id = tenant_id, - customer_id = customer_id ), auth = auth) return response diff --git a/backend/app/core/security.py b/backend/app/core/security.py index 2b589763..555d0417 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -1,14 +1,12 @@ # -*- coding: utf-8 -*- import jwt -from typing import Dict, Optional from fastapi import Form, Request from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security.utils import get_authorization_scheme_param from app.core.exceptions import CustomException from app.config.setting import settings - from app.api.v1.module_system.auth.schema import JWTPayloadSchema @@ -18,9 +16,9 @@ class CustomOAuth2PasswordBearer(OAuth2PasswordBearer): def __init__( self, token_url: str, - scheme_name: Optional[str] = None, - scopes: Optional[Dict[str, str]] = None, - description: Optional[str] = None, + scheme_name: str | None = None, + scopes: dict[str, str] | None = None, + description: str | None = None, auto_error: bool = True ) -> None: super().__init__( @@ -31,7 +29,7 @@ class CustomOAuth2PasswordBearer(OAuth2PasswordBearer): auto_error=auto_error ) - async def __call__(self, request: Request) -> Optional[str]: + async def __call__(self, request: Request) -> str | None: """ 重写认证方法,校验token @@ -39,7 +37,7 @@ class CustomOAuth2PasswordBearer(OAuth2PasswordBearer): - request (Request): FastAPI请求对象。 返回: - - Optional[str]: 校验通过的token,如果校验失败则返回None。 + - str | None: 校验通过的token,如果校验失败则返回None。 异常: - CustomException: 认证失败时抛出,状态码为401。 @@ -61,26 +59,26 @@ class CustomOAuth2PasswordRequestForm(OAuth2PasswordRequestForm): 参数: - grant_type (str | None): 授权类型,默认值为None,正则表达式为'password'。 - scope (str): 作用域,默认值为空字符串。 - - client_id (Optional[str]): 客户端ID,默认值为None。 - - client_secret (Optional[str]): 客户端密钥,默认值为None。 + - client_id (str | None): 客户端ID,默认值为None。 + - client_secret (str | None): 客户端密钥,默认值为None。 - username (str): 用户名。 - password (str): 密码。 - - captcha_key (Optional[str]): 验证码键,默认值为空字符串。 - - captcha (Optional[str]): 验证码值,默认值为空字符串。 - - login_type (Optional[str]): 登录类型,默认值为"PC端",描述为"PC端 | 移动端"。 + - captcha_key (str | None): 验证码键,默认值为空字符串。 + - captcha (str | None): 验证码值,默认值为空字符串。 + - login_type (str | None): 登录类型,默认值为"PC端",描述为"PC端 | 移动端"。 """ def __init__( self, grant_type: str | None = Form(default=None, regex='password'), scope: str = Form(default=''), - client_id: Optional[str] = Form(default=None), - client_secret: Optional[str] = Form(default=None), + client_id: str | None = Form(default=None), + client_secret: str | None = Form(default=None), username: str = Form(), password: str = Form(), - captcha_key: Optional[str] = Form(default=""), - captcha: Optional[str] = Form(default=""), - login_type: Optional[str] = Form(default="PC端", description="PC端 | 移动端") + captcha_key: str | None = Form(default=""), + captcha: str | None = Form(default=""), + login_type: str | None = Form(default="PC端", description="PC端 | 移动端") ): super().__init__( grant_type=grant_type, diff --git a/backend/app/core/serialize.py b/backend/app/core/serialize.py index 22b14ee2..34a99ca5 100644 --- a/backend/app/core/serialize.py +++ b/backend/app/core/serialize.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from pydantic import BaseModel -from typing import TypeVar, Dict, Any, Type, Generic +from typing import Any, TypeVar, Type, Generic from sqlalchemy.orm import DeclarativeBase ModelType = TypeVar("ModelType", bound=DeclarativeBase) @@ -34,7 +34,7 @@ class Serialize(Generic[ModelType, SchemaType]): raise ValueError(f"序列化失败: {str(e)}") @classmethod - def model_to_dict(cls, model: Type[ModelType], schema: Type[SchemaType]) -> Dict[str, Any]: + def model_to_dict(cls, model: Type[ModelType], schema: Type[SchemaType]) -> dict[str, Any]: """ 将 SQLAlchemy 模型转换为 Pydantic Schema @@ -43,7 +43,7 @@ class Serialize(Generic[ModelType, SchemaType]): - schema (Type[SchemaType]): Pydantic Schema 类。 返回: - - Dict[str, Any]: 包含模型数据的字典。 + - dict[str, Any]: 包含模型数据的字典。 异常: - ValueError: 转换过程中可能抛出的异常。 diff --git a/backend/app/core/validator.py b/backend/app/core/validator.py index a6967f3f..6c033147 100644 --- a/backend/app/core/validator.py +++ b/backend/app/core/validator.py @@ -2,7 +2,7 @@ import re from datetime import datetime -from typing import Annotated, Optional, Union +from typing import Annotated from pydantic import AfterValidator, PlainSerializer, WithJsonSchema from app.common.constant import RET @@ -33,7 +33,7 @@ Email = Annotated[ WithJsonSchema({'type': 'string'}, mode='serialization') ] -def datetime_validator(value: Union[str, datetime]) -> datetime: +def datetime_validator(value: str | datetime) -> datetime: """ 日期格式验证器。 @@ -55,9 +55,6 @@ def datetime_validator(value: Union[str, datetime]) -> datetime: except Exception: raise CustomException(code=RET.ERROR.code, msg="无效的日期格式") - # 如果 value 是 None 或其他类型,抛出异常 - raise CustomException(code=RET.ERROR.code, msg="无效的日期格式") - def email_validator(value: str) -> str: """ @@ -83,7 +80,7 @@ def email_validator(value: str) -> str: return value -def mobile_validator(value: Optional[str]) -> Optional[str]: +def mobile_validator(value: str | None) -> str | None: """ 手机号验证器。 diff --git a/backend/app/scripts/data/sys_dept.json b/backend/app/scripts/data/sys_dept.json index 25c553ac..c9946eed 100644 --- a/backend/app/scripts/data/sys_dept.json +++ b/backend/app/scripts/data/sys_dept.json @@ -8,7 +8,6 @@ "phone": "1582112620", "email": "deptadmin@example.com", "description": "集团总公司", - "tenant_id": 1, "parent_id": null, "children": [] } diff --git a/backend/app/scripts/data/sys_menu.json b/backend/app/scripts/data/sys_menu.json index 688dc00e..64abd17a 100644 --- a/backend/app/scripts/data/sys_menu.json +++ b/backend/app/scripts/data/sys_menu.json @@ -252,314 +252,6 @@ } ] }, - { - "name": "租户管理", - "type": 2, - "icon": "el-icon-Avatar", - "order": 3, - "permission": "module_system:tenant:query", - "route_name": "Tenant", - "route_path": "/system/tenant", - "component_path": "module_system/tenant/index", - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "租户管理", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据", - "children": [ - { - "name": "创建租户", - "type": 3, - "icon": null, - "order": 1, - "permission": "module_system:tenant:create", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "创建租户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "更新租户", - "type": 3, - "icon": null, - "order": 2, - "permission": "module_system:tenant:update", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "更新租户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "删除租户", - "type": 3, - "icon": null, - "order": 3, - "permission": "module_system:tenant:delete", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "删除租户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "批量修改租户状态", - "type": 3, - "icon": null, - "order": 4, - "permission": "module_system:tenant:patch", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "批量修改租户状态", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "导出租户", - "type": 3, - "icon": null, - "order": 5, - "permission": "module_system:tenant:export", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "导出租户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "导入租户", - "type": 3, - "icon": null, - "order": 6, - "permission": "module_system:tenant:import", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "导入租户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "下载导入租户模版", - "type": 3, - "icon": null, - "order": 7, - "permission": "module_system:tenant:download", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "下载导入租户模版", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - } - ] - }, - { - "name": "客户管理", - "type": 2, - "icon": "el-icon-Avatar", - "order": 3, - "permission": "module_system:customer:query", - "route_name": "Customer", - "route_path": "/system/customer", - "component_path": "module_system/customer/index", - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "客户管理", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据", - "children": [ - { - "name": "创建客户", - "type": 3, - "icon": null, - "order": 1, - "permission": "module_system:customer:create", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "创建客户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "更新客户", - "type": 3, - "icon": null, - "order": 2, - "permission": "module_system:customer:update", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "更新客户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "删除客户", - "type": 3, - "icon": null, - "order": 3, - "permission": "module_system:customer:delete", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "删除客户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "批量修改客户状态", - "type": 3, - "icon": null, - "order": 4, - "permission": "module_system:customer:patch", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "批量修改客户状态", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "导出客户", - "type": 3, - "icon": null, - "order": 5, - "permission": "module_system:customer:export", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "导出客户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "导入客户", - "type": 3, - "icon": null, - "order": 6, - "permission": "module_system:customer:import", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "导入客户", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - }, - { - "name": "下载导入客户模版", - "type": 3, - "icon": null, - "order": 7, - "permission": "module_system:customer:download", - "route_name": null, - "route_path": null, - "component_path": null, - "status": "0", - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "下载导入客户模版", - "params": null, - "affix": false, - "redirect": null, - "description": "初始化数据" - } - ] - }, { "name": "岗位管理", "type": 2, diff --git a/backend/app/scripts/data/sys_role.json b/backend/app/scripts/data/sys_role.json index 52feb8b6..50ba4569 100644 --- a/backend/app/scripts/data/sys_role.json +++ b/backend/app/scripts/data/sys_role.json @@ -4,9 +4,18 @@ "code": "ADMIN", "status": "0", "order": 1, - "data_scope": "4", + "data_scope": 4, + "description": "初始化角色", + "created_id": 1, + "updated_id": 1 + }, + { + "name": "普通角色", + "code": "COMMON", + "status": "0", + "order": 1, + "data_scope": 3, "description": "初始化角色", - "tenant_id": 1, "created_id": 1, "updated_id": 1 } diff --git a/backend/app/scripts/data/sys_user.json b/backend/app/scripts/data/sys_user.json index 7b28de1b..fb15931e 100644 --- a/backend/app/scripts/data/sys_user.json +++ b/backend/app/scripts/data/sys_user.json @@ -13,13 +13,9 @@ "github_login": null, "wx_login": null, "qq_login": null, - "user_type": "0", - "salt": "123456", "dept_id": 1, "status": "0", "description": "超级管理员", - "tenant_id": 1, - "customer_id": null, "created_id": null, "updated_id": null } diff --git a/backend/app/scripts/initialize.py b/backend/app/scripts/initialize.py index 9ad7f6cb..6dcf8d51 100644 --- a/backend/app/scripts/initialize.py +++ b/backend/app/scripts/initialize.py @@ -2,7 +2,6 @@ import asyncio import json -from typing import Dict, List from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession @@ -11,7 +10,6 @@ from app.core.logger import log from app.core.database import async_db_session, async_engine from app.core.base_model import MappedBase -from app.api.v1.module_system.tenant.model import TenantModel from app.api.v1.module_system.user.model import UserModel, UserRolesModel from app.api.v1.module_system.role.model import RoleModel from app.api.v1.module_system.dept.model import DeptModel @@ -31,15 +29,14 @@ class InitializeData: """ # 按照依赖关系排序:先创建基础表,再创建关联表 self.prepare_init_models = [ - TenantModel, MenuModel, ParamsModel, + DeptModel, DictTypeModel, DictDataModel, - DeptModel, UserModel, RoleModel, - UserRolesModel + UserRolesModel, ] async def __init_create_table(self) -> None: @@ -120,20 +117,20 @@ class InitializeData: log.error(f"❌️ 初始化 {table_name} 表数据失败: {str(e)}") raise - def __create_objects_with_children(self, data: List[Dict], model_class) -> List: + def __create_objects_with_children(self, data: list[dict], model_class) -> list: """ 通用递归创建对象函数,处理嵌套的 children 数据 参数: - - data (List[Dict]): 包含嵌套 children 数据的列表。 + - data (list[dict]): 包含嵌套 children 数据的列表。 - model_class: 对应的 SQLAlchemy 模型类。 返回: - - List: 包含创建的对象的列表。 + - list: 包含创建的对象的列表。 """ objs = [] - def create_object(obj_data: Dict): + def create_object(obj_data: dict): # 分离 children 数据 children_data = obj_data.pop('children', []) @@ -151,7 +148,7 @@ class InitializeData: return objs - async def __get_data(self, filename: str) -> List[Dict]: + async def __get_data(self, filename: str) -> list[dict]: """ 读取初始化数据文件 @@ -159,7 +156,7 @@ class InitializeData: - filename (str): 文件名(不包含扩展名)。 返回: - - List[Dict]: 解析后的 JSON 数据列表。 + - list[dict]: 解析后的 JSON 数据列表。 """ json_path = SCRIPT_DIR / f'{filename}.json' if not json_path.exists(): diff --git a/backend/app/utils/banner.py b/backend/app/utils/banner.py index e4b05857..453b48eb 100644 --- a/backend/app/utils/banner.py +++ b/backend/app/utils/banner.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + from app.config.path_conf import BANNER_FILE from app.core.logger import log diff --git a/backend/app/utils/common_util.py b/backend/app/utils/common_util.py index 8edd21e9..c618a69b 100644 --- a/backend/app/utils/common_util.py +++ b/backend/app/utils/common_util.py @@ -4,7 +4,7 @@ import importlib import re import uuid from pathlib import Path -from typing import Any, Dict, Literal, Union, Sequence, Optional, Generator +from typing import Any, Literal, Sequence, Generator from sqlalchemy.orm import DeclarativeBase from sqlalchemy.engine.row import Row from sqlalchemy.orm.collections import InstrumentedList @@ -81,7 +81,7 @@ def uuid4_str() -> str: return str(uuid.uuid4()) -def get_parent_id_map(model_list: Sequence[DeclarativeBase]) -> Dict[int, int]: +def get_parent_id_map(model_list: Sequence[DeclarativeBase]) -> dict[int, int]: """ 获取父级 ID 映射字典 @@ -100,11 +100,11 @@ def get_parent_recursion(id: int, id_map: dict[int, int], ids: list[int] | None 参数: - id (int): 当前 ID。 - - id_map (Dict[int, int]): ID 映射字典。 - - ids (List[int] | None): 已收集的 ID 列表。 + - id_map (dict[int, int]): ID 映射字典。 + - ids (list[int] | None): 已收集的 ID 列表。 返回: - - List[int]: 所有父级 ID 列表。 + - list[int]: 所有父级 ID 列表。 """ ids = ids or [] if id in ids: @@ -140,11 +140,11 @@ def get_child_recursion(id: int, id_map: dict[int, list[int]], ids: list[int] | 参数: - id (int): 当前 ID。 - - id_map (Dict[int, List[int]]): ID 映射字典。 - - ids (List[int] | None): 已收集的 ID 列表。 + - id_map (dict[int, list[int]]): ID 映射字典。 + - ids (list[int] | None): 已收集的 ID 列表。 返回: - - List[int]: 所有子级 ID 列表。 + - list[int]: 所有子级 ID 列表。 """ ids = ids or [] ids.append(id) @@ -272,7 +272,7 @@ class SqlalchemyUtil: @classmethod def base_to_dict( - cls, obj: Union[DeclarativeBase, Dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' + cls, obj: DeclarativeBase | dict[str, Any], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case' ): """ 将sqlalchemy模型对象转换为字典 @@ -326,7 +326,7 @@ class SqlalchemyUtil: return result @classmethod - def get_server_default_null(cls, dialect_name: str, need_explicit_null: bool = True) -> Optional[TextClause]: + def get_server_default_null(cls, dialect_name: str, need_explicit_null: bool = True) -> TextClause | None: """ 根据数据库方言动态返回值为null的server_default diff --git a/backend/app/utils/excel_util.py b/backend/app/utils/excel_util.py index c8bfb6e2..ed78bfda 100644 --- a/backend/app/utils/excel_util.py +++ b/backend/app/utils/excel_util.py @@ -2,7 +2,7 @@ import io import pandas as pd -from typing import Any, Dict, List +from typing import Any from openpyxl import Workbook from openpyxl.utils import get_column_letter from openpyxl.styles import Alignment, PatternFill @@ -13,30 +13,30 @@ class ExcelUtil: """Excel文件处理工具类""" @classmethod - def __mapping_list(cls, list_data: List[Dict[str, Any]], mapping_dict: Dict) -> List: + def __mapping_list(cls, list_data: list[dict[str, Any]], mapping_dict: dict) -> list: """ 工具方法:将列表数据中的字段名映射为对应的中文字段名。 参数: - - list_data (List[Dict[str, Any]]): 数据列表。 - - mapping_dict (Dict): 字段名映射字典。 + - list_data (list[dict[str, Any]]): 数据列表。 + - mapping_dict (dict): 字段名映射字典。 返回: - - List: 映射后的数据列表。 + - list: 映射后的数据列表。 """ mapping_data = [{mapping_dict.get(key): item.get(key) for key in mapping_dict} for item in list_data] return mapping_data @classmethod - def get_excel_template(cls, header_list: List[str], selector_header_list: List[str], option_list: List[Dict[str, List[str]]]) -> bytes: + def get_excel_template(cls, header_list: list[str], selector_header_list: list[str], option_list: list[dict[str, list[str]]]) -> bytes: """ 生成 Excel 模板文件。 参数: - - header_list (List[str]): 表头列表。 - - selector_header_list (List[str]): 需要设置下拉选择的表头列表。 - - option_list (List[Dict[str, List[str]]]): 下拉选项配置列表。 + - header_list (list[str]): 表头列表。 + - selector_header_list (list[str]): 需要设置下拉选择的表头列表。 + - option_list (list[dict[str, list[str]]]): 下拉选项配置列表。 返回: - bytes: Excel 文件的二进制数据。 @@ -80,13 +80,13 @@ class ExcelUtil: return excel_data @classmethod - def export_list2excel(cls, list_data: List[Dict[str, Any]], mapping_dict: Dict) -> bytes: + def export_list2excel(cls, list_data: list[dict[str, Any]], mapping_dict: dict) -> bytes: """ 将列表数据导出为 Excel 文件。 参数: - - list_data (List[Dict[str, Any]]): 要导出的数据列表。 - - mapping_dict (Dict): 字段名映射字典。 + - list_data (list[dict[str, Any]]): 要导出的数据列表。 + - mapping_dict (dict): 字段名映射字典。 返回: - bytes: Excel 文件的二进制数据。 diff --git a/backend/app/utils/hash_bcrpy_util.py b/backend/app/utils/hash_bcrpy_util.py index f024dc44..0c0af3f7 100644 --- a/backend/app/utils/hash_bcrpy_util.py +++ b/backend/app/utils/hash_bcrpy_util.py @@ -2,7 +2,7 @@ import hashlib import os -from typing import Optional, Any +from typing import Any from passlib.context import CryptContext from cryptography.hazmat.backends.openssl import backend @@ -54,7 +54,7 @@ class PwdUtil: return PwdContext.hash(password) @classmethod - def check_password_strength(cls, password: str) -> Optional[str]: + def check_password_strength(cls, password: str) -> str | None: """ 检查密码强度 @@ -62,7 +62,7 @@ class PwdUtil: - password (str): 明文密码。 返回: - - Optional[str]: 如果密码强度不够返回提示信息,否则返回None。 + - str | None: 如果密码强度不够返回提示信息,否则返回None。 """ if len(password) < 6: return "密码长度至少6位" diff --git a/backend/app/utils/import_util.py b/backend/app/utils/import_util.py index b49445d4..0fc9bfc6 100644 --- a/backend/app/utils/import_util.py +++ b/backend/app/utils/import_util.py @@ -6,7 +6,7 @@ import os from pathlib import Path from functools import lru_cache from sqlalchemy import inspect as sa_inspect -from typing import Any, List, Set, Type +from typing import Any, Type from app.config.path_conf import BASE_DIR @@ -46,7 +46,7 @@ class ImportUtil: @classmethod @lru_cache(maxsize=256) - def find_models(cls, base_class: Type) -> List[Any]: + def find_models(cls, base_class: Type) -> list[Any]: """ 查找并过滤有效的模型类,避免重复和无效定义 @@ -157,7 +157,7 @@ class ImportUtil: return models @classmethod - def _find_apscheduler_model(cls, base_class: Type, models: List[Any], seen_models: Set[Any], seen_tables: Set[str]): + def _find_apscheduler_model(cls, base_class: Type, models: list[Any], seen_models: set[Any], seen_tables: set[str]): """ 专门查找APScheduler相关的模型 diff --git a/backend/app/utils/string_util.py b/backend/app/utils/string_util.py index e2b8d510..838233c1 100644 --- a/backend/app/utils/string_util.py +++ b/backend/app/utils/string_util.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from typing import Dict, List from app.common.constant import CommonConstant @@ -87,13 +86,13 @@ class StringUtil: return False @classmethod - def contains_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + def contains_any_ignore_case(cls, search_str: str, compare_str_list: list[str]): """ 查找指定字符串是否包含列表中的任意一个字符串(忽略大小写) 参数: - search_str (str): 查找的字符串。 - - compare_str_list (List[str]): 比对的字符串列表。 + - compare_str_list (list[str]): 比对的字符串列表。 返回: - bool: 查找结果。 @@ -119,13 +118,13 @@ class StringUtil: return False @classmethod - def equals_any_ignore_case(cls, search_str: str, compare_str_list: List[str]): + def equals_any_ignore_case(cls, search_str: str, compare_str_list: list[str]): """ 判断指定字符串是否与列表中任意一个字符串相等(忽略大小写) 参数: - search_str (str): 查找的字符串。 - - compare_str_list (List[str]): 比对的字符串列表。 + - compare_str_list (list[str]): 比对的字符串列表。 返回: - bool: 比较结果。 @@ -151,13 +150,13 @@ class StringUtil: return False @classmethod - def startswith_any_case(cls, search_str: str, compare_str_list: List[str]): + def startswith_any_case(cls, search_str: str, compare_str_list: list[str]): """ 查找指定字符串是否以列表中任意一个字符串开头 参数: - search_str (str): 查找的字符串。 - - compare_str_list (List[str]): 比对的字符串列表。 + - compare_str_list (list[str]): 比对的字符串列表。 返回: - bool: 查找结果。 @@ -190,12 +189,12 @@ class StringUtil: return ''.join(result) @classmethod - def get_mapping_value_by_key_ignore_case(cls, mapping: Dict[str, str], key: str) -> str: + def get_mapping_value_by_key_ignore_case(cls, mapping: dict[str, str], key: str) -> str: """ 根据忽略大小写的键获取字典中的对应的值 参数: - - mapping (Dict[str, str]): 字典。 + - mapping (dict[str, str]): 字典。 - key (str): 字典的键。 返回: diff --git a/backend/app/utils/time_util.py b/backend/app/utils/time_util.py index 9d19a741..3b6200c6 100644 --- a/backend/app/utils/time_util.py +++ b/backend/app/utils/time_util.py @@ -2,7 +2,7 @@ import re from datetime import datetime -from typing import Any, List, Dict +from typing import Any class TimeUtil: """ @@ -30,7 +30,7 @@ class TimeUtil: return obj @classmethod - def list_format_datetime(cls, lst: List[Any]) -> List[Any]: + def list_format_datetime(cls, lst: list[Any]) -> list[Any]: """ 格式化列表内每个对象的 datetime 属性。 @@ -38,20 +38,20 @@ class TimeUtil: - lst (List[Any]): 对象列表。 返回: - - List[Any]: 格式化后的对象列表。 + - list[Any]: 格式化后的对象列表。 """ return [cls.object_format_datetime(obj) for obj in lst] @classmethod - def format_datetime_dict_list(cls, dicts: List[Dict]) -> List[Dict]: + def format_datetime_dict_list(cls, dicts: list[dict]) -> list[dict]: """ 递归格式化字典列表中的 datetime 值为默认字符串格式。 参数: - - dicts (List[Dict]): 字典列表。 + - dicts (list[dict]): 字典列表。 返回: - - List[Dict]: 格式化后的字典列表。 + - list[dict]: 格式化后的字典列表。 """ def _format_value(value: Any) -> Any: if isinstance(value, dict): diff --git a/backend/app/utils/upload_util.py b/backend/app/utils/upload_util.py index 13242213..e6dfe195 100644 --- a/backend/app/utils/upload_util.py +++ b/backend/app/utils/upload_util.py @@ -3,7 +3,6 @@ import random import mimetypes from datetime import datetime -from typing import List, Dict, Tuple import aiofiles from fastapi import UploadFile from pathlib import Path @@ -182,7 +181,7 @@ class UploadUtil: return False @classmethod - async def upload_file(cls, file: UploadFile, base_url: str) -> Tuple[str, Path, str]: + async def upload_file(cls, file: UploadFile, base_url: str) -> tuple[str, Path, str]: """ 文件上传。 @@ -191,7 +190,7 @@ class UploadUtil: - base_url (str): 基础 URL。 返回: - - Tuple[str, Path, str]: (文件名, 文件路径, 文件 URL)。 + - tuple[str, Path, str]: (文件名, 文件路径, 文件 URL)。 异常: - CustomException: 当文件类型不支持或大小超限时抛出。 @@ -227,7 +226,7 @@ class UploadUtil: raise CustomException(msg='文件上传失败') @staticmethod - def get_file_tree(file_path: str) -> List[Dict]: + def get_file_tree(file_path: str) -> list[dict]: """ 获取文件树结构。 @@ -235,7 +234,7 @@ class UploadUtil: - file_path (str): 文件路径。 返回: - - List[Dict]: 文件树列表。 + - list[dict]: 文件树列表。 """ return [{"name": item.name, "is_dir": item.is_dir()} for item in Path(file_path).iterdir()] diff --git a/backend/docs/业务逻辑.md b/backend/docs/业务逻辑.md index dbe264e5..67c808c9 100644 --- a/backend/docs/业务逻辑.md +++ b/backend/docs/业务逻辑.md @@ -106,8 +106,6 @@ FastAPI Admin 是一个基于 FastAPI 构建的企业级后台管理系统框架 - name: 昵称 - user_type: 用户类型(0:系统用户 1:租户用户 2:客户用户) - dept_id: 所属部门 - - tenant_id: 所属租户 - - customer_id: 所属客户 #### 4.1.2 RoleModel (角色模型) @@ -160,37 +158,14 @@ FastAPI Admin 是一个基于 FastAPI 构建的企业级后台管理系统框架 ## 5. 数据隔离与权限体系 -### 5.1 数据隔离设计 - -#### 5.1.1 租户隔离 (tenant_id) - -- 系统租户(tenant_id=1): 管理平台级数据 -- 普通租户(tenant_id>1): 各自独立的业务数据 -- 实现SaaS多租户架构的核心隔离机制 - -#### 5.1.2 客户隔离 (customer_id) - -- 租户内部的二级隔离 -- 客户属于租户,实现租户下的业务单元隔离 -- 适用于多业务单元的大型租户 - -#### 5.1.3 用户类型与数据隔离关系 - -| 用户类型 | tenant_id | customer_id | 权限范围 | -|---------|-----------|------------|----------| -| 系统用户(0) | 1 | NULL | 管理所有租户 | -| 租户管理员(1) | >1 | NULL | 管理本租户所有数据 | -| 租户普通用户(1) | >1 | NULL | 受角色data_scope控制 | -| 客户用户(2) | >1 | >1 | 只能访问所属客户数据 | - ### 5.2 数据权限机制 #### 5.2.1 权限范围定义 - 1(仅本人): `WHERE created_id = current_user.id` - 2(本部门): `WHERE user.dept_id = current_user.dept_id` -- 3(本部门及以下): `WHERE dept.tree_path LIKE 'current_user.dept.tree_path%'` -- 4(全部数据): `WHERE tenant_id = current_user.tenant_id` +- 3(本部门及以下): +- 4(全部数据): - 5(自定义): `WHERE dept_id IN (SELECT dept_id FROM role_depts WHERE role_id IN current_user.role_ids)` #### 5.2.2 权限叠加规则 @@ -224,12 +199,6 @@ FastAPI Admin 是一个基于 FastAPI 构建的企业级后台管理系统框架 3. 将过滤条件应用到数据库查询 4. 返回符合权限要求的数据 -### 6.4 多租户数据隔离流程 - -1. 系统自动添加tenant_id过滤条件 -2. 客户用户额外添加customer_id过滤条件 -3. 确保用户只能访问其权限范围内的数据 - ## 7. 特色功能 ### 7.1 智能代码生成 diff --git a/backend/sql/mysql/fastapiadmin_2025-11-29_142312.sql b/backend/sql/mysql/fastapiadmin_2025-11-29_142312.sql deleted file mode 100644 index bd924e25..00000000 --- a/backend/sql/mysql/fastapiadmin_2025-11-29_142312.sql +++ /dev/null @@ -1,932 +0,0 @@ --- MySQL dump 10.13 Distrib 8.4.3, for macos14.5 (arm64) --- --- Host: 127.0.0.1 Database: fastapiadmin --- ------------------------------------------------------ --- Server version 8.4.3 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!50503 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `app_ai_mcp` --- - -DROP TABLE IF EXISTS `app_ai_mcp`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `app_ai_mcp` ( - `name` varchar(50) NOT NULL COMMENT 'MCP 名称', - `type` int NOT NULL COMMENT 'MCP 类型(0:stdio 1:sse)', - `url` varchar(255) DEFAULT NULL COMMENT '远程 SSE 地址', - `command` varchar(255) DEFAULT NULL COMMENT 'MCP 命令', - `args` varchar(255) DEFAULT NULL COMMENT 'MCP 命令参数', - `env` json DEFAULT NULL COMMENT 'MCP 环境变量', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_app_ai_mcp_tenant_id` (`tenant_id`), - KEY `ix_app_ai_mcp_customer_id` (`customer_id`), - KEY `ix_app_ai_mcp_created_id` (`created_id`), - KEY `ix_app_ai_mcp_updated_id` (`updated_id`), - CONSTRAINT `app_ai_mcp_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_ai_mcp_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_ai_mcp_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `app_ai_mcp_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MCP 服务器表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `app_ai_mcp` --- - -/*!40000 ALTER TABLE `app_ai_mcp` DISABLE KEYS */; -/*!40000 ALTER TABLE `app_ai_mcp` ENABLE KEYS */; - --- --- Table structure for table `app_job` --- - -DROP TABLE IF EXISTS `app_job`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `app_job` ( - `name` varchar(64) DEFAULT NULL COMMENT '任务名称', - `jobstore` varchar(64) DEFAULT NULL COMMENT '存储器', - `executor` varchar(64) DEFAULT NULL COMMENT '执行器:将运行此作业的执行程序的名称', - `trigger` varchar(64) NOT NULL COMMENT '触发器:控制此作业计划的 trigger 对象', - `trigger_args` text COMMENT '触发器参数', - `func` text NOT NULL COMMENT '任务函数', - `args` text COMMENT '位置参数', - `kwargs` text COMMENT '关键字参数', - `coalesce` tinyint(1) DEFAULT NULL COMMENT '是否合并运行:是否在多个运行时间到期时仅运行作业一次', - `max_instances` int DEFAULT NULL COMMENT '最大实例数:允许的最大并发执行实例数', - `start_date` varchar(64) DEFAULT NULL COMMENT '开始时间', - `end_date` varchar(64) DEFAULT NULL COMMENT '结束时间', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_app_job_tenant_id` (`tenant_id`), - KEY `ix_app_job_customer_id` (`customer_id`), - KEY `ix_app_job_created_id` (`created_id`), - KEY `ix_app_job_updated_id` (`updated_id`), - CONSTRAINT `app_job_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_job_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_job_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `app_job_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='定时任务调度表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `app_job` --- - -/*!40000 ALTER TABLE `app_job` DISABLE KEYS */; -/*!40000 ALTER TABLE `app_job` ENABLE KEYS */; - --- --- Table structure for table `app_job_log` --- - -DROP TABLE IF EXISTS `app_job_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `app_job_log` ( - `job_name` varchar(64) NOT NULL COMMENT '任务名称', - `job_group` varchar(64) NOT NULL COMMENT '任务组名', - `job_executor` varchar(64) NOT NULL COMMENT '任务执行器', - `invoke_target` varchar(500) NOT NULL COMMENT '调用目标字符串', - `job_args` varchar(255) DEFAULT NULL COMMENT '位置参数', - `job_kwargs` varchar(255) DEFAULT NULL COMMENT '关键字参数', - `job_trigger` varchar(255) DEFAULT NULL COMMENT '任务触发器', - `job_message` varchar(500) DEFAULT NULL COMMENT '日志信息', - `exception_info` varchar(2000) DEFAULT NULL COMMENT '异常信息', - `job_id` int DEFAULT NULL COMMENT '任务ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_app_job_log_tenant_id` (`tenant_id`), - KEY `ix_app_job_log_job_id` (`job_id`), - CONSTRAINT `app_job_log_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `app_job` (`id`) ON DELETE CASCADE, - CONSTRAINT `app_job_log_ibfk_2` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='定时任务调度日志表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `app_job_log` --- - -/*!40000 ALTER TABLE `app_job_log` DISABLE KEYS */; -/*!40000 ALTER TABLE `app_job_log` ENABLE KEYS */; - --- --- Table structure for table `app_myapp` --- - -DROP TABLE IF EXISTS `app_myapp`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `app_myapp` ( - `name` varchar(64) NOT NULL COMMENT '应用名称', - `access_url` varchar(500) NOT NULL COMMENT '访问地址', - `icon_url` varchar(300) DEFAULT NULL COMMENT '应用图标URL', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_app_myapp_customer_id` (`customer_id`), - KEY `ix_app_myapp_tenant_id` (`tenant_id`), - KEY `ix_app_myapp_updated_id` (`updated_id`), - KEY `ix_app_myapp_created_id` (`created_id`), - CONSTRAINT `app_myapp_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_myapp_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `app_myapp_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `app_myapp_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='应用系统表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `app_myapp` --- - -/*!40000 ALTER TABLE `app_myapp` DISABLE KEYS */; -/*!40000 ALTER TABLE `app_myapp` ENABLE KEYS */; - --- --- Table structure for table `apscheduler_jobs` --- - -DROP TABLE IF EXISTS `apscheduler_jobs`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `apscheduler_jobs` ( - `id` varchar(191) NOT NULL, - `next_run_time` double DEFAULT NULL, - `job_state` blob NOT NULL, - PRIMARY KEY (`id`), - KEY `ix_apscheduler_jobs_next_run_time` (`next_run_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `apscheduler_jobs` --- - -/*!40000 ALTER TABLE `apscheduler_jobs` DISABLE KEYS */; -/*!40000 ALTER TABLE `apscheduler_jobs` ENABLE KEYS */; - --- --- Table structure for table `gen_demo` --- - -DROP TABLE IF EXISTS `gen_demo`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `gen_demo` ( - `name` varchar(64) DEFAULT NULL COMMENT '名称', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_gen_demo_customer_id` (`customer_id`), - KEY `ix_gen_demo_created_id` (`created_id`), - KEY `ix_gen_demo_updated_id` (`updated_id`), - KEY `ix_gen_demo_tenant_id` (`tenant_id`), - CONSTRAINT `gen_demo_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `gen_demo_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `gen_demo_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `gen_demo_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='示例表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `gen_demo` --- - -/*!40000 ALTER TABLE `gen_demo` DISABLE KEYS */; -/*!40000 ALTER TABLE `gen_demo` ENABLE KEYS */; - --- --- Table structure for table `gen_table` --- - -DROP TABLE IF EXISTS `gen_table`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `gen_table` ( - `table_name` varchar(200) NOT NULL COMMENT '表名称', - `table_comment` varchar(500) DEFAULT NULL COMMENT '表描述', - `class_name` varchar(100) NOT NULL COMMENT '实体类名称', - `package_name` varchar(100) DEFAULT NULL COMMENT '生成包路径', - `module_name` varchar(30) DEFAULT NULL COMMENT '生成模块名', - `business_name` varchar(30) DEFAULT NULL COMMENT '生成业务名', - `function_name` varchar(100) DEFAULT NULL COMMENT '生成功能名', - `sub_table_name` varchar(64) DEFAULT NULL COMMENT '关联子表的表名', - `sub_table_fk_name` varchar(64) DEFAULT NULL COMMENT '子表关联的外键名', - `parent_menu_id` int DEFAULT NULL COMMENT '父菜单ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='代码生成表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `gen_table` --- - -/*!40000 ALTER TABLE `gen_table` DISABLE KEYS */; -/*!40000 ALTER TABLE `gen_table` ENABLE KEYS */; - --- --- Table structure for table `gen_table_column` --- - -DROP TABLE IF EXISTS `gen_table_column`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `gen_table_column` ( - `column_name` varchar(200) NOT NULL COMMENT '列名称', - `column_comment` varchar(500) DEFAULT NULL COMMENT '列描述', - `column_type` varchar(100) NOT NULL COMMENT '列类型', - `column_length` varchar(50) DEFAULT NULL COMMENT '列长度', - `column_default` varchar(200) DEFAULT NULL COMMENT '列默认值', - `is_pk` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否主键', - `is_increment` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否自增', - `is_nullable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否允许为空', - `is_unique` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否唯一', - `python_type` varchar(100) DEFAULT NULL COMMENT 'Python类型', - `python_field` varchar(200) DEFAULT NULL COMMENT 'Python字段名', - `is_insert` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否为新增字段', - `is_edit` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否编辑字段', - `is_list` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否列表字段', - `is_query` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否查询字段', - `query_type` varchar(50) DEFAULT NULL COMMENT '查询方式', - `html_type` varchar(100) DEFAULT NULL COMMENT '显示类型', - `dict_type` varchar(200) DEFAULT NULL COMMENT '字典类型', - `sort` int NOT NULL COMMENT '排序', - `table_id` int NOT NULL COMMENT '归属表编号', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_gen_table_column_table_id` (`table_id`), - CONSTRAINT `gen_table_column_ibfk_1` FOREIGN KEY (`table_id`) REFERENCES `gen_table` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='代码生成表字段'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `gen_table_column` --- - -/*!40000 ALTER TABLE `gen_table_column` DISABLE KEYS */; -/*!40000 ALTER TABLE `gen_table_column` ENABLE KEYS */; - --- --- Table structure for table `sys_customer` --- - -DROP TABLE IF EXISTS `sys_customer`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_customer` ( - `name` varchar(64) NOT NULL COMMENT '客户名称', - `code` varchar(20) NOT NULL COMMENT '客户编码', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_customer_updated_id` (`updated_id`), - KEY `ix_sys_customer_code` (`code`), - KEY `ix_sys_customer_tenant_id` (`tenant_id`), - KEY `ix_sys_customer_created_id` (`created_id`), - CONSTRAINT `sys_customer_ibfk_1` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_customer_ibfk_2` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_customer_ibfk_3` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='客户表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_customer` --- - -/*!40000 ALTER TABLE `sys_customer` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_customer` ENABLE KEYS */; - --- --- Table structure for table `sys_dept` --- - -DROP TABLE IF EXISTS `sys_dept`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_dept` ( - `name` varchar(40) NOT NULL COMMENT '部门名称', - `order` int NOT NULL COMMENT '显示排序', - `code` varchar(20) DEFAULT NULL COMMENT '部门编码', - `leader` varchar(32) DEFAULT NULL COMMENT '部门负责人', - `phone` varchar(11) DEFAULT NULL COMMENT '手机', - `email` varchar(64) DEFAULT NULL COMMENT '邮箱', - `parent_id` int DEFAULT NULL COMMENT '父级部门ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_dept_parent_id` (`parent_id`), - KEY `ix_sys_dept_tenant_id` (`tenant_id`), - KEY `ix_sys_dept_code` (`code`), - CONSTRAINT `sys_dept_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_dept_ibfk_2` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='部门表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_dept` --- - -/*!40000 ALTER TABLE `sys_dept` DISABLE KEYS */; -INSERT INTO `sys_dept` VALUES ('集团总公司',1,'GROUP','部门负责人','1582112620','deptadmin@example.com',NULL,1,'0de7119f-b7ea-4d1f-9331-ee3893c577f9','0','集团总公司','2025-11-29 14:22:57','2025-11-29 14:22:57',1); -/*!40000 ALTER TABLE `sys_dept` ENABLE KEYS */; - --- --- Table structure for table `sys_dict_data` --- - -DROP TABLE IF EXISTS `sys_dict_data`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_dict_data` ( - `dict_sort` int NOT NULL COMMENT '字典排序', - `dict_label` varchar(255) NOT NULL COMMENT '字典标签', - `dict_value` varchar(255) NOT NULL COMMENT '字典键值', - `css_class` varchar(255) DEFAULT NULL COMMENT '样式属性(其他样式扩展)', - `list_class` varchar(255) DEFAULT NULL COMMENT '表格回显样式', - `is_default` tinyint(1) NOT NULL COMMENT '是否默认(True是 False否)', - `dict_type` varchar(255) NOT NULL COMMENT '字典类型', - `dict_type_id` int NOT NULL COMMENT '字典类型ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `dict_type_id` (`dict_type_id`), - CONSTRAINT `sys_dict_data_ibfk_1` FOREIGN KEY (`dict_type_id`) REFERENCES `sys_dict_type` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='字典数据表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_dict_data` --- - -/*!40000 ALTER TABLE `sys_dict_data` DISABLE KEYS */; -INSERT INTO `sys_dict_data` VALUES (1,'男','0','blue',NULL,1,'sys_user_sex',1,1,'d28813ff-0061-4fe6-8c56-0421255ec92f','0','性别男','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'女','1','pink',NULL,0,'sys_user_sex',1,2,'10d6c517-bba7-4031-bbf8-b291c8913da4','0','性别女','2025-11-29 14:22:57','2025-11-29 14:22:57'),(3,'未知','2','red',NULL,0,'sys_user_sex',1,3,'ccba3cdf-ca86-461a-8a84-3823ec423d35','0','性别未知','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'是','1','','primary',1,'sys_yes_no',2,4,'454dd8b1-0522-4eac-ba36-32a91cfa7a81','0','是','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'否','0','','danger',0,'sys_yes_no',2,5,'c4c0c85c-5649-4f5b-b36c-527fe4c147dd','0','否','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'启用','1','','primary',0,'sys_common_status',3,6,'0ca3f4e8-d0b5-45d2-9753-d49de5dcfbf4','0','启用状态','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'停用','0','','danger',0,'sys_common_status',3,7,'005db216-154f-480c-913a-1b1916f050db','0','停用状态','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'通知','1','blue','warning',1,'sys_notice_type',4,8,'c386f6b9-35da-4072-ba1a-57e6e6265261','0','通知','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'公告','2','orange','success',0,'sys_notice_type',4,9,'60dc1426-95ab-46df-b4cf-06d87c1b95f5','0','公告','2025-11-29 14:22:57','2025-11-29 14:22:57'),(99,'其他','0','','info',0,'sys_oper_type',5,10,'d456d193-440a-41a1-9e58-edf2f39340d5','0','其他操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'新增','1','','info',0,'sys_oper_type',5,11,'06227a10-6eea-4dfb-8e0e-ec1e30f55e02','0','新增操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'修改','2','','info',0,'sys_oper_type',5,12,'f597edad-2868-4042-843c-f1486b720952','0','修改操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(3,'删除','3','','danger',0,'sys_oper_type',5,13,'5fa32e55-d97d-4f02-ac72-755cd8d6f879','0','删除操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(4,'分配权限','4','','primary',0,'sys_oper_type',5,14,'21faa4fc-932e-414f-a360-173ed9903bf2','0','授权操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(5,'导出','5','','warning',0,'sys_oper_type',5,15,'a5f09dc6-8a80-4404-8e79-5bdf1436b5be','0','导出操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(6,'导入','6','','warning',0,'sys_oper_type',5,16,'97b67c8b-4667-4d26-8988-5c4ba896e9c2','0','导入操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(7,'强退','7','','danger',0,'sys_oper_type',5,17,'2f3e6bea-ee85-41de-84a6-69197ded8b41','0','强退操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(8,'生成代码','8','','warning',0,'sys_oper_type',5,18,'cc38c812-8683-49a0-a8d6-a6ead9833597','0','生成操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(9,'清空数据','9','','danger',0,'sys_oper_type',5,19,'59aa0c03-ea53-4ba6-9ec3-adb556628f64','0','清空操作','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'默认(Memory)','default','',NULL,1,'sys_job_store',6,20,'fec28128-2d3d-43cf-b772-a3d239cdd184','0','默认分组','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'数据库(Sqlalchemy)','sqlalchemy','',NULL,0,'sys_job_store',6,21,'4c25fa7e-1a58-422a-af3d-a3abc90ec298','0','数据库分组','2025-11-29 14:22:57','2025-11-29 14:22:57'),(3,'数据库(Redis)','redis','',NULL,0,'sys_job_store',6,22,'d4e0fb02-f59b-4f55-aba7-c9e4695fbb8d','0','reids分组','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'线程池','default','',NULL,0,'sys_job_executor',7,23,'b4ce3923-9407-4d40-a830-3d435374a068','0','线程池','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'进程池','processpool','',NULL,0,'sys_job_executor',7,24,'a00f1f1d-1a81-4a88-a13e-8e3f1a6bd8d8','0','进程池','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'演示函数','scheduler_test.job','',NULL,1,'sys_job_function',8,25,'2b0ff9ec-4e9d-4470-ad84-8211ef47b3c2','0','演示函数','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'指定日期(date)','date','',NULL,1,'sys_job_trigger',9,26,'dfc48192-b4de-42d4-9942-1f4751a646c2','0','指定日期任务触发器','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'间隔触发器(interval)','interval','',NULL,0,'sys_job_trigger',9,27,'338b5258-e4df-4923-8e34-8d216ff153a3','0','间隔触发器任务触发器','2025-11-29 14:22:57','2025-11-29 14:22:57'),(3,'cron表达式','cron','',NULL,0,'sys_job_trigger',9,28,'4679b9ec-76fd-480f-aa8f-23c1791d9eb2','0','间隔触发器任务触发器','2025-11-29 14:22:57','2025-11-29 14:22:57'),(1,'默认(default)','default','',NULL,1,'sys_list_class',10,29,'886a2cab-7a93-45db-9eea-13e525530222','0','默认表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'),(2,'主要(primary)','primary','',NULL,0,'sys_list_class',10,30,'17fa756e-b561-49c1-afc5-d6852382d4b7','0','主要表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'),(3,'成功(success)','success','',NULL,0,'sys_list_class',10,31,'e6d8a103-236a-4fe8-b8b9-3c5444e71159','0','成功表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'),(4,'信息(info)','info','',NULL,0,'sys_list_class',10,32,'13997491-854d-4e6e-b8a1-edd74f25a404','0','信息表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'),(5,'警告(warning)','warning','',NULL,0,'sys_list_class',10,33,'63b15da5-cade-4b77-bfef-642db271125f','0','警告表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'),(6,'危险(danger)','danger','',NULL,0,'sys_list_class',10,34,'f551d922-dedb-4f0d-8a03-a4a07b59ff4a','0','危险表格回显样式','2025-11-29 14:22:57','2025-11-29 14:22:57'); -/*!40000 ALTER TABLE `sys_dict_data` ENABLE KEYS */; - --- --- Table structure for table `sys_dict_type` --- - -DROP TABLE IF EXISTS `sys_dict_type`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_dict_type` ( - `dict_name` varchar(255) NOT NULL COMMENT '字典名称', - `dict_type` varchar(255) NOT NULL COMMENT '字典类型', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `dict_type` (`dict_type`), - UNIQUE KEY `uuid` (`uuid`) -) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='字典类型表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_dict_type` --- - -/*!40000 ALTER TABLE `sys_dict_type` DISABLE KEYS */; -INSERT INTO `sys_dict_type` VALUES ('用户性别','sys_user_sex',1,'e0d15aa3-d1de-4cff-ae20-6e36748f66d3','0','用户性别列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('系统是否','sys_yes_no',2,'d8a98556-25ef-498a-a9aa-231ea4274611','0','系统是否列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('系统状态','sys_common_status',3,'d7831abb-a7db-46d6-b3df-98f5a6d1d7fc','0','系统状态','2025-11-29 14:22:57','2025-11-29 14:22:57'),('通知类型','sys_notice_type',4,'035e918d-102d-4d7b-acc9-906e5be1cd80','0','通知类型列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('操作类型','sys_oper_type',5,'9c4a63fc-0e50-43a6-9510-d629e47947fa','0','操作类型列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('任务存储器','sys_job_store',6,'422e9184-3958-4910-972f-d0bb10c96858','0','任务分组列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('任务执行器','sys_job_executor',7,'961dd724-bb43-4350-ba28-c380922278c7','0','任务执行器列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('任务函数','sys_job_function',8,'9a3f12c1-ba47-4bf6-9155-36a1d1c3836f','0','任务函数列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('任务触发器','sys_job_trigger',9,'89df7943-da4f-45e7-a99c-a8305732ce0b','0','任务触发器列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('表格回显样式','sys_list_class',10,'f5dfe92a-967c-4438-ae55-79ece57ace1e','0','表格回显样式列表','2025-11-29 14:22:57','2025-11-29 14:22:57'); -/*!40000 ALTER TABLE `sys_dict_type` ENABLE KEYS */; - --- --- Table structure for table `sys_log` --- - -DROP TABLE IF EXISTS `sys_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_log` ( - `type` int NOT NULL COMMENT '日志类型(1登录日志 2操作日志)', - `request_path` varchar(255) NOT NULL COMMENT '请求路径', - `request_method` varchar(10) NOT NULL COMMENT '请求方式', - `request_payload` text COMMENT '请求体', - `request_ip` varchar(50) DEFAULT NULL COMMENT '请求IP地址', - `login_location` varchar(255) DEFAULT NULL COMMENT '登录位置', - `request_os` varchar(64) DEFAULT NULL COMMENT '操作系统', - `request_browser` varchar(64) DEFAULT NULL COMMENT '浏览器', - `response_code` int NOT NULL COMMENT '响应状态码', - `response_json` text COMMENT '响应体', - `process_time` varchar(20) DEFAULT NULL COMMENT '处理时间', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_log_customer_id` (`customer_id`), - KEY `ix_sys_log_tenant_id` (`tenant_id`), - KEY `ix_sys_log_updated_id` (`updated_id`), - KEY `ix_sys_log_created_id` (`created_id`), - CONSTRAINT `sys_log_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_log_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_log_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_log_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统日志表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_log` --- - -/*!40000 ALTER TABLE `sys_log` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_log` ENABLE KEYS */; - --- --- Table structure for table `sys_menu` --- - -DROP TABLE IF EXISTS `sys_menu`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_menu` ( - `name` varchar(50) NOT NULL COMMENT '菜单名称', - `type` int NOT NULL COMMENT '菜单类型(1:目录 2:菜单 3:按钮/权限 4:链接)', - `order` int NOT NULL COMMENT '显示排序', - `permission` varchar(100) DEFAULT NULL COMMENT '权限标识(如:module_system:user:list)', - `icon` varchar(50) DEFAULT NULL COMMENT '菜单图标', - `route_name` varchar(100) DEFAULT NULL COMMENT '路由名称', - `route_path` varchar(200) DEFAULT NULL COMMENT '路由路径', - `component_path` varchar(200) DEFAULT NULL COMMENT '组件路径', - `redirect` varchar(200) DEFAULT NULL COMMENT '重定向地址', - `hidden` tinyint(1) NOT NULL COMMENT '是否隐藏(True:隐藏 False:显示)', - `keep_alive` tinyint(1) NOT NULL COMMENT '是否缓存(True:是 False:否)', - `always_show` tinyint(1) NOT NULL COMMENT '是否始终显示(True:是 False:否)', - `title` varchar(50) DEFAULT NULL COMMENT '菜单标题', - `params` json DEFAULT NULL COMMENT '路由参数(JSON对象)', - `affix` tinyint(1) NOT NULL COMMENT '是否固定标签页(True:是 False:否)', - `parent_id` int DEFAULT NULL COMMENT '父菜单ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_menu_parent_id` (`parent_id`), - CONSTRAINT `sys_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_menu` (`id`) ON DELETE SET NULL -) ENGINE=InnoDB AUTO_INCREMENT=129 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_menu` --- - -/*!40000 ALTER TABLE `sys_menu` DISABLE KEYS */; -INSERT INTO `sys_menu` VALUES ('仪表盘',1,1,'','client','Dashboard','/dashboard',NULL,'/dashboard/workplace',0,1,1,'仪表盘','null',0,NULL,1,'52bee14d-c12c-47a4-a7aa-29467f0bb31f','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('系统管理',1,2,NULL,'system','System','/system',NULL,'/system/menu',0,1,0,'系统管理','null',0,NULL,2,'fe06901f-c19c-436b-90ee-870bbc8b707d','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('应用管理',1,3,NULL,'el-icon-ShoppingBag','Application','/application',NULL,'/application/myapp',0,0,0,'应用管理','null',0,NULL,3,'b8da2227-ecdc-4608-99ce-cc4efc414199','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('监控管理',1,4,NULL,'monitor','Monitor','/monitor',NULL,'/monitor/online',0,0,0,'监控管理','null',0,NULL,4,'7a9fd33f-099a-4016-b133-d19d0c825925','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('代码管理',1,5,NULL,'code','Gencode','/gencode',NULL,'/gencode/backcode',0,0,0,'代码管理','null',0,NULL,5,'ea1bab65-4bbc-4030-b347-aacaf020d8bf','0','代码管理','2025-11-29 14:22:57','2025-11-29 14:22:57'),('接口管理',1,6,NULL,'document','Common','/common',NULL,'/common/docs',0,0,0,'接口管理','null',0,NULL,6,'94e28cb7-446a-4808-ac6b-8d9e056dc46a','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('案例管理',1,7,NULL,'el-icon-ElementPlus','Example','/example',NULL,'/example/demo',0,0,0,'案例管理','null',0,NULL,7,'e2315e80-4ca5-4420-a48b-9ee3f0e1af9c','0','案例管理','2025-11-29 14:22:57','2025-11-29 14:22:57'),('工作台',2,1,'dashboard:workplace:query','el-icon-PieChart','Workplace','/dashboard/workplace','dashboard/workplace',NULL,0,1,0,'工作台','null',0,1,8,'c4337e0d-3fd0-4cb5-956e-8f3ffb0d9bfe','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('菜单管理',2,1,'module_system:menu:query','menu','Menu','/system/menu','module_system/menu/index',NULL,0,1,0,'菜单管理','null',0,2,9,'ee87f484-46eb-492f-abc2-b2b75ab19ef0','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('部门管理',2,2,'module_system:dept:query','tree','Dept','/system/dept','module_system/dept/index',NULL,0,1,0,'部门管理','null',0,2,10,'9799b763-d03d-465c-b332-6f0ad67e92c1','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('租户管理',2,3,'module_system:tenant:query','el-icon-Avatar','Tenant','/system/tenant','module_system/tenant/index',NULL,0,1,0,'租户管理','null',0,2,11,'69b7b1a5-7b21-4622-9f6f-56c05f12a239','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('客户管理',2,3,'module_system:customer:query','el-icon-Avatar','Customer','/system/customer','module_system/customer/index',NULL,0,1,0,'客户管理','null',0,2,12,'529cc54c-c576-418e-b301-26cb6ce22669','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('岗位管理',2,3,'module_system:position:query','el-icon-Coordinate','Position','/system/position','module_system/position/index',NULL,0,1,0,'岗位管理','null',0,2,13,'8eeea8e3-9101-46ea-a8a5-9fa7fa442487','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('角色管理',2,4,'module_system:role:query','role','Role','/system/role','module_system/role/index',NULL,0,1,0,'角色管理','null',0,2,14,'62bf22cb-b587-4710-a3d4-0a80fc1e1d9b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('用户管理',2,5,'module_system:user:query','el-icon-User','User','/system/user','module_system/user/index',NULL,0,1,0,'用户管理','null',0,2,15,'364eed06-e5d9-4a8b-9471-39928f308e2e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('日志管理',2,6,'module_system:log:query','el-icon-Aim','Log','/system/log','module_system/log/index',NULL,0,1,0,'日志管理','null',0,2,16,'484b3f65-e217-49a0-ad81-37f9c77f27dd','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告管理',2,7,'module_system:notice:query','bell','Notice','/system/notice','module_system/notice/index',NULL,0,1,0,'公告管理','null',0,2,17,'89037624-91e8-46dc-a173-ae02e4e6c5e8','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('参数管理',2,8,'module_system:param:query','setting','Params','/system/param','module_system/param/index',NULL,0,1,0,'参数管理','null',0,2,18,'e81f0f1f-f6b8-4242-af36-e20d42c42eb2','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('字典管理',2,9,'module_system:dict_type:query','dict','Dict','/system/dict','module_system/dict/index',NULL,0,1,0,'字典管理','null',0,2,19,'78a41e5b-4dd4-4a5e-80f4-6c46df072862','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('我的应用',2,1,'module_application:myapp:query','el-icon-ShoppingCartFull','MYAPP','/application/myapp','module_application/myapp/index',NULL,0,1,0,'我的应用','null',0,3,20,'d8d2d746-6755-4afc-8b32-4b4c4dbb8361','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('任务管理',2,2,'module_application:job:query','el-icon-DataLine','Job','/application/job','module_application/job/index',NULL,0,1,0,'任务管理','null',0,3,21,'7cd0f94c-fb95-476d-be73-89fa5cdb7be1','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('AI智能助手',2,3,'module_application:ai:chat','el-icon-ToiletPaper','AI','/application/ai','module_application/ai/index',NULL,0,1,0,'AI智能助手','null',0,3,22,'01637027-fd20-4ff3-aa2b-52431b552261','0','AI智能助手','2025-11-29 14:22:57','2025-11-29 14:22:57'),('流程管理',2,4,'module_application:workflow:query','el-icon-ShoppingBag','Workflow','/application/workflow','module_application/workflow/index',NULL,0,1,0,'我的流程','null',0,3,23,'85a7bf8b-f167-4068-a144-99356157ca98','0','我的流程','2025-11-29 14:22:57','2025-11-29 14:22:57'),('在线用户',2,1,'module_monitor:online:query','el-icon-Headset','MonitorOnline','/monitor/online','module_monitor/online/index',NULL,0,0,0,'在线用户','null',0,4,24,'a8da73ba-0e9a-481e-86de-3ffde76a40d7','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('服务器监控',2,2,'module_monitor:server:query','el-icon-Odometer','MonitorServer','/monitor/server','module_monitor/server/index',NULL,0,0,0,'服务器监控','null',0,4,25,'2c291d97-66b5-49f3-84e7-740ddea51f7e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('缓存监控',2,3,'module_monitor:cache:query','el-icon-Stopwatch','MonitorCache','/monitor/cache','module_monitor/cache/index',NULL,0,0,0,'缓存监控','null',0,4,26,'a244f214-0450-49ea-b769-5e464adba7a8','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件管理',2,4,'module_monitor:resource:query','el-icon-Files','Resource','/monitor/resource','module_monitor/resource/index',NULL,0,1,0,'文件管理','null',0,4,27,'f3b48295-82dc-47b4-92fc-d0bf9c5bd8b4','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('代码生成',2,1,'module_generator:gencode:query','code','Backcode','/gencode/backcode','module_generator/backcode/index',NULL,0,1,0,'代码生成','null',0,5,28,'3190f6f8-2c7d-4444-bd39-d34b10c68d74','0','代码生成','2025-11-29 14:22:57','2025-11-29 14:22:57'),('Swagger文档',4,1,'module_common:docs:query','api','Docs','/common/docs','module_common/docs/index',NULL,0,0,0,'Swagger文档','null',0,6,29,'68e9ece5-719f-439a-9059-b33c82d7fb9c','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('Redoc文档',4,2,'module_common:redoc:query','el-icon-Document','Redoc','/common/redoc','module_common/redoc/index',NULL,0,0,0,'Redoc文档','null',0,6,30,'64ceb6b2-5cab-4030-8720-40cb89cf752e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('示例管理',2,1,'module_example:demo:query','el-icon-ElementPlus','Demo','/example/demo','module_example/demo/index',NULL,0,1,0,'示例管理','null',0,7,31,'aa7fffe6-89a1-4baf-a1f3-9ddbf7a9eb30','0','示例管理','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建菜单',3,1,'module_system:menu:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建菜单','null',0,9,32,'b9135a88-5323-4d04-810f-4ababbd4f0c5','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改菜单',3,2,'module_system:menu:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改菜单','null',0,9,33,'360d3539-f213-4ffd-b24d-8c0289b77191','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除菜单',3,3,'module_system:menu:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除菜单','null',0,9,34,'228fda70-8c29-4eec-ab1d-7d51912fc649','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改菜单状态',3,4,'module_system:menu:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改菜单状态','null',0,9,35,'e15f8e83-056f-46ef-a70a-a9157f70d79a','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建部门',3,1,'module_system:dept:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建部门','null',0,10,36,'81416bc8-8c2f-41d7-9b8f-e0793b891b63','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改部门',3,2,'module_system:dept:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改部门','null',0,10,37,'26ca0b6e-cdbb-47cb-8018-7db6a3201e38','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除部门',3,3,'module_system:dept:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除部门','null',0,10,38,'0526bfca-528c-42d5-8a72-762feb5e3a6b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改部门状态',3,4,'module_system:dept:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改部门状态','null',0,10,39,'784d9c33-94c0-4842-b1c4-0f2658234b76','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建租户',3,1,'module_system:tenant:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建租户','null',0,11,40,'d7ad507d-9864-4f50-80be-107f324258bb','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('更新租户',3,2,'module_system:tenant:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'更新租户','null',0,11,41,'f9244339-a2dc-4106-81a0-47116ab3034e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除租户',3,3,'module_system:tenant:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除租户','null',0,11,42,'2eaeea28-286b-4c04-ae39-f01cd3d8b1be','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改租户状态',3,4,'module_system:tenant:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改租户状态','null',0,11,43,'bd36ddd0-2da3-48d1-b445-777618138c56','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出租户',3,5,'module_system:tenant:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出租户','null',0,11,44,'b30e7a94-4b09-4ee6-84a1-10254e77a36c','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导入租户',3,6,'module_system:tenant:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入租户','null',0,11,45,'d061f69a-d3db-419f-ad70-0d55bfe207ae','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('下载导入租户模版',3,7,'module_system:tenant:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'下载导入租户模版','null',0,11,46,'73a3310c-b9aa-4131-8b43-a6459bc7f10b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建客户',3,1,'module_system:customer:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建客户','null',0,12,47,'8849d9c7-bbb8-44a6-9369-dde7a37d5a84','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('更新客户',3,2,'module_system:customer:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'更新客户','null',0,12,48,'fcb3db0c-9dfc-49a5-a99f-b059f0d8749b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除客户',3,3,'module_system:customer:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除客户','null',0,12,49,'18d2ff4e-d233-4dad-ab4f-837640df7369','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改客户状态',3,4,'module_system:customer:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改客户状态','null',0,12,50,'5e3e1bfa-bb97-4ba2-acf1-25876333f78f','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出客户',3,5,'module_system:customer:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出客户','null',0,12,51,'05c6b71e-9990-47b1-aa88-a09c8c7d2e00','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导入客户',3,6,'module_system:customer:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入客户','null',0,12,52,'e78ef9b9-5b3d-4f87-99aa-73e8ece3c9f1','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('下载导入客户模版',3,7,'module_system:customer:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'下载导入客户模版','null',0,12,53,'42052351-e102-42b4-b78f-9a89c45f4118','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建岗位',3,1,'module_system:position:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建岗位','null',0,13,54,'07df3acb-b133-4e23-a84a-0a10fcede52c','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改岗位',3,2,'module_system:position:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改岗位','null',0,13,55,'ee9e6df3-19f7-4c07-991b-967ce2d80356','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除岗位',3,3,'module_system:position:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改岗位','null',0,13,56,'ac2a08f8-4e87-4faf-a49b-f60e69613030','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改岗位状态',3,4,'module_system:position:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改岗位状态','null',0,13,57,'af49f2e2-54d3-4db2-8568-8440aacfdd82','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('岗位导出',3,5,'module_system:position:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'岗位导出','null',0,13,58,'5fa4dc60-e09a-45d6-a0f1-92a0252f029f','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('设置角色权限',3,8,'module_system:role:permission',NULL,NULL,NULL,NULL,NULL,0,1,0,'设置角色权限','null',0,13,59,'a6ca3ab4-8f39-499c-bd14-10aaa17c3d5b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建角色',3,1,'module_system:role:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建角色','null',0,14,60,'e841abee-ade7-4977-84c5-b69139563b6e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改角色',3,2,'module_system:role:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改角色','null',0,14,61,'3f2a2b7d-62db-4ff5-816a-458492f4fdbb','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除角色',3,3,'module_system:role:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除角色','null',0,14,62,'1659b9cb-a6e2-4cba-b826-b0bb565fd25d','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改角色状态',3,4,'module_system:role:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改角色状态','null',0,14,63,'dcd57c8f-2bb1-410b-bc3e-70a50a72d4df','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('角色导出',3,6,'module_system:role:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'角色导出','null',0,14,64,'9bb94df1-0553-4e2f-a206-4df571fdfc8e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建用户',3,1,'module_system:user:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建用户','null',0,15,65,'cce2e771-e41a-4b78-8df1-04991bd9a9d1','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改用户',3,2,'module_system:user:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改用户','null',0,15,66,'3c46e0c0-66fb-4b11-9213-59ba1491a5d6','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除用户',3,3,'module_system:user:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除用户','null',0,15,67,'6c51ca1a-5508-4773-befd-e85d80812e67','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改用户状态',3,4,'module_system:user:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改用户状态','null',0,15,68,'dafdebfb-324b-4077-9097-f8f0311e474a','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出用户',3,5,'module_system:user:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出用户','null',0,15,69,'159699b4-a1bc-4494-8a8f-8015b9b42173','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导入用户',3,6,'module_system:user:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入用户','null',0,15,70,'c54c1fe8-a40b-4802-b751-157253e005be','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('日志删除',3,1,'module_system:log:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'日志删除','null',0,16,71,'94d54b94-8e5f-4058-8930-57e5b827a3ed','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('日志导出',3,2,'module_system:log:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'日志导出','null',0,16,72,'bdbc6679-1634-43e0-8d44-978c55729a55','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告创建',3,1,'module_system:notice:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告创建','null',0,17,73,'05d1968d-e3ab-4155-a6e0-9e5fad5cac8d','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告修改',3,2,'module_system:notice:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改用户','null',0,17,74,'7b064fa8-f46e-4e11-946d-f015d9312631','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告删除',3,3,'module_system:notice:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告删除','null',0,17,75,'be3761cf-4a1b-452a-8a50-dfc4291855a7','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告导出',3,4,'module_system:notice:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告导出','null',0,17,76,'03192342-0cfd-48e6-9d3c-60b8c16c8b39','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('公告批量修改状态',3,5,'module_system:notice:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告批量修改状态','null',0,17,77,'e03f3aab-0448-41e0-8091-1703d05f365b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建参数',3,1,'module_system:param:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建参数','null',0,18,78,'0863a7a4-32d0-40f9-b40c-067d86deb4a7','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改参数',3,2,'module_system:param:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改参数','null',0,18,79,'940e638f-443d-4701-abbd-b701c9a52152','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除参数',3,3,'module_system:param:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除参数','null',0,18,80,'70fabed6-d669-4bf2-98de-09551b4f956a','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出参数',3,4,'module_system:param:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出参数','null',0,18,81,'201dff9d-5749-40b0-968c-f7826c1a3eca','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('参数上传',3,5,'module_system:param:upload',NULL,NULL,NULL,NULL,NULL,0,1,0,'参数上传','null',0,18,82,'819cf97e-a305-49d2-a5db-98158c3ea94c','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建字典类型',3,1,'module_system:dict_type:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建字典类型','null',0,19,83,'01c5ba1f-a084-4a99-bc39-597d3caaa650','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改字典类型',3,2,'module_system:dict_type:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改字典类型','null',0,19,84,'233b2624-f8b7-4653-9ede-3d2fa4799baf','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除字典类型',3,3,'module_system:dict_type:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除字典类型','null',0,19,85,'7b76dda5-ebc6-45b0-803b-79b7ca61a814','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出字典类型',3,4,'module_system:dict_type:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典类型','null',0,19,86,'fd506189-52f9-4684-bb29-63e3ed6525af','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改字典状态',3,5,'module_system:dict_type:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典类型','null',0,19,87,'4709e179-1203-440e-896b-c12afe57a9aa','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('字典数据查询',3,6,'module_system:dict_data:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'字典数据查询','null',0,19,88,'3a7179bb-5e41-4140-a86e-34d1dfdd899a','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建字典数据',3,7,'module_system:dict_data:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建字典数据','null',0,19,89,'190652d0-ede2-4ea3-8294-c1a860c878d5','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改字典数据',3,8,'module_system:dict_data:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改字典数据','null',0,19,90,'4f499ab4-87ab-4e24-b922-3ba911541898','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除字典数据',3,9,'module_system:dict_data:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除字典数据','null',0,19,91,'a206eb8a-8eec-48ef-99c4-ef4a797aff56','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出字典数据',3,10,'module_system:dict_data:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典数据','null',0,19,92,'4041bff8-3aa1-4bfb-9c04-c35c77ba9ead','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改字典数据状态',3,11,'module_system:dict_data:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改字典数据状态','null',0,19,93,'dd12ca7e-a517-4cce-b9c5-526140aadab6','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建应用',3,1,'module_application:myapp:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建应用','null',0,20,94,'e9e99d58-6659-4f73-b1c8-604956450037','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改应用',3,2,'module_application:myapp:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改应用','null',0,20,95,'150f613b-b531-487e-a444-87682d65c317','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除应用',3,3,'module_application:myapp:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除应用','null',0,20,96,'b68c96c3-b6fd-43e6-a243-78a26aedf0d9','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改应用状态',3,4,'module_application:myapp:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改应用状态','null',0,20,97,'f5572189-03f4-4952-9f38-ec3fbcb79e51','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建任务',3,1,'module_application:job:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建任务','null',0,21,98,'822a1062-3dfb-40f1-b6b4-8f9cb0369dfe','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('修改和操作任务',3,2,'module_application:job:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改和操作任务','null',0,21,99,'fd9c35a8-8d59-4769-948d-4894cbbdd48d','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除和清除任务',3,3,'module_application:job:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除和清除任务','null',0,21,100,'463b381e-81e5-4126-a218-f57611199938','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出定时任务',3,4,'module_application:job:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出定时任务','null',0,21,101,'e034c007-c1c4-48c5-aa33-b69206e4b24f','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('智能对话',3,1,'module_application:ai:chat',NULL,NULL,NULL,NULL,NULL,0,1,0,'智能对话','null',0,22,102,'4d6b25b4-7860-4fa2-b5f6-1934a414693c','0','智能对话','2025-11-29 14:22:57','2025-11-29 14:22:57'),('在线用户强制下线',3,1,'module_monitor:online:delete',NULL,NULL,NULL,NULL,NULL,0,0,0,'在线用户强制下线','null',0,24,103,'c545e044-0741-4bf3-8018-f86e9b223bcf','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('清除缓存',3,1,'module_monitor:cache:delete',NULL,NULL,NULL,NULL,NULL,0,0,0,'清除缓存','null',0,26,104,'b20270b0-e89d-45c9-b453-3f6e98bda0cd','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件上传',3,1,'module_monitor:resource:upload',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件上传','null',0,27,105,'dc4c81cc-0966-421e-a994-9c975b8944bc','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件下载',3,2,'module_monitor:resource:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件下载','null',0,27,106,'e22c08f0-6218-4ea9-bd0c-fcd09f541fc5','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件删除',3,3,'module_monitor:resource:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件删除','null',0,27,107,'165d233f-a66f-41f9-b6ba-02f7883500b8','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件移动',3,4,'module_monitor:resource:move',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件移动','null',0,27,108,'ca69c004-8033-4bb2-8e92-56e67af0c009','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件复制',3,5,'module_monitor:resource:copy',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件复制','null',0,27,109,'0c520223-ae3b-4fba-960d-f1f087256574','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('文件重命名',3,6,'module_monitor:resource:rename',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件重命名','null',0,27,110,'ef1f43a6-5a69-48fc-bbca-526332b8d6f5','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建目录',3,7,'module_monitor:resource:create_dir',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建目录','null',0,27,111,'f89e8160-8b94-49c2-988e-2f0ada2ade3d','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出文件列表',3,9,'module_monitor:resource:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出文件列表','null',0,27,112,'2525c83a-0ef3-409c-aa6b-262c904e8ea1','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('查询代码生成业务表列表',3,1,'module_generator:gencode:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'查询代码生成业务表列表','null',0,28,113,'760e7dee-3ca4-4f02-a8ad-8402a086eaf5','0','查询代码生成业务表列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建表结构',3,2,'module_generator:gencode:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建表结构','null',0,28,114,'289f1509-c255-4d0f-957b-30d3320f3077','0','创建表结构','2025-11-29 14:22:57','2025-11-29 14:22:57'),('编辑业务表信息',3,3,'module_generator:gencode:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'编辑业务表信息','null',0,28,115,'a0af6cc9-ef7b-4a24-9078-99deb00034df','0','编辑业务表信息','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除业务表信息',3,4,'module_generator:gencode:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除业务表信息','null',0,28,116,'c711f269-f592-435a-a74b-1247ff2db404','0','删除业务表信息','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导入表结构',3,5,'module_generator:gencode:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入表结构','null',0,28,117,'33945bc5-a2ee-45b6-a36c-49b85eee7487','0','导入表结构','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量生成代码',3,6,'module_generator:gencode:operate',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量生成代码','null',0,28,118,'4d1839d6-51b1-44fb-ba66-7c1b887e6899','0','批量生成代码','2025-11-29 14:22:57','2025-11-29 14:22:57'),('生成代码到指定路径',3,7,'module_generator:gencode:code',NULL,NULL,NULL,NULL,NULL,0,1,0,'生成代码到指定路径','null',0,28,119,'0dda0065-871a-4d5d-b73f-be4598b889c1','0','生成代码到指定路径','2025-11-29 14:22:57','2025-11-29 14:22:57'),('查询数据库表列表',3,8,'module_generator:dblist:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'查询数据库表列表','null',0,28,120,'bc3a6032-60bf-4de1-b166-4bf4c3e8229a','0','查询数据库表列表','2025-11-29 14:22:57','2025-11-29 14:22:57'),('同步数据库',3,9,'module_generator:db:sync',NULL,NULL,NULL,NULL,NULL,0,1,0,'同步数据库','null',0,28,121,'0621e9ed-f781-4f89-83e1-30ecba5e790e','0','同步数据库','2025-11-29 14:22:57','2025-11-29 14:22:57'),('创建示例',3,1,'module_example:demo:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建示例','null',0,31,122,'5b8405f7-09b3-45ea-a78e-69e489601aff','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('更新示例',3,2,'module_example:demo:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'更新示例','null',0,31,123,'29d19c9d-e09d-4957-8882-65c040b2cdee','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('删除示例',3,3,'module_example:demo:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除示例','null',0,31,124,'9db77833-08f4-41d9-8422-cdf95bd78aa9','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('批量修改示例状态',3,4,'module_example:demo:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改示例状态','null',0,31,125,'ca696354-1c79-4e44-8ea0-4012c5a2a018','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导出示例',3,5,'module_example:demo:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出示例','null',0,31,126,'56d927fa-ff0b-43b4-b3a2-c875228c41a6','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('导入示例',3,6,'module_example:demo:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入示例','null',0,31,127,'34be3df8-49a9-4621-81a2-c9e3e2aeb59c','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('下载导入示例模版',3,7,'module_example:demo:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'下载导入示例模版','null',0,31,128,'264df0fe-1bb0-4ffe-aa9a-7ba0d4591807','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'); -/*!40000 ALTER TABLE `sys_menu` ENABLE KEYS */; - --- --- Table structure for table `sys_notice` --- - -DROP TABLE IF EXISTS `sys_notice`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_notice` ( - `notice_title` varchar(50) NOT NULL COMMENT '公告标题', - `notice_type` varchar(50) NOT NULL COMMENT '公告类型(1通知 2公告)', - `notice_content` text COMMENT '公告内容', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_notice_created_id` (`created_id`), - KEY `ix_sys_notice_customer_id` (`customer_id`), - KEY `ix_sys_notice_tenant_id` (`tenant_id`), - KEY `ix_sys_notice_updated_id` (`updated_id`), - CONSTRAINT `sys_notice_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_notice_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_notice_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_notice_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='通知公告表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_notice` --- - -/*!40000 ALTER TABLE `sys_notice` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_notice` ENABLE KEYS */; - --- --- Table structure for table `sys_param` --- - -DROP TABLE IF EXISTS `sys_param`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_param` ( - `config_name` varchar(500) NOT NULL COMMENT '参数名称', - `config_key` varchar(500) NOT NULL COMMENT '参数键名', - `config_value` varchar(500) DEFAULT NULL COMMENT '参数键值', - `config_type` tinyint(1) DEFAULT NULL COMMENT '系统内置(True:是 False:否)', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`) -) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统参数表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_param` --- - -/*!40000 ALTER TABLE `sys_param` DISABLE KEYS */; -INSERT INTO `sys_param` VALUES ('网站名称','sys_web_title','FastApiAdmin',1,1,'3f11d81d-5a09-47d8-8ae5-f5d00b83852b','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('网站描述','sys_web_description','FastApiAdmin 是完全开源的权限管理系统',1,2,'17d4c3ad-8cb2-4512-a893-41f856085985','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('网页图标','sys_web_favicon','https://service.fastapiadmin.com/api/v1/static/image/favicon.png',1,3,'922929ef-0c75-4862-a002-97f8fdb210f6','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('网站Logo','sys_web_logo','https://service.fastapiadmin.com/api/v1/static/image/logo.png',1,4,'25b39dde-8f58-49af-a0b4-0d9e90f7edb7','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('登录背景','sys_login_background','https://service.fastapiadmin.com/api/v1/static/image/background.svg',1,5,'bb68438c-9731-45c8-8409-a0f9e2daac9e','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('版权信息','sys_web_copyright','Copyright © 2025-2026 service.fastapiadmin.com 版权所有',1,6,'158ad7bb-cb2c-4800-97b3-37055ecade85','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('备案信息','sys_keep_record','陕ICP备2025069493号-1',1,7,'b6432848-efca-4ff8-b9d3-f7cdb6a133dd','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('帮助文档','sys_help_doc','https://service.fastapiadmin.com',1,8,'0b7137c3-75ec-44dd-b333-33a11732d5c2','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('隐私政策','sys_web_privacy','https://github.com/1014TaoTao/FastapiAdmin/blob/master/LICENSE',1,9,'ea110687-e0e2-4bae-8f0a-25fb1c76b588','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('用户协议','sys_web_clause','https://github.com/1014TaoTao/FastapiAdmin/blob/master/LICENSE',1,10,'1a751f3d-b877-4539-9138-04a72b92e327','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('源码代码','sys_git_code','https://github.com/1014TaoTao/FastapiAdmin.git',1,11,'50007973-bb66-477d-94f3-9cf612368ce0','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('项目版本','sys_web_version','2.0.0',1,12,'f197e4d9-8f88-4d22-8eaa-e464323be170','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('演示模式启用','demo_enable','false',1,13,'75d7b7ef-1c3b-4f31-9409-408829be61d0','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('演示访问IP白名单','ip_white_list','[\"127.0.0.1\"]',1,14,'cbfb6b36-1e87-4e7a-98f3-d31f3643d1a5','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('接口白名单','white_api_list_path','[\"/api/v1/system/auth/login\", \"/api/v1/system/auth/token/refresh\", \"/api/v1/system/auth/captcha/get\", \"/api/v1/system/auth/logout\", \"/api/v1/system/config/info\", \"/api/v1/system/user/current/info\", \"/api/v1/system/notice/available\"]',1,15,'2ea596a1-0775-431c-a492-1ded89e56907','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'),('访问IP黑名单','ip_black_list','[]',1,16,'56146e39-11af-4c3f-9000-d74b50ebba14','0','初始化数据','2025-11-29 14:22:57','2025-11-29 14:22:57'); -/*!40000 ALTER TABLE `sys_param` ENABLE KEYS */; - --- --- Table structure for table `sys_position` --- - -DROP TABLE IF EXISTS `sys_position`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_position` ( - `name` varchar(40) NOT NULL COMMENT '岗位名称', - `order` int NOT NULL COMMENT '显示排序', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_position_created_id` (`created_id`), - KEY `ix_sys_position_updated_id` (`updated_id`), - KEY `ix_sys_position_tenant_id` (`tenant_id`), - CONSTRAINT `sys_position_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_position_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_position_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='岗位表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_position` --- - -/*!40000 ALTER TABLE `sys_position` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_position` ENABLE KEYS */; - --- --- Table structure for table `sys_role` --- - -DROP TABLE IF EXISTS `sys_role`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_role` ( - `name` varchar(40) NOT NULL COMMENT '角色名称', - `code` varchar(20) DEFAULT NULL COMMENT '角色编码', - `order` int NOT NULL COMMENT '显示排序', - `data_scope` varchar(10) DEFAULT NULL COMMENT '数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - PRIMARY KEY (`id`), - UNIQUE KEY `uuid` (`uuid`), - KEY `ix_sys_role_tenant_id` (`tenant_id`), - KEY `ix_sys_role_created_id` (`created_id`), - KEY `ix_sys_role_code` (`code`), - KEY `ix_sys_role_updated_id` (`updated_id`), - CONSTRAINT `sys_role_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_role_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_role_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_role` --- - -/*!40000 ALTER TABLE `sys_role` DISABLE KEYS */; -INSERT INTO `sys_role` VALUES ('管理员角色','ADMIN',1,'4',1,'1d0fd634-3faa-40e2-aedf-5ad6d1fca647','0','初始化角色','2025-11-29 14:22:57','2025-11-29 14:22:57',1,1,1); -/*!40000 ALTER TABLE `sys_role` ENABLE KEYS */; - --- --- Table structure for table `sys_role_depts` --- - -DROP TABLE IF EXISTS `sys_role_depts`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_role_depts` ( - `role_id` int NOT NULL COMMENT '角色ID', - `dept_id` int NOT NULL COMMENT '部门ID', - PRIMARY KEY (`role_id`,`dept_id`), - KEY `dept_id` (`dept_id`), - CONSTRAINT `sys_role_depts_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_role_depts_ibfk_2` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色部门关联表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_role_depts` --- - -/*!40000 ALTER TABLE `sys_role_depts` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_role_depts` ENABLE KEYS */; - --- --- Table structure for table `sys_role_menus` --- - -DROP TABLE IF EXISTS `sys_role_menus`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_role_menus` ( - `role_id` int NOT NULL COMMENT '角色ID', - `menu_id` int NOT NULL COMMENT '菜单ID', - PRIMARY KEY (`role_id`,`menu_id`), - KEY `menu_id` (`menu_id`), - CONSTRAINT `sys_role_menus_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_role_menus_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色菜单关联表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_role_menus` --- - -/*!40000 ALTER TABLE `sys_role_menus` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_role_menus` ENABLE KEYS */; - --- --- Table structure for table `sys_tenant` --- - -DROP TABLE IF EXISTS `sys_tenant`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_tenant` ( - `name` varchar(100) NOT NULL COMMENT '租户名称', - `code` varchar(100) NOT NULL COMMENT '租户编码', - `start_time` datetime DEFAULT NULL COMMENT '开始时间', - `end_time` datetime DEFAULT NULL COMMENT '结束时间', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `name` (`name`), - UNIQUE KEY `code` (`code`), - UNIQUE KEY `uuid` (`uuid`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='租户表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_tenant` --- - -/*!40000 ALTER TABLE `sys_tenant` DISABLE KEYS */; -INSERT INTO `sys_tenant` VALUES ('运行管理平台','SYSTEM',NULL,NULL,1,'ea56ab5e-11cb-452d-b6d5-3d46b6f311ff','0','系统内置租户,用于管理平台全局配置和所有租户','2025-11-29 14:22:57','2025-11-29 14:22:57'); -/*!40000 ALTER TABLE `sys_tenant` ENABLE KEYS */; - --- --- Table structure for table `sys_user` --- - -DROP TABLE IF EXISTS `sys_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_user` ( - `username` varchar(32) NOT NULL COMMENT '用户名/登录账号', - `password` varchar(255) NOT NULL COMMENT '密码哈希', - `name` varchar(32) NOT NULL COMMENT '昵称', - `mobile` varchar(11) DEFAULT NULL COMMENT '手机号', - `email` varchar(64) DEFAULT NULL COMMENT '邮箱', - `gender` varchar(1) DEFAULT NULL COMMENT '性别(0:男 1:女 2:未知)', - `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL地址', - `is_superuser` tinyint(1) NOT NULL COMMENT '是否超管', - `last_login` datetime DEFAULT NULL COMMENT '最后登录时间', - `gitee_login` varchar(32) DEFAULT NULL COMMENT 'Gitee登录', - `github_login` varchar(32) DEFAULT NULL COMMENT 'Github登录', - `wx_login` varchar(32) DEFAULT NULL COMMENT '微信登录', - `qq_login` varchar(32) DEFAULT NULL COMMENT 'QQ登录', - `user_type` varchar(32) NOT NULL COMMENT '用户类型(0:系统用户 1:租户用户 2:客户用户)', - `salt` varchar(255) DEFAULT NULL COMMENT '加密盐', - `dept_id` int DEFAULT NULL COMMENT '部门ID', - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', - `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', - `description` text COMMENT '备注/描述', - `created_time` datetime NOT NULL COMMENT '创建时间', - `updated_time` datetime NOT NULL COMMENT '更新时间', - `created_id` int DEFAULT NULL COMMENT '创建人ID', - `updated_id` int DEFAULT NULL COMMENT '更新人ID', - `tenant_id` int NOT NULL COMMENT '所属租户ID', - `customer_id` int DEFAULT NULL COMMENT '所属客户ID(NULL表示租户级数据,>0表示客户级数据)', - PRIMARY KEY (`id`), - UNIQUE KEY `username` (`username`), - UNIQUE KEY `uuid` (`uuid`), - UNIQUE KEY `mobile` (`mobile`), - UNIQUE KEY `email` (`email`), - KEY `ix_sys_user_customer_id` (`customer_id`), - KEY `ix_sys_user_tenant_id` (`tenant_id`), - KEY `ix_sys_user_updated_id` (`updated_id`), - KEY `ix_sys_user_dept_id` (`dept_id`), - KEY `ix_sys_user_created_id` (`created_id`), - CONSTRAINT `sys_user_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_user_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, - CONSTRAINT `sys_user_ibfk_3` FOREIGN KEY (`tenant_id`) REFERENCES `sys_tenant` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_user_ibfk_4` FOREIGN KEY (`customer_id`) REFERENCES `sys_customer` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_user_ibfk_5` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_user` --- - -/*!40000 ALTER TABLE `sys_user` DISABLE KEYS */; -INSERT INTO `sys_user` VALUES ('admin','$2b$12$e2IJgS/cvHgJ0H3G7Xa08OXoXnk6N/NX3IZRtubBDElA0VLZhkNOa','超级管理员',NULL,NULL,'0','https://service.fastapiadmin.com/api/v1/static/image/avatar.png',1,NULL,NULL,NULL,NULL,NULL,'0','123456',1,1,'3f67a36b-40eb-4c7c-8f85-3d7f0a2d4736','0','超级管理员','2025-11-29 14:22:57','2025-11-29 14:22:57',NULL,NULL,1,NULL); -/*!40000 ALTER TABLE `sys_user` ENABLE KEYS */; - --- --- Table structure for table `sys_user_positions` --- - -DROP TABLE IF EXISTS `sys_user_positions`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_user_positions` ( - `user_id` int NOT NULL COMMENT '用户ID', - `position_id` int NOT NULL COMMENT '岗位ID', - PRIMARY KEY (`user_id`,`position_id`), - KEY `position_id` (`position_id`), - CONSTRAINT `sys_user_positions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_user_positions_ibfk_2` FOREIGN KEY (`position_id`) REFERENCES `sys_position` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户岗位关联表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_user_positions` --- - -/*!40000 ALTER TABLE `sys_user_positions` DISABLE KEYS */; -/*!40000 ALTER TABLE `sys_user_positions` ENABLE KEYS */; - --- --- Table structure for table `sys_user_roles` --- - -DROP TABLE IF EXISTS `sys_user_roles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_user_roles` ( - `user_id` int NOT NULL COMMENT '用户ID', - `role_id` int NOT NULL COMMENT '角色ID', - PRIMARY KEY (`user_id`,`role_id`), - KEY `role_id` (`role_id`), - CONSTRAINT `sys_user_roles_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT `sys_user_roles_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `sys_user_roles` --- - -/*!40000 ALTER TABLE `sys_user_roles` DISABLE KEYS */; -INSERT INTO `sys_user_roles` VALUES (1,1); -/*!40000 ALTER TABLE `sys_user_roles` ENABLE KEYS */; - --- --- Dumping routines for database 'fastapiadmin' --- -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2025-11-29 14:23:15 diff --git a/backend/sql/mysql/fastapiadmin_2025-11-30_233258.sql b/backend/sql/mysql/fastapiadmin_2025-11-30_233258.sql new file mode 100644 index 00000000..dadf59d9 --- /dev/null +++ b/backend/sql/mysql/fastapiadmin_2025-11-30_233258.sql @@ -0,0 +1,817 @@ +-- MySQL dump 10.13 Distrib 8.4.3, for macos14.5 (arm64) +-- +-- Host: 127.0.0.1 Database: fastapiadmin +-- ------------------------------------------------------ +-- Server version 8.4.3 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `app_ai_mcp` +-- + +DROP TABLE IF EXISTS `app_ai_mcp`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `app_ai_mcp` ( + `name` varchar(50) NOT NULL COMMENT 'MCP 名称', + `type` int NOT NULL COMMENT 'MCP 类型(0:stdio 1:sse)', + `url` varchar(255) DEFAULT NULL COMMENT '远程 SSE 地址', + `command` varchar(255) DEFAULT NULL COMMENT 'MCP 命令', + `args` varchar(255) DEFAULT NULL COMMENT 'MCP 命令参数', + `env` json DEFAULT NULL COMMENT 'MCP 环境变量', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_app_ai_mcp_updated_id` (`updated_id`), + KEY `ix_app_ai_mcp_created_id` (`created_id`), + CONSTRAINT `app_ai_mcp_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `app_ai_mcp_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='MCP 服务器表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `app_ai_mcp` +-- + +/*!40000 ALTER TABLE `app_ai_mcp` DISABLE KEYS */; +/*!40000 ALTER TABLE `app_ai_mcp` ENABLE KEYS */; + +-- +-- Table structure for table `app_job` +-- + +DROP TABLE IF EXISTS `app_job`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `app_job` ( + `name` varchar(64) DEFAULT NULL COMMENT '任务名称', + `jobstore` varchar(64) DEFAULT NULL COMMENT '存储器', + `executor` varchar(64) DEFAULT NULL COMMENT '执行器:将运行此作业的执行程序的名称', + `trigger` varchar(64) NOT NULL COMMENT '触发器:控制此作业计划的 trigger 对象', + `trigger_args` text COMMENT '触发器参数', + `func` text NOT NULL COMMENT '任务函数', + `args` text COMMENT '位置参数', + `kwargs` text COMMENT '关键字参数', + `coalesce` tinyint(1) DEFAULT NULL COMMENT '是否合并运行:是否在多个运行时间到期时仅运行作业一次', + `max_instances` int DEFAULT NULL COMMENT '最大实例数:允许的最大并发执行实例数', + `start_date` varchar(64) DEFAULT NULL COMMENT '开始时间', + `end_date` varchar(64) DEFAULT NULL COMMENT '结束时间', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_app_job_created_id` (`created_id`), + KEY `ix_app_job_updated_id` (`updated_id`), + CONSTRAINT `app_job_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `app_job_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='定时任务调度表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `app_job` +-- + +/*!40000 ALTER TABLE `app_job` DISABLE KEYS */; +/*!40000 ALTER TABLE `app_job` ENABLE KEYS */; + +-- +-- Table structure for table `app_job_log` +-- + +DROP TABLE IF EXISTS `app_job_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `app_job_log` ( + `job_name` varchar(64) NOT NULL COMMENT '任务名称', + `job_group` varchar(64) NOT NULL COMMENT '任务组名', + `job_executor` varchar(64) NOT NULL COMMENT '任务执行器', + `invoke_target` varchar(500) NOT NULL COMMENT '调用目标字符串', + `job_args` varchar(255) DEFAULT NULL COMMENT '位置参数', + `job_kwargs` varchar(255) DEFAULT NULL COMMENT '关键字参数', + `job_trigger` varchar(255) DEFAULT NULL COMMENT '任务触发器', + `job_message` varchar(500) DEFAULT NULL COMMENT '日志信息', + `exception_info` varchar(2000) DEFAULT NULL COMMENT '异常信息', + `job_id` int DEFAULT NULL COMMENT '任务ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_app_job_log_job_id` (`job_id`), + CONSTRAINT `app_job_log_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `app_job` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='定时任务调度日志表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `app_job_log` +-- + +/*!40000 ALTER TABLE `app_job_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `app_job_log` ENABLE KEYS */; + +-- +-- Table structure for table `app_myapp` +-- + +DROP TABLE IF EXISTS `app_myapp`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `app_myapp` ( + `name` varchar(64) NOT NULL COMMENT '应用名称', + `access_url` varchar(500) NOT NULL COMMENT '访问地址', + `icon_url` varchar(300) DEFAULT NULL COMMENT '应用图标URL', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_app_myapp_created_id` (`created_id`), + KEY `ix_app_myapp_updated_id` (`updated_id`), + CONSTRAINT `app_myapp_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `app_myapp_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='应用系统表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `app_myapp` +-- + +/*!40000 ALTER TABLE `app_myapp` DISABLE KEYS */; +/*!40000 ALTER TABLE `app_myapp` ENABLE KEYS */; + +-- +-- Table structure for table `apscheduler_jobs` +-- + +DROP TABLE IF EXISTS `apscheduler_jobs`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `apscheduler_jobs` ( + `id` varchar(191) NOT NULL, + `next_run_time` double DEFAULT NULL, + `job_state` blob NOT NULL, + PRIMARY KEY (`id`), + KEY `ix_apscheduler_jobs_next_run_time` (`next_run_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `apscheduler_jobs` +-- + +/*!40000 ALTER TABLE `apscheduler_jobs` DISABLE KEYS */; +/*!40000 ALTER TABLE `apscheduler_jobs` ENABLE KEYS */; + +-- +-- Table structure for table `gen_demo` +-- + +DROP TABLE IF EXISTS `gen_demo`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `gen_demo` ( + `name` varchar(64) DEFAULT NULL COMMENT '名称', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_gen_demo_created_id` (`created_id`), + KEY `ix_gen_demo_updated_id` (`updated_id`), + CONSTRAINT `gen_demo_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `gen_demo_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='示例表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `gen_demo` +-- + +/*!40000 ALTER TABLE `gen_demo` DISABLE KEYS */; +/*!40000 ALTER TABLE `gen_demo` ENABLE KEYS */; + +-- +-- Table structure for table `gen_table` +-- + +DROP TABLE IF EXISTS `gen_table`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `gen_table` ( + `table_name` varchar(200) NOT NULL COMMENT '表名称', + `table_comment` varchar(500) DEFAULT NULL COMMENT '表描述', + `class_name` varchar(100) NOT NULL COMMENT '实体类名称', + `package_name` varchar(100) DEFAULT NULL COMMENT '生成包路径', + `module_name` varchar(30) DEFAULT NULL COMMENT '生成模块名', + `business_name` varchar(30) DEFAULT NULL COMMENT '生成业务名', + `function_name` varchar(100) DEFAULT NULL COMMENT '生成功能名', + `sub_table_name` varchar(64) DEFAULT NULL COMMENT '关联子表的表名', + `sub_table_fk_name` varchar(64) DEFAULT NULL COMMENT '子表关联的外键名', + `parent_menu_id` int DEFAULT NULL COMMENT '父菜单ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_gen_table_created_id` (`created_id`), + KEY `ix_gen_table_updated_id` (`updated_id`), + CONSTRAINT `gen_table_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `gen_table_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='代码生成表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `gen_table` +-- + +/*!40000 ALTER TABLE `gen_table` DISABLE KEYS */; +/*!40000 ALTER TABLE `gen_table` ENABLE KEYS */; + +-- +-- Table structure for table `gen_table_column` +-- + +DROP TABLE IF EXISTS `gen_table_column`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `gen_table_column` ( + `column_name` varchar(200) NOT NULL COMMENT '列名称', + `column_comment` varchar(500) DEFAULT NULL COMMENT '列描述', + `column_type` varchar(100) NOT NULL COMMENT '列类型', + `column_length` varchar(50) DEFAULT NULL COMMENT '列长度', + `column_default` varchar(200) DEFAULT NULL COMMENT '列默认值', + `is_pk` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否主键', + `is_increment` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否自增', + `is_nullable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否允许为空', + `is_unique` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否唯一', + `python_type` varchar(100) DEFAULT NULL COMMENT 'Python类型', + `python_field` varchar(200) DEFAULT NULL COMMENT 'Python字段名', + `is_insert` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否为新增字段', + `is_edit` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否编辑字段', + `is_list` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否列表字段', + `is_query` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否查询字段', + `query_type` varchar(50) DEFAULT NULL COMMENT '查询方式', + `html_type` varchar(100) DEFAULT NULL COMMENT '显示类型', + `dict_type` varchar(200) DEFAULT NULL COMMENT '字典类型', + `sort` int NOT NULL COMMENT '排序', + `table_id` int NOT NULL COMMENT '归属表编号', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_gen_table_column_table_id` (`table_id`), + KEY `ix_gen_table_column_created_id` (`created_id`), + KEY `ix_gen_table_column_updated_id` (`updated_id`), + CONSTRAINT `gen_table_column_ibfk_1` FOREIGN KEY (`table_id`) REFERENCES `gen_table` (`id`) ON DELETE CASCADE, + CONSTRAINT `gen_table_column_ibfk_2` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `gen_table_column_ibfk_3` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='代码生成表字段'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `gen_table_column` +-- + +/*!40000 ALTER TABLE `gen_table_column` DISABLE KEYS */; +/*!40000 ALTER TABLE `gen_table_column` ENABLE KEYS */; + +-- +-- Table structure for table `sys_dept` +-- + +DROP TABLE IF EXISTS `sys_dept`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_dept` ( + `name` varchar(40) NOT NULL COMMENT '部门名称', + `order` int NOT NULL COMMENT '显示排序', + `code` varchar(20) DEFAULT NULL COMMENT '部门编码', + `leader` varchar(32) DEFAULT NULL COMMENT '部门负责人', + `phone` varchar(11) DEFAULT NULL COMMENT '手机', + `email` varchar(64) DEFAULT NULL COMMENT '邮箱', + `parent_id` int DEFAULT NULL COMMENT '父级部门ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_dept_code` (`code`), + KEY `ix_sys_dept_parent_id` (`parent_id`), + CONSTRAINT `sys_dept_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='部门表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_dept` +-- + +/*!40000 ALTER TABLE `sys_dept` DISABLE KEYS */; +INSERT INTO `sys_dept` VALUES ('集团总公司',1,'GROUP','部门负责人','1582112620','deptadmin@example.com',NULL,1,'767eb8e0-a420-4e1b-9b48-28db7faef1ff','0','集团总公司','2025-11-30 23:30:07','2025-11-30 23:30:07'); +/*!40000 ALTER TABLE `sys_dept` ENABLE KEYS */; + +-- +-- Table structure for table `sys_dict_data` +-- + +DROP TABLE IF EXISTS `sys_dict_data`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_dict_data` ( + `dict_sort` int NOT NULL COMMENT '字典排序', + `dict_label` varchar(255) NOT NULL COMMENT '字典标签', + `dict_value` varchar(255) NOT NULL COMMENT '字典键值', + `css_class` varchar(255) DEFAULT NULL COMMENT '样式属性(其他样式扩展)', + `list_class` varchar(255) DEFAULT NULL COMMENT '表格回显样式', + `is_default` tinyint(1) NOT NULL COMMENT '是否默认(True是 False否)', + `dict_type` varchar(255) NOT NULL COMMENT '字典类型', + `dict_type_id` int NOT NULL COMMENT '字典类型ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `dict_type_id` (`dict_type_id`), + CONSTRAINT `sys_dict_data_ibfk_1` FOREIGN KEY (`dict_type_id`) REFERENCES `sys_dict_type` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='字典数据表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_dict_data` +-- + +/*!40000 ALTER TABLE `sys_dict_data` DISABLE KEYS */; +INSERT INTO `sys_dict_data` VALUES (1,'男','0','blue',NULL,1,'sys_user_sex',1,1,'0e42c256-288d-445b-941f-72a59f21418a','0','性别男','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'女','1','pink',NULL,0,'sys_user_sex',1,2,'f21cce54-fb3a-4a69-8b33-7ca18ea33b69','0','性别女','2025-11-30 23:30:07','2025-11-30 23:30:07'),(3,'未知','2','red',NULL,0,'sys_user_sex',1,3,'2cb63e5e-2177-4a68-8dee-39d261d42859','0','性别未知','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'是','1','','primary',1,'sys_yes_no',2,4,'e53beae7-c68b-46ac-9268-5d7c71961ebc','0','是','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'否','0','','danger',0,'sys_yes_no',2,5,'336cd66a-c3cb-42dd-a6d1-76dd6b796d3c','0','否','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'启用','1','','primary',0,'sys_common_status',3,6,'f4688198-3466-4c7b-945f-b2dba05d48c0','0','启用状态','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'停用','0','','danger',0,'sys_common_status',3,7,'7d951d26-2929-466d-9bb8-64aeed9307e1','0','停用状态','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'通知','1','blue','warning',1,'sys_notice_type',4,8,'609f7a14-836c-4807-b53f-db71e0c4779b','0','通知','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'公告','2','orange','success',0,'sys_notice_type',4,9,'0f34e8e6-abf5-4845-9516-3e920d770330','0','公告','2025-11-30 23:30:07','2025-11-30 23:30:07'),(99,'其他','0','','info',0,'sys_oper_type',5,10,'53696192-af81-4b5a-b788-44c842af9243','0','其他操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'新增','1','','info',0,'sys_oper_type',5,11,'8e646904-e4e1-4fa4-8f13-67b2df72764d','0','新增操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'修改','2','','info',0,'sys_oper_type',5,12,'0f1b13ac-8cad-47fa-81d5-f9ff9fb4f5f2','0','修改操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(3,'删除','3','','danger',0,'sys_oper_type',5,13,'363a0109-ed5e-4290-ba31-2732108396d3','0','删除操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(4,'分配权限','4','','primary',0,'sys_oper_type',5,14,'b6478bd5-0d94-4abf-91d0-9e777f66a71c','0','授权操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(5,'导出','5','','warning',0,'sys_oper_type',5,15,'f755bebf-ff56-47e0-8c1f-3c02d6b6ade6','0','导出操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(6,'导入','6','','warning',0,'sys_oper_type',5,16,'b7ad45e0-f50e-47e2-a0e9-a38f8a7a4a7e','0','导入操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(7,'强退','7','','danger',0,'sys_oper_type',5,17,'8b506d44-f67b-45c3-b053-fda3e51848ae','0','强退操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(8,'生成代码','8','','warning',0,'sys_oper_type',5,18,'951aa566-d848-40c5-a1d3-92b43bca819a','0','生成操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(9,'清空数据','9','','danger',0,'sys_oper_type',5,19,'49ff7df6-b6e8-4ff1-80a3-f9a4a9c2dc2a','0','清空操作','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'默认(Memory)','default','',NULL,1,'sys_job_store',6,20,'2278a919-e384-458d-ae08-d1b11d25b447','0','默认分组','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'数据库(Sqlalchemy)','sqlalchemy','',NULL,0,'sys_job_store',6,21,'ee93549f-2427-460d-b633-fb23ca05525d','0','数据库分组','2025-11-30 23:30:07','2025-11-30 23:30:07'),(3,'数据库(Redis)','redis','',NULL,0,'sys_job_store',6,22,'c8c0ca6a-7519-45b1-94d9-6b426af7bc3a','0','reids分组','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'线程池','default','',NULL,0,'sys_job_executor',7,23,'a55ba518-367d-4f2b-878d-92f606a1a82e','0','线程池','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'进程池','processpool','',NULL,0,'sys_job_executor',7,24,'067a8bca-0990-4304-80e0-6ad164f30e84','0','进程池','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'演示函数','scheduler_test.job','',NULL,1,'sys_job_function',8,25,'86baa0cb-2ce0-435c-9df0-f60ad0d7ff53','0','演示函数','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'指定日期(date)','date','',NULL,1,'sys_job_trigger',9,26,'82e93554-87dc-4bc6-9cbf-8817784fce29','0','指定日期任务触发器','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'间隔触发器(interval)','interval','',NULL,0,'sys_job_trigger',9,27,'d6b8d122-16e1-4ca0-b53c-ca3a1f29789a','0','间隔触发器任务触发器','2025-11-30 23:30:07','2025-11-30 23:30:07'),(3,'cron表达式','cron','',NULL,0,'sys_job_trigger',9,28,'0da2ab7e-7a13-480f-8a55-1dd4052838ef','0','间隔触发器任务触发器','2025-11-30 23:30:07','2025-11-30 23:30:07'),(1,'默认(default)','default','',NULL,1,'sys_list_class',10,29,'84cf3314-3536-48c5-8f90-ed3e06c79d0e','0','默认表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'),(2,'主要(primary)','primary','',NULL,0,'sys_list_class',10,30,'f8444959-5d33-49eb-82af-db55eee4e54f','0','主要表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'),(3,'成功(success)','success','',NULL,0,'sys_list_class',10,31,'85682bfb-bccd-489c-aade-e329ba22fedf','0','成功表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'),(4,'信息(info)','info','',NULL,0,'sys_list_class',10,32,'4644633c-f540-4bb6-923f-f712208577fc','0','信息表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'),(5,'警告(warning)','warning','',NULL,0,'sys_list_class',10,33,'15367a42-3ad2-4f24-b8a0-4148097df50f','0','警告表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'),(6,'危险(danger)','danger','',NULL,0,'sys_list_class',10,34,'27e0b3b7-e3fe-4b83-9b54-b2c2b814f0bd','0','危险表格回显样式','2025-11-30 23:30:07','2025-11-30 23:30:07'); +/*!40000 ALTER TABLE `sys_dict_data` ENABLE KEYS */; + +-- +-- Table structure for table `sys_dict_type` +-- + +DROP TABLE IF EXISTS `sys_dict_type`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_dict_type` ( + `dict_name` varchar(255) NOT NULL COMMENT '字典名称', + `dict_type` varchar(255) NOT NULL COMMENT '字典类型', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `dict_type` (`dict_type`), + UNIQUE KEY `uuid` (`uuid`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='字典类型表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_dict_type` +-- + +/*!40000 ALTER TABLE `sys_dict_type` DISABLE KEYS */; +INSERT INTO `sys_dict_type` VALUES ('用户性别','sys_user_sex',1,'ac2cab6f-e3b6-46cb-998e-cbd8e7f9c622','0','用户性别列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('系统是否','sys_yes_no',2,'330def97-e729-42ed-8e90-fdeaf6f33269','0','系统是否列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('系统状态','sys_common_status',3,'c7a0413b-de48-4714-9f88-24ba2e324dae','0','系统状态','2025-11-30 23:30:07','2025-11-30 23:30:07'),('通知类型','sys_notice_type',4,'675f9e98-6280-4f65-98a4-3bdf1a8a46bd','0','通知类型列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('操作类型','sys_oper_type',5,'529d3556-6034-4819-9525-4f271f4a711b','0','操作类型列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('任务存储器','sys_job_store',6,'bba17e70-ea81-4389-b1be-3e6a94f8015d','0','任务分组列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('任务执行器','sys_job_executor',7,'b0e2f3e9-9200-44b6-b25a-b9c91418c8bd','0','任务执行器列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('任务函数','sys_job_function',8,'43ad871f-48e6-4c6f-8326-bd332cb2fbd1','0','任务函数列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('任务触发器','sys_job_trigger',9,'bca90925-93ad-4458-a181-3f4e9ccffddf','0','任务触发器列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('表格回显样式','sys_list_class',10,'d01ee536-3e12-4313-937b-b7836ea5d5b9','0','表格回显样式列表','2025-11-30 23:30:07','2025-11-30 23:30:07'); +/*!40000 ALTER TABLE `sys_dict_type` ENABLE KEYS */; + +-- +-- Table structure for table `sys_log` +-- + +DROP TABLE IF EXISTS `sys_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_log` ( + `type` int NOT NULL COMMENT '日志类型(1登录日志 2操作日志)', + `request_path` varchar(255) NOT NULL COMMENT '请求路径', + `request_method` varchar(10) NOT NULL COMMENT '请求方式', + `request_payload` text COMMENT '请求体', + `request_ip` varchar(50) DEFAULT NULL COMMENT '请求IP地址', + `login_location` varchar(255) DEFAULT NULL COMMENT '登录位置', + `request_os` varchar(64) DEFAULT NULL COMMENT '操作系统', + `request_browser` varchar(64) DEFAULT NULL COMMENT '浏览器', + `response_code` int NOT NULL COMMENT '响应状态码', + `response_json` text COMMENT '响应体', + `process_time` varchar(20) DEFAULT NULL COMMENT '处理时间', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_log_created_id` (`created_id`), + KEY `ix_sys_log_updated_id` (`updated_id`), + CONSTRAINT `sys_log_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_log_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统日志表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_log` +-- + +/*!40000 ALTER TABLE `sys_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_log` ENABLE KEYS */; + +-- +-- Table structure for table `sys_menu` +-- + +DROP TABLE IF EXISTS `sys_menu`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_menu` ( + `name` varchar(50) NOT NULL COMMENT '菜单名称', + `type` int NOT NULL COMMENT '菜单类型(1:目录 2:菜单 3:按钮/权限 4:链接)', + `order` int NOT NULL COMMENT '显示排序', + `permission` varchar(100) DEFAULT NULL COMMENT '权限标识(如:module_system:user:list)', + `icon` varchar(50) DEFAULT NULL COMMENT '菜单图标', + `route_name` varchar(100) DEFAULT NULL COMMENT '路由名称', + `route_path` varchar(200) DEFAULT NULL COMMENT '路由路径', + `component_path` varchar(200) DEFAULT NULL COMMENT '组件路径', + `redirect` varchar(200) DEFAULT NULL COMMENT '重定向地址', + `hidden` tinyint(1) NOT NULL COMMENT '是否隐藏(True:隐藏 False:显示)', + `keep_alive` tinyint(1) NOT NULL COMMENT '是否缓存(True:是 False:否)', + `always_show` tinyint(1) NOT NULL COMMENT '是否始终显示(True:是 False:否)', + `title` varchar(50) DEFAULT NULL COMMENT '菜单标题', + `params` json DEFAULT NULL COMMENT '路由参数(JSON对象)', + `affix` tinyint(1) NOT NULL COMMENT '是否固定标签页(True:是 False:否)', + `parent_id` int DEFAULT NULL COMMENT '父菜单ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_menu_parent_id` (`parent_id`), + CONSTRAINT `sys_menu_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `sys_menu` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB AUTO_INCREMENT=113 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_menu` +-- + +/*!40000 ALTER TABLE `sys_menu` DISABLE KEYS */; +INSERT INTO `sys_menu` VALUES ('仪表盘',1,1,'','client','Dashboard','/dashboard',NULL,'/dashboard/workplace',0,1,1,'仪表盘','null',0,NULL,1,'acff2466-baf4-43c1-9767-68cd5e87374e','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('系统管理',1,2,NULL,'system','System','/system',NULL,'/system/menu',0,1,0,'系统管理','null',0,NULL,2,'c7b83bc5-d01d-4414-a643-b1124fabe3db','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('应用管理',1,3,NULL,'el-icon-ShoppingBag','Application','/application',NULL,'/application/myapp',0,0,0,'应用管理','null',0,NULL,3,'4fa097d5-d3ee-43aa-a8c3-99026f842ebc','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('监控管理',1,4,NULL,'monitor','Monitor','/monitor',NULL,'/monitor/online',0,0,0,'监控管理','null',0,NULL,4,'50d461d9-a4f8-4e78-8c3c-5875215e2093','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('代码管理',1,5,NULL,'code','Gencode','/gencode',NULL,'/gencode/backcode',0,0,0,'代码管理','null',0,NULL,5,'78b0bc5f-07d8-4f47-98e7-bfeb1707c839','0','代码管理','2025-11-30 23:30:07','2025-11-30 23:30:07'),('接口管理',1,6,NULL,'document','Common','/common',NULL,'/common/docs',0,0,0,'接口管理','null',0,NULL,6,'8dd87a3e-cf88-4f73-80ff-dd2adba5bb53','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('案例管理',1,7,NULL,'el-icon-ElementPlus','Example','/example',NULL,'/example/demo',0,0,0,'案例管理','null',0,NULL,7,'edbdba07-4d1a-429d-b2f2-31668fe6d377','0','案例管理','2025-11-30 23:30:07','2025-11-30 23:30:07'),('工作台',2,1,'dashboard:workplace:query','el-icon-PieChart','Workplace','/dashboard/workplace','dashboard/workplace',NULL,0,1,0,'工作台','null',0,1,8,'241d5916-5b2f-4408-9ccf-57f620dc6ee6','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('菜单管理',2,1,'module_system:menu:query','menu','Menu','/system/menu','module_system/menu/index',NULL,0,1,0,'菜单管理','null',0,2,9,'6fafb39a-2d77-4f5c-bf5d-1d27b856ccb3','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('部门管理',2,2,'module_system:dept:query','tree','Dept','/system/dept','module_system/dept/index',NULL,0,1,0,'部门管理','null',0,2,10,'c994866e-0959-40cb-bb98-efbaaaf8e5b7','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('岗位管理',2,3,'module_system:position:query','el-icon-Coordinate','Position','/system/position','module_system/position/index',NULL,0,1,0,'岗位管理','null',0,2,11,'dc6a19ee-9384-4871-a580-3990de3d007d','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('角色管理',2,4,'module_system:role:query','role','Role','/system/role','module_system/role/index',NULL,0,1,0,'角色管理','null',0,2,12,'8ac538ff-e005-4e2a-b0bf-d4e74e8852f3','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('用户管理',2,5,'module_system:user:query','el-icon-User','User','/system/user','module_system/user/index',NULL,0,1,0,'用户管理','null',0,2,13,'1c33bedb-696f-400c-a414-78211e50efe9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('日志管理',2,6,'module_system:log:query','el-icon-Aim','Log','/system/log','module_system/log/index',NULL,0,1,0,'日志管理','null',0,2,14,'18d6e3a9-4c6e-4266-bdbd-50301676108c','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告管理',2,7,'module_system:notice:query','bell','Notice','/system/notice','module_system/notice/index',NULL,0,1,0,'公告管理','null',0,2,15,'56946652-bfea-4ba6-9a9e-bba24ac2a66f','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('参数管理',2,8,'module_system:param:query','setting','Params','/system/param','module_system/param/index',NULL,0,1,0,'参数管理','null',0,2,16,'a191776c-556b-4983-adb4-9049d676ee5c','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('字典管理',2,9,'module_system:dict_type:query','dict','Dict','/system/dict','module_system/dict/index',NULL,0,1,0,'字典管理','null',0,2,17,'0c0cd53d-0377-4132-bcd9-acc4855f6582','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('我的应用',2,1,'module_application:myapp:query','el-icon-ShoppingCartFull','MYAPP','/application/myapp','module_application/myapp/index',NULL,0,1,0,'我的应用','null',0,3,18,'0d073ab7-fa43-4b52-a9e1-1b54b84ac7fd','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('任务管理',2,2,'module_application:job:query','el-icon-DataLine','Job','/application/job','module_application/job/index',NULL,0,1,0,'任务管理','null',0,3,19,'938eabe0-8cb7-4e46-b2b2-bc698d1f041b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('AI智能助手',2,3,'module_application:ai:chat','el-icon-ToiletPaper','AI','/application/ai','module_application/ai/index',NULL,0,1,0,'AI智能助手','null',0,3,20,'b71d1958-c2eb-47bd-a6e9-d882a40124f9','0','AI智能助手','2025-11-30 23:30:07','2025-11-30 23:30:07'),('流程管理',2,4,'module_application:workflow:query','el-icon-ShoppingBag','Workflow','/application/workflow','module_application/workflow/index',NULL,0,1,0,'我的流程','null',0,3,21,'92d83108-7045-439d-b4f6-844f27fe7897','0','我的流程','2025-11-30 23:30:07','2025-11-30 23:30:07'),('在线用户',2,1,'module_monitor:online:query','el-icon-Headset','MonitorOnline','/monitor/online','module_monitor/online/index',NULL,0,0,0,'在线用户','null',0,4,22,'f356693e-75f8-4886-bf80-8df8f4573c76','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('服务器监控',2,2,'module_monitor:server:query','el-icon-Odometer','MonitorServer','/monitor/server','module_monitor/server/index',NULL,0,0,0,'服务器监控','null',0,4,23,'0b220d5c-46c1-41b1-9dc5-e548c88d26b3','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('缓存监控',2,3,'module_monitor:cache:query','el-icon-Stopwatch','MonitorCache','/monitor/cache','module_monitor/cache/index',NULL,0,0,0,'缓存监控','null',0,4,24,'80fd4593-cd20-44b7-9a16-974995074c79','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件管理',2,4,'module_monitor:resource:query','el-icon-Files','Resource','/monitor/resource','module_monitor/resource/index',NULL,0,1,0,'文件管理','null',0,4,25,'e2c92994-bb21-4ca7-85d9-4f5eb0effd0b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('代码生成',2,1,'module_generator:gencode:query','code','Backcode','/gencode/backcode','module_generator/backcode/index',NULL,0,1,0,'代码生成','null',0,5,26,'c00d6c1a-9fc9-4585-ac24-9d84b37200c1','0','代码生成','2025-11-30 23:30:07','2025-11-30 23:30:07'),('Swagger文档',4,1,'module_common:docs:query','api','Docs','/common/docs','module_common/docs/index',NULL,0,0,0,'Swagger文档','null',0,6,27,'6908e475-9be1-45fe-9f2e-132360860656','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('Redoc文档',4,2,'module_common:redoc:query','el-icon-Document','Redoc','/common/redoc','module_common/redoc/index',NULL,0,0,0,'Redoc文档','null',0,6,28,'6eac861d-8e10-4f9c-a22a-821eb9e1523d','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('示例管理',2,1,'module_example:demo:query','el-icon-ElementPlus','Demo','/example/demo','module_example/demo/index',NULL,0,1,0,'示例管理','null',0,7,29,'9e9026f6-d58c-4e18-b9da-f9d10c55bc4f','0','示例管理','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建菜单',3,1,'module_system:menu:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建菜单','null',0,9,30,'98519cf9-afaf-4f45-bafd-6d00a0e92cd3','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改菜单',3,2,'module_system:menu:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改菜单','null',0,9,31,'03b5da40-055b-4d82-bc44-b516b2601490','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除菜单',3,3,'module_system:menu:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除菜单','null',0,9,32,'9f3566bb-2fa1-4eae-af7f-eb6d7cff9a10','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改菜单状态',3,4,'module_system:menu:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改菜单状态','null',0,9,33,'a495a9b2-bcbf-4be0-b91a-494000c1bdd4','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建部门',3,1,'module_system:dept:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建部门','null',0,10,34,'893cb650-a11a-405f-9193-9135e0b21984','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改部门',3,2,'module_system:dept:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改部门','null',0,10,35,'4034724e-b400-4862-90d0-1752cc565ee5','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除部门',3,3,'module_system:dept:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除部门','null',0,10,36,'f210d9af-3a9d-401d-80f5-9d767befa80b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改部门状态',3,4,'module_system:dept:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改部门状态','null',0,10,37,'a1b439ba-6e60-4c94-a54d-db2d380efcd6','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建岗位',3,1,'module_system:position:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建岗位','null',0,11,38,'7c88dd16-01c9-404f-8735-1c876b369dd9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改岗位',3,2,'module_system:position:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改岗位','null',0,11,39,'da9c1359-e522-4b24-8369-48196d6f0bdb','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除岗位',3,3,'module_system:position:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改岗位','null',0,11,40,'496c444b-18e4-4485-b6b6-c0d07c2acf41','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改岗位状态',3,4,'module_system:position:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改岗位状态','null',0,11,41,'9066be99-f7ee-4ee9-8856-1a14c16c2449','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('岗位导出',3,5,'module_system:position:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'岗位导出','null',0,11,42,'1c02ae37-aa41-4f09-b983-024e363f686f','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('设置角色权限',3,8,'module_system:role:permission',NULL,NULL,NULL,NULL,NULL,0,1,0,'设置角色权限','null',0,11,43,'baa7a7cd-5175-4f6b-a02b-d1cb3d6da6b7','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建角色',3,1,'module_system:role:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建角色','null',0,12,44,'bc634733-f481-4d20-9466-9dea40da8dff','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改角色',3,2,'module_system:role:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改角色','null',0,12,45,'07303500-5951-4194-8031-ca4090fdec2b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除角色',3,3,'module_system:role:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除角色','null',0,12,46,'98883e00-a4fa-4668-8d2e-89b311d54fb0','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改角色状态',3,4,'module_system:role:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改角色状态','null',0,12,47,'6dfb8c65-ca4f-44e7-8502-bcc0709ea957','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('角色导出',3,6,'module_system:role:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'角色导出','null',0,12,48,'b1832bf3-7845-4724-bb80-3e21da719c29','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建用户',3,1,'module_system:user:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建用户','null',0,13,49,'d578972b-4f7f-4048-80fc-536deb096aa4','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改用户',3,2,'module_system:user:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改用户','null',0,13,50,'e2740ff8-b934-43b9-8558-d8714f29800b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除用户',3,3,'module_system:user:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除用户','null',0,13,51,'87a54373-babf-406c-825a-c3652aab4c8a','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改用户状态',3,4,'module_system:user:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改用户状态','null',0,13,52,'cfafff9a-618c-4113-95a0-02a3c29e4966','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出用户',3,5,'module_system:user:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出用户','null',0,13,53,'6c2a9f2e-83e5-4d7d-aea2-927454bb78a8','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导入用户',3,6,'module_system:user:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入用户','null',0,13,54,'5e295be1-bb43-4c4a-acc0-bc5640eb7cde','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('日志删除',3,1,'module_system:log:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'日志删除','null',0,14,55,'da2c2092-6b23-495e-836a-7c22bc1b442e','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('日志导出',3,2,'module_system:log:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'日志导出','null',0,14,56,'9837c9c6-295e-4d30-a153-08d3553282f1','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告创建',3,1,'module_system:notice:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告创建','null',0,15,57,'f376c9ad-4cef-45f4-96ab-7835867b08c7','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告修改',3,2,'module_system:notice:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改用户','null',0,15,58,'13f65be3-39db-4e10-9861-fd8a347d20ac','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告删除',3,3,'module_system:notice:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告删除','null',0,15,59,'90aa5609-461a-4063-a682-c092b0f6931e','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告导出',3,4,'module_system:notice:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告导出','null',0,15,60,'23333afe-59bd-4b79-9aec-e22660d58f80','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('公告批量修改状态',3,5,'module_system:notice:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'公告批量修改状态','null',0,15,61,'85b2962d-69cf-4b12-9473-e7b8df0f4b00','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建参数',3,1,'module_system:param:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建参数','null',0,16,62,'2321cb42-19f8-4f22-aefa-b41e96e9b579','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改参数',3,2,'module_system:param:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改参数','null',0,16,63,'3bcc8210-003b-430f-91b1-7cea025827fc','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除参数',3,3,'module_system:param:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除参数','null',0,16,64,'9e239440-d5db-4801-bd6b-c8a44c994eed','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出参数',3,4,'module_system:param:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出参数','null',0,16,65,'ae30e885-18b7-490b-80a4-8f9942f17bc9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('参数上传',3,5,'module_system:param:upload',NULL,NULL,NULL,NULL,NULL,0,1,0,'参数上传','null',0,16,66,'d736cf3f-05f6-4b13-a3e6-67a98af65680','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建字典类型',3,1,'module_system:dict_type:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建字典类型','null',0,17,67,'f109555c-2de1-4bbe-8735-5ffb40a97aef','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改字典类型',3,2,'module_system:dict_type:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改字典类型','null',0,17,68,'77337418-7493-4487-9436-98b252db90fa','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除字典类型',3,3,'module_system:dict_type:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除字典类型','null',0,17,69,'9e29aa5b-2e31-4a8b-83df-4a3749238b28','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出字典类型',3,4,'module_system:dict_type:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典类型','null',0,17,70,'baa0eac3-4ec5-4af2-ad46-fc6c6f53e592','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改字典状态',3,5,'module_system:dict_type:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典类型','null',0,17,71,'0d5a304a-5124-4b8e-b468-a3cdd2e69089','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('字典数据查询',3,6,'module_system:dict_data:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'字典数据查询','null',0,17,72,'9a4e0bc3-0c7e-4603-81af-53f9d2878b4d','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建字典数据',3,7,'module_system:dict_data:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建字典数据','null',0,17,73,'9998bd2b-d4fd-4892-b239-5e4f4879bcb5','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改字典数据',3,8,'module_system:dict_data:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改字典数据','null',0,17,74,'e1d01836-72e9-4ad9-83cc-03e1afa74371','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除字典数据',3,9,'module_system:dict_data:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除字典数据','null',0,17,75,'ce04b9c4-6a6e-498f-b64b-c2d08d163332','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出字典数据',3,10,'module_system:dict_data:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出字典数据','null',0,17,76,'b2061c45-00bf-4566-96ea-4f30db1885d6','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改字典数据状态',3,11,'module_system:dict_data:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改字典数据状态','null',0,17,77,'200dcdcd-4b69-4ab6-98d8-bbc7ecfd15c9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建应用',3,1,'module_application:myapp:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建应用','null',0,18,78,'0ef77606-9161-4f4a-a071-49e2d7d5406d','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改应用',3,2,'module_application:myapp:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改应用','null',0,18,79,'8b59a401-4ee8-4c9a-9a0b-0f47a9233641','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除应用',3,3,'module_application:myapp:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除应用','null',0,18,80,'2351d369-2743-4704-9bc3-1aad0236bf77','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改应用状态',3,4,'module_application:myapp:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改应用状态','null',0,18,81,'6b9ae6a4-3474-41f1-bde1-7e61acbab3b1','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建任务',3,1,'module_application:job:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建任务','null',0,19,82,'a81dd6de-1edc-4eae-8f88-5e7690aca3eb','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('修改和操作任务',3,2,'module_application:job:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'修改和操作任务','null',0,19,83,'517672cf-b0cb-4a3e-b121-d0dcbdb89dde','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除和清除任务',3,3,'module_application:job:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除和清除任务','null',0,19,84,'86b4bf38-d340-4edd-9e7b-673e6e032165','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出定时任务',3,4,'module_application:job:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出定时任务','null',0,19,85,'783a4ce4-e2c4-450e-be4d-5fb866bbb08b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('智能对话',3,1,'module_application:ai:chat',NULL,NULL,NULL,NULL,NULL,0,1,0,'智能对话','null',0,20,86,'aaa3dbad-d5cc-488f-8c96-cb10bb61eae8','0','智能对话','2025-11-30 23:30:07','2025-11-30 23:30:07'),('在线用户强制下线',3,1,'module_monitor:online:delete',NULL,NULL,NULL,NULL,NULL,0,0,0,'在线用户强制下线','null',0,22,87,'8fc88c13-321d-40dc-89dd-28220b3b991b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('清除缓存',3,1,'module_monitor:cache:delete',NULL,NULL,NULL,NULL,NULL,0,0,0,'清除缓存','null',0,24,88,'5052ca7f-b520-4951-94cf-75c616b42afe','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件上传',3,1,'module_monitor:resource:upload',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件上传','null',0,25,89,'12e716ff-5b22-491e-8419-fec6bc7c30d0','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件下载',3,2,'module_monitor:resource:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件下载','null',0,25,90,'13e57315-d533-4c8c-9861-356f38bb07f8','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件删除',3,3,'module_monitor:resource:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件删除','null',0,25,91,'52f5428d-aa9c-4c7c-a65d-b955ac7c9c83','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件移动',3,4,'module_monitor:resource:move',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件移动','null',0,25,92,'438dfadb-5712-43ba-b76e-5210955a1e93','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件复制',3,5,'module_monitor:resource:copy',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件复制','null',0,25,93,'c19353db-4880-404b-9755-68d0f7810c28','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('文件重命名',3,6,'module_monitor:resource:rename',NULL,NULL,NULL,NULL,NULL,0,1,0,'文件重命名','null',0,25,94,'e4f4c02f-9af9-42c7-86dd-e5e4ead1c22a','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建目录',3,7,'module_monitor:resource:create_dir',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建目录','null',0,25,95,'ee890fa3-260f-47cd-8ff7-a7b01cf6ef1f','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出文件列表',3,9,'module_monitor:resource:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出文件列表','null',0,25,96,'07632b69-bfe6-4671-b834-b57bcee5ca89','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('查询代码生成业务表列表',3,1,'module_generator:gencode:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'查询代码生成业务表列表','null',0,26,97,'64635267-d546-4e7f-93b9-6fad210f95a4','0','查询代码生成业务表列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建表结构',3,2,'module_generator:gencode:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建表结构','null',0,26,98,'21c90294-8261-410c-b546-b1655d70f6fc','0','创建表结构','2025-11-30 23:30:07','2025-11-30 23:30:07'),('编辑业务表信息',3,3,'module_generator:gencode:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'编辑业务表信息','null',0,26,99,'c33fee4f-1203-4d2a-bde5-79dd33ef4995','0','编辑业务表信息','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除业务表信息',3,4,'module_generator:gencode:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除业务表信息','null',0,26,100,'b05a652f-704b-4118-8359-0c5582bf125e','0','删除业务表信息','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导入表结构',3,5,'module_generator:gencode:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入表结构','null',0,26,101,'baa3517e-1be3-4557-acb2-ebf8ca440e60','0','导入表结构','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量生成代码',3,6,'module_generator:gencode:operate',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量生成代码','null',0,26,102,'127ecd32-9f17-45cd-b9b8-83bf0267206a','0','批量生成代码','2025-11-30 23:30:07','2025-11-30 23:30:07'),('生成代码到指定路径',3,7,'module_generator:gencode:code',NULL,NULL,NULL,NULL,NULL,0,1,0,'生成代码到指定路径','null',0,26,103,'769e9f13-dee0-46d2-b3d6-d0c09a65db8b','0','生成代码到指定路径','2025-11-30 23:30:07','2025-11-30 23:30:07'),('查询数据库表列表',3,8,'module_generator:dblist:query',NULL,NULL,NULL,NULL,NULL,0,1,0,'查询数据库表列表','null',0,26,104,'4babba87-555f-4796-a94b-2a7d3aea93ed','0','查询数据库表列表','2025-11-30 23:30:07','2025-11-30 23:30:07'),('同步数据库',3,9,'module_generator:db:sync',NULL,NULL,NULL,NULL,NULL,0,1,0,'同步数据库','null',0,26,105,'ba0d8fe5-fb48-4424-ae89-d1ecc60ecff8','0','同步数据库','2025-11-30 23:30:07','2025-11-30 23:30:07'),('创建示例',3,1,'module_example:demo:create',NULL,NULL,NULL,NULL,NULL,0,1,0,'创建示例','null',0,29,106,'504f0e40-ff3c-4005-8314-ec8d4240c413','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('更新示例',3,2,'module_example:demo:update',NULL,NULL,NULL,NULL,NULL,0,1,0,'更新示例','null',0,29,107,'310b16ec-e515-411f-9181-69b4d26e7e81','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('删除示例',3,3,'module_example:demo:delete',NULL,NULL,NULL,NULL,NULL,0,1,0,'删除示例','null',0,29,108,'180a823d-fa0b-4a42-a01b-054a939e230b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('批量修改示例状态',3,4,'module_example:demo:patch',NULL,NULL,NULL,NULL,NULL,0,1,0,'批量修改示例状态','null',0,29,109,'fb3c4a6e-761b-4c93-97a4-439e31bd9c9a','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导出示例',3,5,'module_example:demo:export',NULL,NULL,NULL,NULL,NULL,0,1,0,'导出示例','null',0,29,110,'72ddc679-5a99-46f3-b8d6-aab9cf0e19f9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('导入示例',3,6,'module_example:demo:import',NULL,NULL,NULL,NULL,NULL,0,1,0,'导入示例','null',0,29,111,'ee50f8ad-74ef-48d7-9cb9-32021cea5bd2','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('下载导入示例模版',3,7,'module_example:demo:download',NULL,NULL,NULL,NULL,NULL,0,1,0,'下载导入示例模版','null',0,29,112,'7e746d8a-2747-4bb3-a4e0-1b67272f0a1a','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'); +/*!40000 ALTER TABLE `sys_menu` ENABLE KEYS */; + +-- +-- Table structure for table `sys_notice` +-- + +DROP TABLE IF EXISTS `sys_notice`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_notice` ( + `notice_title` varchar(50) NOT NULL COMMENT '公告标题', + `notice_type` varchar(50) NOT NULL COMMENT '公告类型(1通知 2公告)', + `notice_content` text COMMENT '公告内容', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_notice_created_id` (`created_id`), + KEY `ix_sys_notice_updated_id` (`updated_id`), + CONSTRAINT `sys_notice_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_notice_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='通知公告表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_notice` +-- + +/*!40000 ALTER TABLE `sys_notice` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_notice` ENABLE KEYS */; + +-- +-- Table structure for table `sys_param` +-- + +DROP TABLE IF EXISTS `sys_param`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_param` ( + `config_name` varchar(500) NOT NULL COMMENT '参数名称', + `config_key` varchar(500) NOT NULL COMMENT '参数键名', + `config_value` varchar(500) DEFAULT NULL COMMENT '参数键值', + `config_type` tinyint(1) DEFAULT NULL COMMENT '系统内置(True:是 False:否)', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`) +) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统参数表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_param` +-- + +/*!40000 ALTER TABLE `sys_param` DISABLE KEYS */; +INSERT INTO `sys_param` VALUES ('网站名称','sys_web_title','FastApiAdmin',1,1,'3cfed3d0-0069-4508-9430-9dbabcfe2843','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('网站描述','sys_web_description','FastApiAdmin 是完全开源的权限管理系统',1,2,'ab65533d-96c3-4a60-a194-f5863010212c','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('网页图标','sys_web_favicon','https://service.fastapiadmin.com/api/v1/static/image/favicon.png',1,3,'7af7ed61-a1be-4aaf-a4fa-f40d928994fd','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('网站Logo','sys_web_logo','https://service.fastapiadmin.com/api/v1/static/image/logo.png',1,4,'c9fe7f5f-e572-4afe-9166-b01f3306d52e','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('登录背景','sys_login_background','https://service.fastapiadmin.com/api/v1/static/image/background.svg',1,5,'da453975-1a06-4649-b31b-2f06589e8be1','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('版权信息','sys_web_copyright','Copyright © 2025-2026 service.fastapiadmin.com 版权所有',1,6,'09be19b1-974e-4bcb-abfd-e3a2f87393fc','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('备案信息','sys_keep_record','陕ICP备2025069493号-1',1,7,'e35655d1-264c-41c5-9ddb-0c1582c59834','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('帮助文档','sys_help_doc','https://service.fastapiadmin.com',1,8,'5b25feee-04e7-4cf3-97b3-e6836045a988','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('隐私政策','sys_web_privacy','https://github.com/1014TaoTao/FastapiAdmin/blob/master/LICENSE',1,9,'52f64449-56c1-43d3-9316-6293f9166b9d','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('用户协议','sys_web_clause','https://github.com/1014TaoTao/FastapiAdmin/blob/master/LICENSE',1,10,'6194b69a-dbc8-4068-b830-b81ce05d7671','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('源码代码','sys_git_code','https://github.com/1014TaoTao/FastapiAdmin.git',1,11,'e69dd930-7879-4274-a47a-a3c4830f4b5b','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('项目版本','sys_web_version','2.0.0',1,12,'bfd1e39b-4944-4f2f-bd3c-03f4792514b9','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('演示模式启用','demo_enable','false',1,13,'324da1f8-b9dd-40ae-bea4-086a3b4212d5','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('演示访问IP白名单','ip_white_list','[\"127.0.0.1\"]',1,14,'32218b4c-a78d-4533-9703-cb5b526940ce','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('接口白名单','white_api_list_path','[\"/api/v1/system/auth/login\", \"/api/v1/system/auth/token/refresh\", \"/api/v1/system/auth/captcha/get\", \"/api/v1/system/auth/logout\", \"/api/v1/system/config/info\", \"/api/v1/system/user/current/info\", \"/api/v1/system/notice/available\"]',1,15,'f4473d46-9a96-4e3e-ba48-ba8b2873104e','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'),('访问IP黑名单','ip_black_list','[]',1,16,'cf5558b1-0312-40c3-8d2f-031da1dde565','0','初始化数据','2025-11-30 23:30:07','2025-11-30 23:30:07'); +/*!40000 ALTER TABLE `sys_param` ENABLE KEYS */; + +-- +-- Table structure for table `sys_position` +-- + +DROP TABLE IF EXISTS `sys_position`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_position` ( + `name` varchar(40) NOT NULL COMMENT '岗位名称', + `order` int NOT NULL COMMENT '显示排序', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_position_created_id` (`created_id`), + KEY `ix_sys_position_updated_id` (`updated_id`), + CONSTRAINT `sys_position_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_position_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='岗位表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_position` +-- + +/*!40000 ALTER TABLE `sys_position` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_position` ENABLE KEYS */; + +-- +-- Table structure for table `sys_role` +-- + +DROP TABLE IF EXISTS `sys_role`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_role` ( + `name` varchar(40) NOT NULL COMMENT '角色名称', + `code` varchar(20) DEFAULT NULL COMMENT '角色编码', + `order` int NOT NULL COMMENT '显示排序', + `data_scope` int NOT NULL COMMENT '数据权限范围(1:仅本人 2:本部门 3:本部门及以下 4:全部 5:自定义)', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `ix_sys_role_code` (`code`), + KEY `ix_sys_role_updated_id` (`updated_id`), + KEY `ix_sys_role_created_id` (`created_id`), + CONSTRAINT `sys_role_ibfk_1` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_role_ibfk_2` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_role` +-- + +/*!40000 ALTER TABLE `sys_role` DISABLE KEYS */; +INSERT INTO `sys_role` VALUES ('管理员角色','ADMIN',1,4,1,'82087985-9004-4275-af38-74f23ae1f51f','0','初始化角色','2025-11-30 23:30:07','2025-11-30 23:30:07',1,1),('普通角色','COMMON',1,3,2,'1929fc4a-a2e2-4583-9d18-7f4b3f8c1315','0','初始化角色','2025-11-30 23:30:07','2025-11-30 23:30:07',1,1); +/*!40000 ALTER TABLE `sys_role` ENABLE KEYS */; + +-- +-- Table structure for table `sys_role_depts` +-- + +DROP TABLE IF EXISTS `sys_role_depts`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_role_depts` ( + `role_id` int NOT NULL COMMENT '角色ID', + `dept_id` int NOT NULL COMMENT '部门ID', + PRIMARY KEY (`role_id`,`dept_id`), + KEY `dept_id` (`dept_id`), + CONSTRAINT `sys_role_depts_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `sys_role_depts_ibfk_2` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色部门关联表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_role_depts` +-- + +/*!40000 ALTER TABLE `sys_role_depts` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_role_depts` ENABLE KEYS */; + +-- +-- Table structure for table `sys_role_menus` +-- + +DROP TABLE IF EXISTS `sys_role_menus`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_role_menus` ( + `role_id` int NOT NULL COMMENT '角色ID', + `menu_id` int NOT NULL COMMENT '菜单ID', + PRIMARY KEY (`role_id`,`menu_id`), + KEY `menu_id` (`menu_id`), + CONSTRAINT `sys_role_menus_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `sys_role_menus_ibfk_2` FOREIGN KEY (`menu_id`) REFERENCES `sys_menu` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色菜单关联表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_role_menus` +-- + +/*!40000 ALTER TABLE `sys_role_menus` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_role_menus` ENABLE KEYS */; + +-- +-- Table structure for table `sys_user` +-- + +DROP TABLE IF EXISTS `sys_user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_user` ( + `username` varchar(32) NOT NULL COMMENT '用户名/登录账号', + `password` varchar(255) NOT NULL COMMENT '密码哈希', + `name` varchar(32) NOT NULL COMMENT '昵称', + `mobile` varchar(11) DEFAULT NULL COMMENT '手机号', + `email` varchar(64) DEFAULT NULL COMMENT '邮箱', + `gender` varchar(1) DEFAULT NULL COMMENT '性别(0:男 1:女 2:未知)', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL地址', + `is_superuser` tinyint(1) NOT NULL COMMENT '是否超管', + `last_login` datetime DEFAULT NULL COMMENT '最后登录时间', + `gitee_login` varchar(32) DEFAULT NULL COMMENT 'Gitee登录', + `github_login` varchar(32) DEFAULT NULL COMMENT 'Github登录', + `wx_login` varchar(32) DEFAULT NULL COMMENT '微信登录', + `qq_login` varchar(32) DEFAULT NULL COMMENT 'QQ登录', + `dept_id` int DEFAULT NULL COMMENT '部门ID', + `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `uuid` varchar(64) NOT NULL COMMENT 'UUID全局唯一标识', + `status` varchar(10) NOT NULL COMMENT '是否启用(0:启用 1:禁用)', + `description` text COMMENT '备注/描述', + `created_time` datetime NOT NULL COMMENT '创建时间', + `updated_time` datetime NOT NULL COMMENT '更新时间', + `created_id` int DEFAULT NULL COMMENT '创建人ID', + `updated_id` int DEFAULT NULL COMMENT '更新人ID', + PRIMARY KEY (`id`), + UNIQUE KEY `username` (`username`), + UNIQUE KEY `uuid` (`uuid`), + UNIQUE KEY `mobile` (`mobile`), + UNIQUE KEY `email` (`email`), + KEY `ix_sys_user_created_id` (`created_id`), + KEY `ix_sys_user_dept_id` (`dept_id`), + KEY `ix_sys_user_updated_id` (`updated_id`), + CONSTRAINT `sys_user_ibfk_1` FOREIGN KEY (`dept_id`) REFERENCES `sys_dept` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_user_ibfk_2` FOREIGN KEY (`created_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `sys_user_ibfk_3` FOREIGN KEY (`updated_id`) REFERENCES `sys_user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_user` +-- + +/*!40000 ALTER TABLE `sys_user` DISABLE KEYS */; +INSERT INTO `sys_user` VALUES ('admin','$2b$12$e2IJgS/cvHgJ0H3G7Xa08OXoXnk6N/NX3IZRtubBDElA0VLZhkNOa','超级管理员',NULL,NULL,'0','https://service.fastapiadmin.com/api/v1/static/image/avatar.png',1,'2025-11-30 23:32:15',NULL,NULL,NULL,NULL,1,1,'d6cb30d6-fc27-4b61-8f40-f6873da494b1','0','超级管理员','2025-11-30 23:30:07','2025-11-30 23:32:15',NULL,NULL); +/*!40000 ALTER TABLE `sys_user` ENABLE KEYS */; + +-- +-- Table structure for table `sys_user_positions` +-- + +DROP TABLE IF EXISTS `sys_user_positions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_user_positions` ( + `user_id` int NOT NULL COMMENT '用户ID', + `position_id` int NOT NULL COMMENT '岗位ID', + PRIMARY KEY (`user_id`,`position_id`), + KEY `position_id` (`position_id`), + CONSTRAINT `sys_user_positions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `sys_user_positions_ibfk_2` FOREIGN KEY (`position_id`) REFERENCES `sys_position` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户岗位关联表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_user_positions` +-- + +/*!40000 ALTER TABLE `sys_user_positions` DISABLE KEYS */; +/*!40000 ALTER TABLE `sys_user_positions` ENABLE KEYS */; + +-- +-- Table structure for table `sys_user_roles` +-- + +DROP TABLE IF EXISTS `sys_user_roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_user_roles` ( + `user_id` int NOT NULL COMMENT '用户ID', + `role_id` int NOT NULL COMMENT '角色ID', + PRIMARY KEY (`user_id`,`role_id`), + KEY `role_id` (`role_id`), + CONSTRAINT `sys_user_roles_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `sys_user_roles_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sys_user_roles` +-- + +/*!40000 ALTER TABLE `sys_user_roles` DISABLE KEYS */; +INSERT INTO `sys_user_roles` VALUES (1,1); +/*!40000 ALTER TABLE `sys_user_roles` ENABLE KEYS */; + +-- +-- Dumping routines for database 'fastapiadmin' +-- +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2025-11-30 23:33:03 diff --git a/frontend/src/api/module_application/job.ts b/frontend/src/api/module_application/job.ts index 8453a516..9a2d8b06 100644 --- a/frontend/src/api/module_application/job.ts +++ b/frontend/src/api/module_application/job.ts @@ -125,7 +125,9 @@ export interface JobPageQuery extends PageQuery { name?: string; status?: string; created_id?: number; + updated_id?: number; created_time?: string[]; + updated_time?: string[]; } export interface JobLogPageQuery extends PageQuery { @@ -133,6 +135,7 @@ export interface JobLogPageQuery extends PageQuery { job_name?: string; status?: string; created_time?: string[]; + updated_time?: string[]; } export interface JobOptionData { @@ -155,8 +158,6 @@ export interface JobTable extends BaseType { end_date?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } export interface JobForm extends BaseFormType { @@ -187,7 +188,6 @@ export interface JobRunLog extends BaseType { coalesce: boolean; max_instances: number; next_run_time: string; - tenant?: CommonType; } // 定时任务日志详情接口(对应数据库日志表) @@ -201,7 +201,6 @@ export interface JobLogDetail extends BaseType { job_trigger?: string; job_message?: string; exception_info?: string; - tenant?: CommonType; } // 定时任务日志列表接口(对应数据库日志表) @@ -215,5 +214,4 @@ export interface JobLogTable extends BaseType { job_trigger?: string; job_message?: string; exception_info?: string; - tenant?: CommonType; } diff --git a/frontend/src/api/module_application/myapp.ts b/frontend/src/api/module_application/myapp.ts index 2aa107ea..640a47d2 100644 --- a/frontend/src/api/module_application/myapp.ts +++ b/frontend/src/api/module_application/myapp.ts @@ -86,6 +86,8 @@ export interface ApplicationPageQuery extends PageQuery { status?: string; created_id?: number; created_time?: string[]; + updated_id?: number; + updated_time?: string[]; } /** @@ -97,8 +99,6 @@ export interface ApplicationInfo extends BaseType { icon_url?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } /** diff --git a/frontend/src/api/module_example/demo.ts b/frontend/src/api/module_example/demo.ts index 4b936ead..5cc17fff 100644 --- a/frontend/src/api/module_example/demo.ts +++ b/frontend/src/api/module_example/demo.ts @@ -82,22 +82,18 @@ const DemoAPI = { export default DemoAPI; export interface DemoPageQuery extends PageQuery { - /** 示例标题 */ name?: string; - /** 示例状态 */ status?: string; - /** 创建时间 */ created_time?: string[]; - // 创建人ID + updated_time?: string[]; created_id?: number; + updated_id?: number; } export interface DemoTable extends BaseType { name?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } export interface DemoForm extends BaseFormType { diff --git a/frontend/src/api/module_generator/gencode.ts b/frontend/src/api/module_generator/gencode.ts index 73bee483..db008e19 100644 --- a/frontend/src/api/module_generator/gencode.ts +++ b/frontend/src/api/module_generator/gencode.ts @@ -137,9 +137,7 @@ export interface GenTablePageQuery extends PageQuery { } /** 代码生成业务表模型 */ -export interface GenTableSchema { - /** 主键 */ - id?: number; +export interface GenTableSchema extends BaseType { /** 表名称 */ table_name?: string; /** 表描述 */ @@ -160,8 +158,6 @@ export interface GenTableSchema { function_name?: string; /** 所属父级分类 */ parent_menu_id?: number; - /** 表描述 */ - description?: string; /** 表列信息 */ columns: GenTableColumnSchema[]; /** 主键信息 */ @@ -170,12 +166,12 @@ export interface GenTableSchema { sub_table?: GenTableSchema; /** 是否为子表 */ sub?: boolean; + created_by?: creatorType; + updated_by?: updatorType; } /** 代码生成业务表列模型 */ -export interface GenTableColumnSchema { - /** 主键 */ - id?: number; +export interface GenTableColumnSchema extends BaseType { /** 归属表编号 */ table_id?: number; /** 列名称 */ @@ -216,6 +212,6 @@ export interface GenTableColumnSchema { dict_type?: string; /** 排序 */ sort?: number; - /** 功能描述 */ - description?: string; + created_by?: creatorType; + updated_by?: updatorType; } diff --git a/frontend/src/api/module_system/customer.ts b/frontend/src/api/module_system/customer.ts deleted file mode 100644 index f0a520b2..00000000 --- a/frontend/src/api/module_system/customer.ts +++ /dev/null @@ -1,101 +0,0 @@ -import request from "@/utils/request"; - -const API_PATH = "/system/customer"; - -const CustomerAPI = { - listCustomer(query: CustomerPageQuery) { - return request>>({ - url: `${API_PATH}/list`, - method: "get", - params: query, - }); - }, - - detailCustomer(query: number) { - return request>({ - url: `${API_PATH}/detail/${query}`, - method: "get", - }); - }, - - createCustomer(body: CustomerForm) { - return request({ - url: `${API_PATH}/create`, - method: "post", - data: body, - }); - }, - - updateCustomer(id: number, body: CustomerForm) { - return request({ - url: `${API_PATH}/update/${id}`, - method: "put", - data: body, - }); - }, - - deleteCustomer(body: number[]) { - return request({ - url: `${API_PATH}/delete`, - method: "delete", - data: body, - }); - }, - - batchCustomer(body: BatchType) { - return request({ - url: `${API_PATH}/available/setting`, - method: "patch", - data: body, - }); - }, - - exportCustomer(body: CustomerPageQuery) { - return request({ - url: `${API_PATH}/export`, - method: "post", - data: body, - responseType: "blob", - }); - }, - - downloadCustomer() { - return request({ - url: `${API_PATH}/download/template`, - method: "post", - responseType: "blob", - }); - }, - - importCustomer(body: FormData) { - return request({ - url: `${API_PATH}/import`, - method: "post", - data: body, - headers: { - "Content-Type": "multipart/form-data", - }, - }); - }, -}; - -export default CustomerAPI; - -export interface CustomerPageQuery extends PageQuery { - name?: string; - status?: string; - created_time?: string[]; -} - -export interface CustomerTable extends BaseType { - name?: string; - code?: string; - created_by?: creatorType; - updated_by?: updatorType; - tenant?: CommonType; -} - -export interface CustomerForm extends BaseFormType { - name?: string; - code?: string; -} diff --git a/frontend/src/api/module_system/dept.ts b/frontend/src/api/module_system/dept.ts index 310ac3d6..cc709692 100644 --- a/frontend/src/api/module_system/dept.ts +++ b/frontend/src/api/module_system/dept.ts @@ -69,9 +69,6 @@ export interface DeptTable extends BaseType { parent_id?: number; parent_name?: string; children?: DeptTable[]; - created_by?: creatorType; - updated_by?: updatorType; - tenant?: CommonType; } export interface DeptForm extends BaseFormType { diff --git a/frontend/src/api/module_system/dict.ts b/frontend/src/api/module_system/dict.ts index 179a4327..5bd87117 100644 --- a/frontend/src/api/module_system/dict.ts +++ b/frontend/src/api/module_system/dict.ts @@ -133,14 +133,11 @@ const DictAPI = { export default DictAPI; export interface DictPageQuery extends PageQuery { - /** 通知标题 */ dict_name?: string; - /** 通知类型 */ dict_type?: string; - /** 通知状态 */ status?: string; - /** 创建时间 */ created_time?: string[]; + updated_time?: string[]; } export interface DictDataPageQuery extends PageQuery { @@ -149,6 +146,7 @@ export interface DictDataPageQuery extends PageQuery { dict_type_id?: number; status?: string; created_time?: string[]; + updated_time?: string[]; } export interface DictTable extends BaseType { diff --git a/frontend/src/api/module_system/log.ts b/frontend/src/api/module_system/log.ts index 42cfc31a..33908915 100644 --- a/frontend/src/api/module_system/log.ts +++ b/frontend/src/api/module_system/log.ts @@ -43,7 +43,9 @@ export interface LogPageQuery extends PageQuery { request_path?: string; creator_name?: string; created_time?: string[]; + updated_time?: string[]; created_id?: number; + updated_id?: number; } export interface LogTable extends BaseType { @@ -60,6 +62,4 @@ export interface LogTable extends BaseType { process_time?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } diff --git a/frontend/src/api/module_system/menu.ts b/frontend/src/api/module_system/menu.ts index 293127df..b5187653 100644 --- a/frontend/src/api/module_system/menu.ts +++ b/frontend/src/api/module_system/menu.ts @@ -56,8 +56,8 @@ export default MenuAPI; export interface MenuPageQuery { name?: string; status?: string; - /** 创建时间 */ created_time?: string[]; + updated_time?: string[]; } export interface MenuTable extends BaseType { diff --git a/frontend/src/api/module_system/notice.ts b/frontend/src/api/module_system/notice.ts index 4275aa39..f5bc8702 100644 --- a/frontend/src/api/module_system/notice.ts +++ b/frontend/src/api/module_system/notice.ts @@ -74,7 +74,9 @@ export interface NoticePageQuery extends PageQuery { notice_type?: string; status?: string; created_time?: string[]; + updated_time?: string[]; created_id?: number; + updated_id?: number; } export interface NoticeTable extends BaseType { @@ -83,8 +85,6 @@ export interface NoticeTable extends BaseType { notice_content?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } export interface NoticeForm extends BaseFormType { diff --git a/frontend/src/api/module_system/params.ts b/frontend/src/api/module_system/params.ts index 3c60fb02..3f4678f7 100644 --- a/frontend/src/api/module_system/params.ts +++ b/frontend/src/api/module_system/params.ts @@ -71,14 +71,11 @@ const ParamsAPI = { export default ParamsAPI; export interface ConfigPageQuery extends PageQuery { - /** 配置名称 */ config_name?: string; - /** 配置键 */ config_key?: string; - /** 配置类型 */ config_type?: boolean; - /** 创建时间 */ created_time?: string[]; + updated_time?: string[]; } export interface ConfigTable extends BaseType { diff --git a/frontend/src/api/module_system/position.ts b/frontend/src/api/module_system/position.ts index 8628f6aa..cd884c40 100644 --- a/frontend/src/api/module_system/position.ts +++ b/frontend/src/api/module_system/position.ts @@ -67,6 +67,7 @@ export interface PositionPageQuery extends PageQuery { status?: string; created_id?: number; created_time?: string[]; + updated_time?: string[]; } export interface PositionTable extends BaseType { @@ -74,7 +75,6 @@ export interface PositionTable extends BaseType { order?: number; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; } export interface PositionForm extends BaseFormType { diff --git a/frontend/src/api/module_system/role.ts b/frontend/src/api/module_system/role.ts index 05008e4e..fe6dc1fb 100644 --- a/frontend/src/api/module_system/role.ts +++ b/frontend/src/api/module_system/role.ts @@ -74,10 +74,13 @@ export interface TablePageQuery extends PageQuery { name?: string; status?: string; created_id?: number; + updated_id?: number; created_time?: string[]; + updated_time?: string[]; } export interface RoleTable extends BaseType { + id: number; name: string; order?: number; code?: string; @@ -86,7 +89,6 @@ export interface RoleTable extends BaseType { depts?: permissionDeptType[]; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; } export interface RoleForm extends BaseFormType { @@ -115,7 +117,7 @@ export interface permissionMenuType { type: number; permission: string; parent_id?: number; - status: boolean; + status: string; description?: string; children?: permissionMenuType[]; } diff --git a/frontend/src/api/module_system/tenant.ts b/frontend/src/api/module_system/tenant.ts deleted file mode 100644 index bc6be9a9..00000000 --- a/frontend/src/api/module_system/tenant.ts +++ /dev/null @@ -1,102 +0,0 @@ -import request from "@/utils/request"; - -const API_PATH = "/system/tenant"; - -const TenantAPI = { - listTenant(query: TenantPageQuery) { - return request>>({ - url: `${API_PATH}/list`, - method: "get", - params: query, - }); - }, - - detailTenant(query: number) { - return request>({ - url: `${API_PATH}/detail/${query}`, - method: "get", - }); - }, - - createTenant(body: TenantForm) { - return request({ - url: `${API_PATH}/create`, - method: "post", - data: body, - }); - }, - - updateTenant(id: number, body: TenantForm) { - return request({ - url: `${API_PATH}/update/${id}`, - method: "put", - data: body, - }); - }, - - deleteTenant(body: number[]) { - return request({ - url: `${API_PATH}/delete`, - method: "delete", - data: body, - }); - }, - - batchTenant(body: BatchType) { - return request({ - url: `${API_PATH}/available/setting`, - method: "patch", - data: body, - }); - }, - - exportTenant(body: TenantPageQuery) { - return request({ - url: `${API_PATH}/export`, - method: "post", - data: body, - responseType: "blob", - }); - }, - - downloadTenant() { - return request({ - url: `${API_PATH}/download/template`, - method: "post", - responseType: "blob", - }); - }, - - importTenant(body: FormData) { - return request({ - url: `${API_PATH}/import`, - method: "post", - data: body, - headers: { - "Content-Type": "multipart/form-data", - }, - }); - }, -}; - -export default TenantAPI; - -export interface TenantPageQuery extends PageQuery { - name?: string; - status?: string; - created_time?: string[]; -} - -export interface TenantTable extends BaseType { - name?: string; - code?: string; - start_time?: string; - end_time?: string; -} - -export interface TenantForm extends BaseFormType { - name?: string; - code?: string; - start_time?: string; - end_time?: string; -} diff --git a/frontend/src/api/module_system/user.ts b/frontend/src/api/module_system/user.ts index 71ead090..da4476f1 100644 --- a/frontend/src/api/module_system/user.ts +++ b/frontend/src/api/module_system/user.ts @@ -146,14 +146,8 @@ export interface ForgetPasswordForm { } export interface RegisterForm { - name: string; - mobile?: string; username: string; password: string; - role_ids?: number[]; - customer_id?: number; - description?: string; - user_type?: string; confirmPassword: string; } @@ -166,6 +160,7 @@ export interface UserPageQuery extends PageQuery { status?: string; created_time?: string[]; created_id?: number; + updated_id?: number; } export interface searchSelectDataType { @@ -195,8 +190,6 @@ export interface UserInfo extends BaseType { last_login?: string; created_by?: creatorType; updated_by?: updatorType; - tenant?: CommonType; - customer?: CommonType; } export interface deptTreeType { diff --git a/frontend/src/views/module_application/job/components/JobLogDrawer.vue b/frontend/src/views/module_application/job/components/JobLogDrawer.vue index 6f11a260..28cc5467 100644 --- a/frontend/src/views/module_application/job/components/JobLogDrawer.vue +++ b/frontend/src/views/module_application/job/components/JobLogDrawer.vue @@ -172,7 +172,8 @@ show-overflow-tooltip /> - + + diff --git a/frontend/src/views/module_application/job/index.vue b/frontend/src/views/module_application/job/index.vue index 4f978809..83cda46d 100644 --- a/frontend/src/views/module_application/job/index.vue +++ b/frontend/src/views/module_application/job/index.vue @@ -25,16 +25,27 @@ - - + + - + + + + + + + + +