feat: 新增刷新缓存功能并优化系统初始化

refactor: 重构模型加载选项和CRUD预加载逻辑

fix: 修复日期范围查询格式问题

style: 优化前端组件样式和布局

perf: 提升字典数据更新性能

docs: 更新注释和文档说明

chore: 清理无用文件和代码

test: 更新测试用例

build: 添加vue-json-pretty依赖

ci: 优化初始化脚本和事务处理
This commit is contained in:
zhangtao
2025-10-27 02:33:50 +08:00
parent ab90f86623
commit 780e02b9de
89 changed files with 1549 additions and 914 deletions
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
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
@@ -21,42 +21,45 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
self.auth = auth
super().__init__(model=McpModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[McpModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[McpModel]:
"""
获取MCP服务器详情
参数:
- id (int): MCP服务器ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[McpModel]: MCP服务器模型实例(如果存在)
"""
return await self.get(id=id)
return await self.get(id=id, preload=preload)
async def get_by_name_crud(self, name: str) -> Optional[McpModel]:
async def get_by_name_crud(self, name: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[McpModel]:
"""
通过名称获取MCP服务器
参数:
- name (str): MCP服务器名称
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[McpModel]: MCP服务器模型实例(如果存在)
"""
return await self.get(name=name)
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) -> Sequence[McpModel]:
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]:
"""
列表查询MCP服务器
参数:
- search (Optional[Dict]): 查询参数字典
- order_by (Optional[List[Dict[str, str]]]): 排序参数列表
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[McpModel]: MCP服务器模型实例序列
"""
return await self.list(search=search or {}, order_by=order_by or [{'id': 'asc'}])
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]:
"""
@@ -93,4 +96,4 @@ class McpCRUD(CRUDBase[McpModel, McpCreateSchema, McpUpdateSchema]):
返回:
- None
"""
return await self.delete(ids=ids)
return await self.delete(ids=ids)
@@ -14,6 +14,7 @@ class McpModel(CreatorMixin):
__tablename__ = 'app_ai_mcp'
__table_args__ = ({'comment': 'MCP 服务器表'})
__loader_options__ = ["creator"]
name: Mapped[str] = mapped_column(String(50), unique=True, comment='MCP 名称')
type: Mapped[int] = mapped_column(Integer, default=0, comment='MCP 类型(0:stdio 1:sse')
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
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
@@ -21,30 +21,32 @@ 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) -> Optional[JobModel]:
async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[JobModel]:
"""
获取定时任务详情
参数:
- id (int): 定时任务ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[JobModel]: 定时任务模型,如果不存在则为None
"""
return await self.get(id=id)
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) -> Sequence[JobModel]:
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]:
"""
获取定时任务列表
参数:
- search (Optional[Dict]): 查询参数字典
- order_by (Optional[List[Dict[str, str]]]): 排序参数列表
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[JobModel]: 定时任务模型序列
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_obj_crud(self, data: JobCreateSchema) -> Optional[JobModel]:
"""
@@ -113,30 +115,32 @@ 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) -> Optional[JobLogModel]:
async def get_obj_log_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[JobLogModel]:
"""
获取定时任务日志详情
参数:
- id (int): 定时任务日志ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[JobLogModel]: 定时任务日志模型,如果不存在则为None
"""
return await self.get(id=id)
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) -> Sequence[JobLogModel]:
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]:
"""
获取定时任务日志列表
参数:
- search (Optional[Dict]): 查询参数字典
- order_by (Optional[List[Dict[str, str]]]): 排序参数列表
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[JobLogModel]: 定时任务日志模型序列
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def delete_obj_log_crud(self, ids: List[int]) -> None:
"""
@@ -14,6 +14,7 @@ class JobModel(CreatorMixin):
"""
__tablename__ = 'app_job'
__table_args__ = ({'comment': '定时任务调度表'})
__loader_options__ = ["job_logs", "creator"]
name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='', comment='任务名称')
jobstore: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='default', comment='存储器')
@@ -37,6 +38,7 @@ class JobLogModel(MappedBase):
"""
__tablename__ = 'app_job_log'
__table_args__ = ({'comment': '定时任务调度日志表'})
__loader_options__ = ["job"]
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
job_name: Mapped[str] = mapped_column(String(64),nullable=False,comment='任务名称')
@@ -52,4 +54,4 @@ class JobLogModel(MappedBase):
status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)")
create_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, default=datetime.now, comment='创建时间')
# 任务关联关系
job: Mapped[Optional["JobModel"]] = relationship(back_populates="job_logs", lazy="selectin")
job: Mapped[Optional["JobModel"]] = relationship(back_populates="job_logs", lazy="selectin")
@@ -60,10 +60,10 @@ class JobLogCreateSchema(BaseModel):
class JobLogUpdateSchema(JobLogCreateSchema):
"""定时任务调度日志表更新模型"""
...
job_log_id: Optional[int] = Field(default=None, description='任务日志ID')
id: Optional[int] = Field(default=None, description='任务日志ID')
class JobLogOutSchema(JobLogCreateSchema):
class JobLogOutSchema(JobLogUpdateSchema):
"""定时任务调度日志表响应模型"""
model_config = ConfigDict(from_attributes=True)
...
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
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
@@ -21,30 +21,32 @@ class ApplicationCRUD(CRUDBase[ApplicationModel, ApplicationCreateSchema, Applic
self.auth = auth
super().__init__(model=ApplicationModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[ApplicationModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ApplicationModel]:
"""
根据id获取应用详情
参数:
- id (int): 应用ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[ApplicationModel]: 应用详情,如果不存在则为None
"""
return await self.get(id=id)
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) -> Sequence[ApplicationModel]:
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]:
"""
列表查询应用
参数:
- search (Optional[Dict]): 查询参数,默认None
- order_by (Optional[List[Dict[str, str]]]): 排序参数,默认None
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[ApplicationModel]: 应用列表
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_crud(self, data: ApplicationCreateSchema) -> Optional[ApplicationModel]:
"""
@@ -13,6 +13,7 @@ class ApplicationModel(CreatorMixin):
__tablename__ = 'app_myapp'
__table_args__ = ({'comment': '应用系统表'})
__loader_options__ = ["creator"]
# 基本信息(必备字段)
name: Mapped[str] = mapped_column(String(64), nullable=False, comment='应用名称', unique=True)
@@ -24,5 +25,4 @@ class ApplicationModel(CreatorMixin):
access_url: Mapped[str] = mapped_column(String(500), nullable=False, comment='访问地址')
# 外观展示
icon_url: Mapped[str] = mapped_column(String(300), nullable=True, comment='应用图标URL')
icon_url: Mapped[str] = mapped_column(String(300), nullable=True, comment='应用图标URL')
@@ -5,7 +5,6 @@ from fastapi.responses import JSONResponse, StreamingResponse
import urllib.parse
from app.common.response import StreamResponse, SuccessResponse
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
from app.core.base_params import PaginationQueryParam
from app.core.dependencies import AuthPermission
@@ -59,8 +58,14 @@ async def get_obj_list_controller(
返回:
- JSONResponse: 包含示例列表分页信息的JSON响应
"""
result_dict_list = await DemoService.list_service(auth=auth, search=search, order_by=page.order_by)
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
# 使用数据库分页而不是应用层分页
result_dict = await DemoService.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
)
logger.info("查询示例列表成功")
return SuccessResponse(data=result_dict, msg="查询示例列表成功")
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
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 DemoModel
from .schema import DemoCreateSchema, DemoUpdateSchema
from .schema import DemoCreateSchema, DemoUpdateSchema, DemoOutSchema
class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]):
@@ -20,30 +20,32 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]):
"""
super().__init__(model=DemoModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[DemoModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DemoModel]:
"""
详情
参数:
- id (int): 示例ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[DemoModel]: 示例模型实例或None
"""
return await self.get(id=id)
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) -> Sequence[DemoModel]:
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]:
"""
列表查询
参数:
- search (Optional[Dict]): 查询参数
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[DemoModel]: 示例模型实例序列
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_crud(self, data: DemoCreateSchema) -> Optional[DemoModel]:
"""
@@ -93,4 +95,30 @@ class DemoCRUD(CRUDBase[DemoModel, DemoCreateSchema, DemoUpdateSchema]):
返回:
- None
"""
return await self.set(ids=ids, status=status)
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=DemoOutSchema,
preload=preload
)
@@ -13,6 +13,7 @@ class DemoModel(CreatorMixin):
"""
__tablename__ = 'gen_demo'
__table_args__ = ({'comment': '示例表'})
__loader_options__ = ["creator"]
name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, default='', comment='名称')
status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)")
status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)")
@@ -54,6 +54,33 @@ class DemoService:
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:
"""
分页查询
参数:
- auth (AuthSchema): 认证信息模型
- page_no (int): 页码
- page_size (int): 每页数量
- search (Optional[DemoQueryParam]): 查询参数
- 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 DemoCRUD(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: DemoCreateSchema) -> Dict:
"""
@@ -4,7 +4,7 @@ from sqlalchemy.engine.row import Row
from sqlalchemy import and_, delete, select, text, update
from sqlalchemy.orm import selectinload
from sqlglot.expressions import Expression
from typing import List, Optional, Sequence, Dict
from typing import List, Optional, Sequence, Dict, Union, Any
from app.core.logger import logger
from app.config.setting import settings
@@ -34,12 +34,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
"""
super().__init__(model=GenTableModel, auth=auth)
async def get_gen_table_by_id(self, table_id: int) -> Optional[GenTableModel]:
async def get_gen_table_by_id(self, table_id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableModel]:
"""
根据业务表ID获取需要生成的业务表信息。
参数:
- table_id (int): 业务表ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- GenTableModel | None: 业务表信息对象。
@@ -58,12 +59,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
return gen_table
async def get_gen_table_by_name(self, table_name: str) -> Optional[GenTableModel]:
async def get_gen_table_by_name(self, table_name: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableModel]:
"""
根据业务表名称获取需要生成的业务表信息。
参数:
- table_name (str): 业务表名称。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- GenTableModel | None: 业务表信息对象。
@@ -82,10 +84,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
return gen_table
async def get_gen_table_all(self) -> Sequence[GenTableModel]:
async def get_gen_table_all(self, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]:
"""
获取所有业务表信息。
参数:
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[GenTableModel]: 所有业务表信息列表。
"""
@@ -98,12 +103,13 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
return gen_table_all
async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None) -> Sequence[GenTableModel]:
async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableModel]:
"""
根据查询参数获取代码生成业务表列表信息。
参数:
- search (GenTableQueryParam | None): 查询参数对象。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[GenTableModel]: 业务表列表信息。
@@ -400,39 +406,42 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
"""
super().__init__(model=GenTableColumnModel, auth=auth)
async def get_gen_table_column_by_id(self, id: int) -> Optional[GenTableColumnModel]:
async def get_gen_table_column_by_id(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableColumnModel]:
"""根据业务表字段ID获取业务表字段信息。
参数:
- id (int): 业务表字段ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[GenTableColumnModel]: 业务表字段信息对象。
"""
return await self.get(id=id)
return await self.get(id=id, preload=preload)
async def get_gen_table_column_list_by_table_id(self, table_id: int) -> Optional[GenTableColumnModel]:
async def get_gen_table_column_list_by_table_id(self, table_id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[GenTableColumnModel]:
"""根据业务表ID获取业务表字段列表信息。
参数:
- table_id (int): 业务表ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[GenTableColumnModel]: 业务表字段列表信息对象。
"""
return await self.get(table_id=table_id)
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) -> Sequence[GenTableColumnModel]:
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]:
"""根据业务表ID查询业务表字段列表。
参数:
- table_id (int): 业务表ID。
- order_by (Optional[List[Dict[str, str]]]): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[GenTableColumnModel]: 业务表字段列表信息对象序列。
"""
return await self.list(search={"table_id": table_id}, order_by=order_by)
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]:
"""
@@ -516,17 +525,18 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
return result
async def list_gen_table_column_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[GenTableColumnModel]:
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]:
"""根据业务表字段查询业务表字段列表。
参数:
- search (Optional[Dict]): 查询参数,例如{"table_id": 1}。
- order_by (Optional[List[Dict[str, str]]]): 排序字段列表,每个元素为{"field": "字段名", "order": "asc" | "desc"}。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[GenTableColumnModel]: 业务表字段列表信息对象序列。
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_gen_table_column_crud(self, data: GenTableColumnSchema) -> Optional[GenTableColumnModel]:
"""创建业务表字段。
@@ -13,6 +13,7 @@ class GenTableModel(CreatorMixin):
"""
__tablename__ = 'gen_table'
__table_args__ = ({'comment': '代码生成表'})
__loader_options__ = ["columns", "creator"]
table_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='表名称')
table_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, default='', comment='表描述')
@@ -45,6 +46,7 @@ class GenTableColumnModel(CreatorMixin):
"""
__tablename__ = 'gen_table_column'
__table_args__ = ({'comment': '代码生成表字段'})
__loader_options__ = ["tables", "creator"]
column_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='列名称')
column_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='列描述')
+11 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -16,46 +16,49 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]):
self.auth = auth
super().__init__(model=DeptModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[DeptModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DeptModel]:
"""
根据 id 获取部门信息。
参数:
- id (int): 部门 ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- DeptModel | None: 部门信息,未找到返回 None。
"""
obj = await self.get(id=id)
obj = await self.get(id=id, preload=preload)
if not obj:
return None
return obj
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[DeptModel]:
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[DeptModel]:
"""
获取部门列表。
参数:
- search (Dict | None): 搜索条件。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[DeptModel]: 部门列表。
"""
return await self.list(search=search, order_by=order_by)
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) -> Sequence[DeptModel]:
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]:
"""
获取部门树形列表。
参数:
- search (Dict | None): 搜索条件。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[DeptModel]: 部门树形列表。
"""
return await self.tree_list(search=search, order_by=order_by, children_attr='children')
return await self.tree_list(search=search, order_by=order_by, children_attr='children', preload=preload)
async def set_available_crud(self, ids: List[int], status: bool) -> None:
"""
@@ -81,4 +84,4 @@ class DeptCRUD(CRUDBase[DeptModel, DeptCreateSchema, DeptUpdateSchema]):
- str | None: 部门名称,未找到返回 None。
"""
obj = await self.get(id=id)
return obj.name if obj else None
return obj.name if obj else None
@@ -136,6 +136,13 @@ class DeptService:
dept = await DeptCRUD(auth).get_by_id_crud(id=id)
if not dept:
raise CustomException(msg='删除失败,该部门不存在')
# 校验是否存在子级部门,存在则禁止删除
dept_list = await DeptCRUD(auth).get_list_crud()
id_map = get_child_id_map(model_list=dept_list)
for id in ids:
descendants = get_child_recursion(id=id, id_map=id_map)
if len(descendants) > 1:
raise CustomException(msg='删除失败,存在子级部门,请先删除子级部门')
await DeptCRUD(auth).delete(ids=ids)
@classmethod
+13 -9
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.dict.model import DictDataModel, DictTypeModel
@@ -21,30 +21,32 @@ 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) -> Optional[DictTypeModel]:
async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DictTypeModel]:
"""
获取数据字典类型详情
参数:
- id (int): 数据字典类型ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[DictTypeModel]: 数据字典类型模型,如果不存在则为None
"""
return await self.get(id=id)
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) -> Sequence[DictTypeModel]:
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]:
"""
获取数据字典类型列表
参数:
- search (Optional[Dict]): 查询参数,默认值为None
- order_by (Optional[List[Dict[str, str]]]): 排序参数,默认值为None
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[DictTypeModel]: 数据字典类型模型序列
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_obj_crud(self, data: DictTypeCreateSchema) -> Optional[DictTypeModel]:
"""
@@ -110,30 +112,32 @@ 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) -> Optional[DictDataModel]:
async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[DictDataModel]:
"""
获取数据字典数据详情
参数:
- id (int): 数据字典数据ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[DictDataModel]: 数据字典数据模型,如果不存在则为None
"""
return await self.get(id=id)
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) -> Sequence[DictDataModel]:
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]:
"""
获取数据字典数据列表
参数:
- search (Optional[Dict]): 查询参数,默认值为None
- order_by (Optional[List[Dict[str, str]]]): 排序参数,默认值为None
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[DictDataModel]: 数据字典数据模型序列
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_obj_crud(self, data: DictDataCreateSchema) -> Optional[DictDataModel]:
"""
@@ -14,6 +14,7 @@ class DictTypeModel(CreatorMixin):
__tablename__ = "system_dict_type"
__table_args__ = ({'comment': '字典类型表'})
__loader_options__ = ["creator"]
dict_name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment='字典名称')
dict_type: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment='字典类型')
@@ -28,6 +29,7 @@ class DictDataModel(CreatorMixin):
__tablename__ = 'system_dict_data'
__table_args__ = ({'comment': '字典数据表'})
__loader_options__ = ["creator"]
dict_sort: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment='字典排序')
dict_label: Mapped[str] = mapped_column(String(100), nullable=False, comment='字典标签')
@@ -378,27 +378,16 @@ class DictDataService:
- Dict: 数据字典数据详情字典
"""
exist_obj = await DictDataCRUD(auth).get_obj_by_id_crud(id=id)
if not exist_obj:
raise CustomException(msg='更新失败,该字典数据不存在')
exist_obj = await DictDataCRUD(auth).get(dict_label=data.dict_label)
if not exist_obj:
raise CustomException(msg='更新失败,该字典数据不存在')
if exist_obj.id != id:
raise CustomException(msg='更新失败,数据字典数据重复')
# 如果状态变更,需要同步更新字典类型状态并刷新缓存
if exist_obj.status != data.status or exist_obj.dict_type != data.dict_type:
# 如果字典类型变更,仅刷新旧类型缓存,不联动字典类型状态
if exist_obj.dict_type != data.dict_type:
dict_type = await DictTypeCRUD(auth).get(dict_type=exist_obj.dict_type)
if dict_type:
update_data = DictTypeUpdateSchema(
dict_name=dict_type.dict_name,
dict_type=dict_type.dict_type,
status=data.status,
description=dict_type.description
)
await DictTypeCRUD(auth).update_obj_crud(id=dict_type.id, data=update_data)
# 刷新Redis缓存
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{dict_type.dict_type}"
try:
dict_data_list = await DictDataCRUD(auth).get_obj_list_crud(search={'dict_type': dict_type.dict_type})
@@ -409,7 +398,7 @@ class DictDataService:
value=value,
)
except Exception as e:
logger.error(f"更新字典数据状态时刷新缓存失败: {e}")
logger.error(f"更新字典数据类型变更时刷新缓存失败: {e}")
obj = await DictDataCRUD(auth).update_obj_crud(id=id, data=data)
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{data.dict_type}"
@@ -451,6 +440,9 @@ class DictDataService:
exist_obj = await DictDataCRUD(auth).get_obj_by_id_crud(id=id)
if not exist_obj:
raise CustomException(msg=f'{id} 删除失败,该字典数据不存在')
# 新增:系统默认字典数据不允许删除(通过 is_default 判断)
if exist_obj.is_default:
raise CustomException(msg='删除失败,系统默认字典数据不允许删除')
# 删除Redis缓存
redis_key = f"{RedisInitKeyConfig.SYSTEM_DICT.key}:{exist_obj.dict_type}"
try:
+7 -6
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -32,28 +32,29 @@ class OperationLogCRUD(CRUDBase[OperationLogModel, OperationLogCreateSchema, Ope
"""
return await self.create(data=data)
async def get_by_id_crud(self, id: int) -> Optional[OperationLogModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[OperationLogModel]:
"""
根据ID获取操作日志详情。
参数:
- id (int): 操作日志ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- OperationLogModel | None: 操作日志记录。
"""
return await self.get(id=id)
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) -> Sequence[OperationLogModel]:
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]:
"""
获取操作日志列表。
参数:
- search (Dict | None): 搜索条件字典。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[OperationLogModel]: 操作日志列表。
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
@@ -13,6 +13,7 @@ class OperationLogModel(CreatorMixin):
"""
__tablename__ = "system_log"
__table_args__ = ({'comment': '系统日志表'})
__loader_options__ = ["creator"]
type: Mapped[int] = mapped_column(Integer, comment="日志类型(1登录日志 2操作日志)")
request_path: Mapped[str] = mapped_column(String(255), comment="请求路径")
+11 -8
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -16,46 +16,49 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]):
self.auth = auth
super().__init__(model=MenuModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[MenuModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[MenuModel]:
"""
根据 id 获取菜单信息。
参数:
- id (int): 菜单 ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- MenuModel | None: 菜单信息,未找到返回 None。
"""
obj = await self.get(id=id)
obj = await self.get(id=id, preload=preload)
if not obj:
return None
return obj
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[MenuModel]:
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[MenuModel]:
"""
获取菜单列表。
参数:
- search (Dict | None): 搜索条件。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[MenuModel]: 菜单列表。
"""
return await self.list(search=search, order_by=order_by)
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) -> Sequence[MenuModel]:
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]:
"""
获取菜单树形列表。
参数:
- search (Dict | None): 搜索条件。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[MenuModel]: 菜单树形列表。
"""
return await self.tree_list(search=search, order_by=order_by, children_attr='children')
return await self.tree_list(search=search, order_by=order_by, children_attr='children', preload=preload)
async def set_available_crud(self, ids: List[int], status: bool) -> None:
"""
@@ -68,4 +71,4 @@ class MenuCRUD(CRUDBase[MenuModel, MenuCreateSchema, MenuUpdateSchema]):
返回:
- None
"""
await self.set(ids=ids, status=status)
await self.set(ids=ids, status=status)
@@ -4,13 +4,16 @@
定义系统菜单相关数据模型
"""
from typing import Optional, List
from typing import Optional, List, TYPE_CHECKING
from sqlalchemy import Boolean, String, Integer, JSON, ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.core.base_model import ModelMixin
if TYPE_CHECKING:
from app.api.v1.module_system.role.model import RoleModel
class MenuModel(ModelMixin):
"""
@@ -24,6 +27,8 @@ class MenuModel(ModelMixin):
"""
__tablename__ = "system_menu"
__table_args__ = ({'comment': '菜单表'})
__loader_options__ = ["roles"]
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
name: Mapped[str] = mapped_column(String(50), nullable=False, comment='菜单名称', unique=True)
type: Mapped[int] = mapped_column(Integer, nullable=False, default=2, comment='菜单类型(1:目录 2:菜单 3:按钮/权限 4:链接)')
@@ -44,7 +49,7 @@ class MenuModel(ModelMixin):
parent_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey('system_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID')
parent: Mapped[Optional['MenuModel']] = relationship(back_populates='children', remote_side=[id], uselist=False)
children: Mapped[Optional[List['MenuModel']]] = relationship(back_populates='parent')
children: Mapped[Optional[List['MenuModel']]] = relationship(back_populates='parent', order_by="MenuModel.order")
# 角色关联关系
roles: Mapped[List["RoleModel"]] = relationship(secondary="system_role_menus", back_populates="menus", lazy="selectin")
@@ -137,6 +137,13 @@ class MenuService:
menu = await MenuCRUD(auth).get_by_id_crud(id=id)
if not menu:
raise CustomException(msg='删除失败,该菜单不存在')
# 校验是否存在子级菜单,存在则禁止删除
menu_list = await MenuCRUD(auth).get_list_crud()
id_map = get_child_id_map(model_list=menu_list)
for id in ids:
descendants = get_child_recursion(id=id, id_map=id_map)
if len(descendants) > 1:
raise CustomException(msg='删除失败,存在子级菜单,请先删除子级菜单')
await MenuCRUD(auth).delete(ids=ids)
@classmethod
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -21,30 +21,32 @@ class NoticeCRUD(CRUDBase[NoticeModel, NoticeCreateSchema, NoticeUpdateSchema]):
self.auth = auth
super().__init__(model=NoticeModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[NoticeModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[NoticeModel]:
"""
根据ID获取公告详情。
参数:
- id (int): 公告ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[NoticeModel]: 公告模型实例。
"""
return await self.get(id=id)
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) -> Sequence[NoticeModel]:
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]:
"""
获取公告列表。
参数:
- search (Optional[Dict]): 查询参数。
- order_by (Optional[List[Dict[str, str]]]): 排序参数。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[NoticeModel]: 公告模型实例列表。
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_crud(self, data: NoticeCreateSchema) -> Optional[NoticeModel]:
"""
@@ -22,6 +22,7 @@ class NoticeModel(CreatorMixin):
"""
__tablename__ = "system_notice"
__table_args__ = ({'comment': '通知公告表'})
__loader_options__ = ["creator"]
notice_title: Mapped[str] = mapped_column(String(50), nullable=False, comment='公告标题')
notice_type: Mapped[str] = mapped_column(String(50), nullable=False, comment='公告类型(1通知 2公告)')
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -21,42 +21,45 @@ 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) -> Optional[ParamsModel]:
async def get_obj_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ParamsModel]:
"""
获取配置管理型详情
参数:
- id (int): 配置管理型ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[ParamsModel]: 配置管理型模型实例
"""
return await self.get(id=id)
return await self.get(id=id, preload=preload)
async def get_obj_by_key_crud(self, key: str) -> Optional[ParamsModel]:
async def get_obj_by_key_crud(self, key: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[ParamsModel]:
"""
根据key获取配置管理型详情
参数:
- key (str): 配置管理型key
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Optional[ParamsModel]: 配置管理型模型实例
"""
return await self.get(config_key=key)
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) -> Sequence[ParamsModel]:
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]:
"""
获取配置管理型列表
参数:
- search (Dict | None): 查询参数对象。
- order_by (List[Dict[str, str]] | None): 排序参数列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[ParamsModel]: 配置管理型模型实例列表
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def create_obj_crud(self, data: ParamsCreateSchema) -> Optional[ParamsModel]:
"""
@@ -93,4 +96,4 @@ class ParamsCRUD(CRUDBase[ParamsModel, ParamsCreateSchema, ParamsUpdateSchema]):
返回:
- None
"""
return await self.delete(ids=ids)
return await self.delete(ids=ids)
@@ -13,6 +13,7 @@ class ParamsModel(CreatorMixin):
"""
__tablename__ = "system_param"
__table_args__ = ({'comment': '系统参数表'})
__loader_options__ = ["creator"]
# 基础字段
config_name: Mapped[str] = mapped_column(String(500), nullable=False, unique=True, comment='参数名称')
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Optional, Sequence
from typing import Dict, List, Optional, Sequence, Union, Any
from app.core.base_crud import CRUDBase
from ..auth.schema import AuthSchema
@@ -21,30 +21,32 @@ class PositionCRUD(CRUDBase[PositionModel, PositionCreateSchema, PositionUpdateS
self.auth = auth
super().__init__(model=PositionModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[PositionModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[PositionModel]:
"""
根据 id 获取岗位信息。
参数:
- id (int): 岗位 ID。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- PositionModel | None: 岗位信息,未找到返回 None。
"""
return await self.get(id=id)
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) -> Sequence[PositionModel]:
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]:
"""
获取岗位列表。
参数:
- search (Dict | None): 搜索条件。
- order_by (List[Dict[str, str]] | None): 排序字段列表。
- preload (Optional[List[Union[str, Any]]]): 预加载关系,未提供时使用模型默认项
返回:
- Sequence[PositionModel]: 岗位列表。
"""
return await self.list(search=search, order_by=order_by)
return await self.list(search=search, order_by=order_by, preload=preload)
async def set_available_crud(self, ids: List[int], status: bool) -> None:
"""
@@ -74,4 +76,4 @@ class PositionCRUD(CRUDBase[PositionModel, PositionCreateSchema, PositionUpdateS
obj = await self.get(id=id)
if obj:
position_names.append(obj.name)
return position_names
return position_names
@@ -18,6 +18,7 @@ class PositionModel(CreatorMixin):
"""
__tablename__ = "system_position"
__table_args__ = ({'comment': '岗位表'})
__loader_options__ = ["creator"]
name: Mapped[str] = mapped_column(String(40), nullable=False, unique=True, comment="岗位名称")
order: Mapped[int] = mapped_column(Integer, nullable=False, default=1, comment="显示排序")
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Sequence, Optional
from typing import Dict, List, Sequence, Optional, Union, Any
from app.core.base_crud import CRUDBase
from .model import RoleModel
@@ -23,30 +23,32 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]):
self.auth = auth
super().__init__(model=RoleModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[RoleModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[RoleModel]:
"""
根据id获取角色信息
参数:
- id (int): 角色ID
- preload (Optional[List[Union[str, Any]]]): 预加载选项
返回:
- Optional[RoleModel]: 角色模型对象
"""
return await self.get(id=id)
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) -> Sequence[RoleModel]:
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]:
"""
获取角色列表
参数:
- search (Optional[Dict]): 查询参数
- order_by (Optional[List[Dict[str, str]]]): 排序参数
- preload (Optional[List[Union[str, Any]]]): 预加载选项
返回:
- Sequence[RoleModel]: 角色模型对象列表
"""
return await self.list(search=search, order_by=order_by)
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:
"""
@@ -112,4 +114,4 @@ class RoleCRUD(CRUDBase[RoleModel, RoleCreateSchema, RoleUpdateSchema]):
返回:
- None
"""
await self.set(ids=ids, status=status)
await self.set(ids=ids, status=status)
@@ -4,13 +4,18 @@
定义角色相关数据模型和关联表
"""
from typing import Optional, List
from typing import Optional, List, TYPE_CHECKING
from sqlalchemy import Boolean, String, Integer, ForeignKey
from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.core.base_model import MappedBase, CreatorMixin
if TYPE_CHECKING:
from app.api.v1.module_system.menu.model import MenuModel
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.user.model import UserModel
class RoleMenusModel(MappedBase):
"""
@@ -71,6 +76,7 @@ class RoleModel(CreatorMixin):
"""
__tablename__ = "system_role"
__table_args__ = ({'comment': '角色表'})
__loader_options__ = ["menus", "depts", "creator"]
name: Mapped[str] = mapped_column(String(40), nullable=False, unique=True, comment="角色名称")
code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True, unique=True, comment="角色编码")
@@ -78,7 +84,7 @@ class RoleModel(CreatorMixin):
status: Mapped[bool] = mapped_column(Boolean(), default=True, nullable=False, comment="是否启用(True:启用 False:禁用)")
data_scope: Mapped[int] = mapped_column(Integer, nullable=False, default=1, comment="数据权限范围")
menus: Mapped[List["MenuModel"]] = relationship(secondary="system_role_menus", back_populates="roles", lazy="selectin")
menus: Mapped[List["MenuModel"]] = relationship(secondary="system_role_menus", back_populates="roles", lazy="selectin", order_by="MenuModel.order")
depts: Mapped[List["DeptModel"]] = relationship(secondary="system_role_depts", back_populates="roles", lazy="selectin")
users: Mapped[List["UserModel"]] = relationship(secondary="system_user_roles", back_populates="roles", lazy="selectin")
+29 -12
View File
@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
from typing import Dict, List, Sequence, Optional
from typing import Dict, List, Optional, Sequence, Union, Any
from datetime import datetime
from app.core.base_crud import CRUDBase
from .model import UserModel
from .schema import UserCreateSchema, UserForgetPasswordSchema, UserUpdateSchema
@@ -26,54 +25,73 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
self.auth = auth
super().__init__(model=UserModel, auth=auth)
async def get_by_id_crud(self, id: int) -> Optional[UserModel]:
async def get_by_id_crud(self, id: int, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
"""
根据id获取用户信息
参数:
- id (int): 用户ID
- preload (Optional[List[Union[str, Any]]]): 预加载关系未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
"""
return await self.get(id=id)
return await self.get(
preload=preload,
id=id,
)
async def get_by_username_crud(self, username: str) -> Optional[UserModel]:
async def get_by_username_crud(self, username: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
"""
根据用户名获取用户信息
参数:
- username (str): 用户名
- preload (Optional[List[Union[str, Any]]]): 预加载关系未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
"""
return await self.get(username=username)
return await self.get(
preload=preload,
username=username,
)
async def get_by_mobile_crud(self, mobile: str) -> Optional[UserModel]:
async def get_by_mobile_crud(self, mobile: str, preload: Optional[List[Union[str, Any]]] = None) -> Optional[UserModel]:
"""
根据手机号获取用户信息
参数:
- mobile (str): 手机号
- preload (Optional[List[Union[str, Any]]]): 预加载关系未提供时使用模型默认项
返回:
- Optional[UserModel]: 用户信息,如果不存在则为None
"""
return await self.get(mobile=mobile)
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) -> Sequence[UserModel]:
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]:
"""
获取用户列表
参数:
- search (Dict | None): 查询参数对象
- order_by (List[Dict[str, str]] | None): 排序参数列表
- preload (Optional[List[Union[str, Any]]]): 预加载关系未提供时使用模型默认项
返回:
Sequence[UserModel]: 用户列表
"""
return await self.list(search=search, order_by=order_by)
return await self.list(
search=search,
order_by=order_by,
preload=preload,
)
async def update_last_login_crud(self, id: int) -> Optional[UserModel]:
"""
@@ -187,5 +205,4 @@ class UserCRUD(CRUDBase[UserModel, UserCreateSchema, UserUpdateSchema]):
"""
if await self.get_by_username_crud(username=data.username):
return None
return await self.create(data=UserCreateSchema(**data.model_dump()))
return await self.create(data=UserCreateSchema(**data.model_dump()))
@@ -13,7 +13,7 @@ from sqlalchemy.orm import relationship, Mapped, mapped_column
from app.api.v1.module_system.dept.model import DeptModel
from app.api.v1.module_system.position.model import PositionModel
from app.api.v1.module_system.role.model import RoleModel
from app.core.base_model import MappedBase, CreatorMixin
from app.core.base_model import MappedBase
class UserRolesModel(MappedBase):
@@ -68,6 +68,7 @@ class UserModel(MappedBase):
"""
__tablename__ = "system_users"
__table_args__ = ({'comment': '用户表'})
__loader_options__ = ["dept", "roles", "positions", "creator"]
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True, comment='主键ID')
@@ -27,7 +27,7 @@ class UserRegisterSchema(BaseModel):
mobile: Optional[str] = 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=[2], description='角色ID')
role_ids: Optional[List[int]] = Field(default=[1], description='角色ID')
creator_id: Optional[int] = Field(default=1, description='创建人ID')
description: Optional[str] = Field(default=None, max_length=255, description="备注")
@@ -77,11 +77,6 @@ class UserService:
user_list = await UserCRUD(auth).get_list_crud(search=search.__dict__, order_by=order_by)
user_dict_list = []
for user in user_list:
if user.dept_id:
dept = await DeptCRUD(auth).get_by_id_crud(id=user.dept_id)
UserOutSchema.dept_name = dept.name if dept else None
else:
UserOutSchema.dept_name = None
user_dict = UserOutSchema.model_validate(user).model_dump()
user_dict_list.append(user_dict)
@@ -101,6 +96,9 @@ class UserService:
"""
if not data.username:
raise CustomException(msg="用户名不能为空")
# 检查是否试图创建超级管理员
if data.is_superuser:
raise CustomException(msg='不允许创建超级管理员')
# 检查用户名是否存在
user = await UserCRUD(auth).get_by_username_crud(username=data.username)
if user:
@@ -142,17 +140,34 @@ class UserService:
"""
if not data.username:
raise CustomException(msg="用户名不能为空")
# 检查是否是超级管理员
if data.is_superuser:
raise CustomException(msg='超级管理员系统唯一')
# 检查用户是否存在
user = await UserCRUD(auth).get_by_id_crud(id=id)
if not user:
raise CustomException(msg='用户不存在')
# 检查是否尝试修改超级管理员
if user.is_superuser:
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='已存在相同的用户名')
# 新增:检查手机号是否重复
if data.mobile:
exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
if exist_mobile_user and exist_mobile_user.id != id:
raise CustomException(msg='更新失败,手机号已存在')
# 新增:检查邮箱是否重复
if data.email:
exist_email_user = await UserCRUD(auth).get(email=data.email)
if exist_email_user and exist_email_user.id != id:
raise CustomException(msg='更新失败,邮箱已存在')
# 检查部门是否存在且可用
if data.dept_id:
dept = await DeptCRUD(auth).get_by_id_crud(id=data.dept_id)
if not dept:
@@ -167,7 +182,8 @@ class UserService:
# 更新用户
# user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"})
# new_user = await UserCRUD(auth).update(id=id, data=user_dict)
new_user = await UserCRUD(auth).update(id=id, data=data)
user_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids", "last_login", "password"})
new_user = await UserCRUD(auth).update(id=id, data=user_dict)
# 更新角色和岗位
if data.role_ids and len(data.role_ids) > 0:
@@ -248,7 +264,7 @@ class UserService:
# 获取菜单权限
if auth.user and auth.user.is_superuser:
# 使用树形结构查询,预加载children关系
menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'type': ('in', [1, 2, 4]), 'status': True})
menu_all = await MenuCRUD(auth).get_tree_list_crud(search={'type': ('in', [1, 2, 4]), 'status': True}, order_by=[{"order": "asc"}])
menus = [MenuOutSchema.model_validate(menu).model_dump() for menu in menu_all]
else:
@@ -263,7 +279,7 @@ class UserService:
# 使用树形结构查询,预加载children关系
menus = [
MenuOutSchema.model_validate(menu).model_dump()
for menu in await MenuCRUD(auth).get_tree_list_crud(search={'id': ('in', list(menu_ids))})
for menu in await MenuCRUD(auth).get_tree_list_crud(search={'id': ('in', list(menu_ids))}, order_by=[{"order": "asc"}])
] if menu_ids else []
user_dict["menus"] = traversal_to_tree(menus)
return user_dict
@@ -285,6 +301,18 @@ class UserService:
user = await UserCRUD(auth).get_by_id_crud(id=auth.user.id)
if not user:
raise CustomException(msg="用户不存在")
if user.is_superuser:
raise CustomException(msg="超级管理员不能修改个人信息")
# 新增:检查手机号是否重复
if data.mobile:
exist_mobile_user = await UserCRUD(auth).get_by_mobile_crud(mobile=data.mobile)
if exist_mobile_user and exist_mobile_user.id != auth.user.id:
raise CustomException(msg='更新失败,手机号已存在')
# 新增:检查邮箱是否重复
if data.email:
exist_email_user = await UserCRUD(auth).get(email=data.email)
if exist_email_user and exist_email_user.id != auth.user.id:
raise CustomException(msg='更新失败,邮箱已存在')
user_update_data = UserUpdateSchema(**data.model_dump())
new_user = await UserCRUD(auth).update(id=auth.user.id, data=user_update_data)
return UserOutSchema.model_validate(new_user).model_dump()
@@ -380,6 +408,10 @@ class UserService:
user = await UserCRUD(auth).get_by_id_crud(id=data.id)
if not user:
raise CustomException(msg="用户不存在")
# 检查是否是超级管理员
if user.is_superuser:
raise CustomException(msg="超级管理员密码不能重置")
# 更新密码
new_password_hash = PwdUtil.set_password_hash(password=data.password)
@@ -405,14 +437,10 @@ class UserService:
data.password = PwdUtil.set_password_hash(password=data.password)
data.name = data.username
data.creator_id = 1
# dict_data = data.model_dump(exclude_unset=True)
# result = await UserCRUD(auth).create(data=dict_data)
user_create_data = UserCreateSchema(**data.model_dump())
result = await UserCRUD(auth).create(data=user_create_data)
create_dict = data.model_dump(exclude_unset=True, exclude={"role_ids", "position_ids"})
result = await UserCRUD(auth).create(data=create_dict)
if data.role_ids:
await UserCRUD(auth).set_user_roles_crud(user_ids=[result.id], role_ids=data.role_ids)
# await UserCRUD(auth).set_user_positions_crud(user_ids=[result.id], position_ids=data.position_ids)
return UserOutSchema.model_validate(result).model_dump()
@classmethod
@@ -432,6 +460,11 @@ class UserService:
raise CustomException(msg="用户不存在")
if not user.status:
raise CustomException(msg="用户已停用")
# 检查是否是超级管理员
if user.is_superuser:
raise CustomException(msg="超级管理员密码不能重置")
new_password_hash = PwdUtil.set_password_hash(password=data.new_password)
new_user = await UserCRUD(auth).forget_password_crud(id=user.id, password_hash=new_password_hash)
return UserOutSchema.model_validate(new_user).model_dump()
@@ -510,6 +543,10 @@ class UserService:
# 处理用户导入
exists_user = await UserCRUD(auth).get_by_username_crud(username=user_data["username"])
if exists_user:
# 检查是否是超级管理员
if exists_user.is_superuser:
error_msgs.append(f"{count}行: 超级管理员不允许修改")
continue
if update_support:
user_update_data = UserUpdateSchema(**user_data)
await UserCRUD(auth).update(id=exists_user.id, data=user_update_data)