refactor: 重构查询参数处理逻辑,统一搜索参数处理

- 移除各模块查询schema中的自定义model_validator,统一通过search_to_dict处理搜索参数
- 为需要的字段添加json_schema_extra标记查询操作类型
- 重构base_crud的条件解析逻辑,支持直接处理普通字符串、数字类型参数
- 重构base_schema中的公共查询参数校验逻辑,简化时间范围和创建更新人参数处理
- 修复菜单查询的异步懒加载问题,添加多级预加载
- 调整日志打印配置,关闭uvicorn.access重复日志
- 修复前端路由跳转路径错误
- 调整弹窗宽度适配内容
- 修复测试环境限流器未注册问题
- 清理无用的导入和废弃函数
This commit is contained in:
zhangtao
2026-07-16 01:19:03 +08:00
parent 3f7d5aa4b9
commit f74b5f277e
63 changed files with 217 additions and 490 deletions
@@ -38,7 +38,7 @@ async def get_session_detail_controller(
async def get_session_list_controller( async def get_session_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_ai:chat:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[ChatSessionQueryParam, Query(description="查询参数")], search: Annotated[ChatSessionQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
service = ChatService(auth) service = ChatService(auth)
result_dict = await service.page( result_dict = await service.page(
+3 -8
View File
@@ -1,8 +1,7 @@
from typing import Any from typing import Any
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, TenantByQueryParam, UserByQueryParam from app.core.base_schema import BaseQueryParam, TenantByQueryParam, UserByQueryParam
@@ -57,13 +56,9 @@ class ChatSessionMessageSchema(BaseModel):
class ChatSessionQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class ChatSessionQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""会话查询参数""" """会话查询参数"""
title: str | tuple[str, str] | None = Field(None, description="会话标题") title: str | None = Field(None, description="会话标题")
@model_validator(mode="after")
def validate_query_params(self) -> "ChatSessionQueryParam":
if isinstance(self.title, str):
self.title = (QueueEnum.like.value, self.title)
return self
class AiChatRequestSchema(BaseModel): class AiChatRequestSchema(BaseModel):
@@ -21,7 +21,7 @@ GenRouter = APIRouter(route_class=OperationLogRoute, prefix="/gencode", tags=["
async def gen_table_list_controller( async def gen_table_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_generator:gencode:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_generator:gencode:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[GenTableQueryParam, Query(description="查询参数")], search: Annotated[GenTableQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
order_by = [{"created_time": "desc"}] order_by = [{"created_time": "desc"}]
@@ -40,7 +40,7 @@ async def gen_table_list_controller(
async def get_gen_db_table_list_controller( async def get_gen_db_table_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_generator:dblist:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_generator:dblist:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[GenTableQueryParam, Query(description="查询参数")], search: Annotated[GenTableQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await GenTableService(auth, db).get_gen_db_table_page( result_dict = await GenTableService(auth, db).get_gen_db_table_page(
@@ -1,8 +1,7 @@
import re import re
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -286,16 +285,8 @@ class GenTableQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
- 空值将被忽略,不参与过滤。 - 空值将被忽略,不参与过滤。
""" """
table_name: str | tuple[str, str] | None = Field(None, description="表名称") table_name: str | None = Field(None, description="表名称")
table_comment: str | tuple[str, str] | None = Field(None, description="表注释") table_comment: str | None = Field(None, description="表注释")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "GenTableQueryParam":
if isinstance(self.table_name, str):
self.table_name = (QueueEnum.like.value, self.table_name)
if isinstance(self.table_comment, str):
self.table_comment = (QueueEnum.like.value, self.table_comment)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -25,7 +25,7 @@ _STATS_NS = "online_stats"
async def get_online_list_controller( async def get_online_list_controller(
redis: Annotated[Redis, Depends(redis_getter)], redis: Annotated[Redis, Depends(redis_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[OnlineQueryParam, Query(description="在线用户查询参数")], search: Annotated[OnlineQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict_list = await OnlineService.get_online_list(redis=redis, search=search) result_dict_list = await OnlineService.get_online_list(redis=redis, search=search)
result_dict = await PaginationService.paginate( result_dict = await PaginationService.paginate(
@@ -1,8 +1,7 @@
from datetime import datetime from datetime import datetime
from pydantic import BaseModel, Field, model_validator from pydantic import BaseModel, Field
from app.common.enums import QueueEnum
from app.core.base_schema import SessionInfoSchema from app.core.base_schema import SessionInfoSchema
@@ -13,19 +12,9 @@ class OnlineOutSchema(SessionInfoSchema):
class OnlineQueryParam(BaseModel): class OnlineQueryParam(BaseModel):
"""在线用户查询参数""" """在线用户查询参数"""
name: str | tuple[str, str] | None = Field(None, description="登录名称") name: str | None = Field(None, description="登录名称")
ipaddr: str | tuple[str, str] | None = Field(None, description="登陆IP地址") ipaddr: str | None = Field(None, description="登陆IP地址")
login_location: str | tuple[str, str] | None = Field(None, description="登录所属地") login_location: str | None = Field(None, description="登录所属地")
@model_validator(mode="after")
def validate_query_params(self) -> "OnlineQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.ipaddr, str):
self.ipaddr = (QueueEnum.like.value, self.ipaddr)
if isinstance(self.login_location, str):
self.login_location = (QueueEnum.like.value, self.login_location)
return self
class RecentLoginItem(BaseModel): class RecentLoginItem(BaseModel):
@@ -21,7 +21,7 @@ ResourceRouter = APIRouter(route_class=OperationLogRoute, prefix="/resource", ta
async def get_directory_list_controller( async def get_directory_list_controller(
request: Request, request: Request,
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[ResourceSearchQueryParam, Query(description="资源查询参数")], search: Annotated[ResourceSearchQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict_list = await ResourceService.get_resources_list(search=search, base_url=str(request.base_url)) result_dict_list = await ResourceService.get_resources_list(search=search, base_url=str(request.base_url))
result_dict = await PaginationService.paginate( result_dict = await PaginationService.paginate(
@@ -111,7 +111,7 @@ async def create_directory_controller(
@ResourceRouter.post("/export", summary="导出资源列表", dependencies=[Security(AuthPermission(["module_monitor:resource:export"]))]) @ResourceRouter.post("/export", summary="导出资源列表", dependencies=[Security(AuthPermission(["module_monitor:resource:export"]))])
async def export_resource_list_controller( async def export_resource_list_controller(
request: Request, request: Request,
search: Annotated[ResourceSearchQueryParam, Query(description="资源查询参数")], search: Annotated[ResourceSearchQueryParam, Query()],
) -> StreamingResponse: ) -> StreamingResponse:
result_dict_list = await ResourceService.get_resources_list(search=search, base_url=str(request.base_url)) result_dict_list = await ResourceService.get_resources_list(search=search, base_url=str(request.base_url))
export_result = await ResourceService.export_resource(data_list=result_dict_list) export_result = await ResourceService.export_resource(data_list=result_dict_list)
@@ -9,9 +9,6 @@ from pydantic import (
model_validator, model_validator,
) )
from app.common.enums import QueueEnum
class ResourceItemSchema(BaseModel): class ResourceItemSchema(BaseModel):
"""资源项目模型""" """资源项目模型"""
@@ -186,13 +183,7 @@ class ResourceCreateDirSchema(BaseModel):
class ResourceSearchQueryParam(BaseModel): class ResourceSearchQueryParam(BaseModel):
"""资源搜索查询参数""" """资源搜索查询参数"""
name: str | tuple[str, str] | None = Field(None, description="搜索关键词") name: str | None = Field(None, description="搜索关键词")
path: str | tuple[str, str] | None = Field(None, description="目录路径") path: str | None = Field(None, description="目录路径")
@model_validator(mode="after")
def validate_query_params(self) -> "ResourceSearchQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.path, str):
self.path = (QueueEnum.like.value, self.path)
return self
@@ -29,7 +29,7 @@ async def invoice_apply_controller(
async def invoice_list_my_controller( async def invoice_list_my_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:invoice:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:invoice:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[InvoiceQueryParam, Query(description="发票查询参数")], search: Annotated[InvoiceQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
result = await InvoiceTenantService.list_my( result = await InvoiceTenantService.list_my(
@@ -1,6 +1,6 @@
from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic import BaseModel, ConfigDict, Field
from app.common.enums import InvoiceTypeEnum, QueueEnum from app.common.enums import InvoiceTypeEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantBySchema, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantBySchema, UserBySchema
@@ -55,16 +55,8 @@ class InvoiceOutSchema(InvoiceCreateSchema, BaseSchema, UserBySchema, TenantBySc
class InvoiceQueryParam(BaseQueryParam): class InvoiceQueryParam(BaseQueryParam):
"""发票查询参数""" """发票查询参数"""
invoice_type: InvoiceTypeEnum | tuple[str, InvoiceTypeEnum] | None = Field(None, description="发票类型") invoice_type: InvoiceTypeEnum | None = Field(None, description="发票类型")
status: int | tuple[str, int] | None = Field(None, description="状态") status: int | None = Field(None, description="状态")
tenant_id: int | tuple[str, int] | None = Field(None, description="租户ID") tenant_id: int | None = Field(None, description="租户ID")
@model_validator(mode="after")
def validate_query_params(self) -> "InvoiceQueryParam":
if isinstance(self.invoice_type, InvoiceTypeEnum):
self.invoice_type = (QueueEnum.eq.value, self.invoice_type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
if isinstance(self.tenant_id, int):
self.tenant_id = (QueueEnum.eq.value, self.tenant_id)
return self
@@ -24,7 +24,7 @@ _MENU_NS = "menu"
async def get_menu_tree_controller( async def get_menu_tree_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:menu:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:menu:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
search: Annotated[MenuQueryParam, Query(description="菜单查询参数")], search: Annotated[MenuQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
order_by = [{"order": "asc"}] order_by = [{"order": "asc"}]
result_dict_tree = await MenuService(auth, db).tree(search=search, order_by=order_by) result_dict_tree = await MenuService(auth, db).tree(search=search, order_by=order_by)
@@ -2,7 +2,6 @@ from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema from app.core.base_schema import BaseQueryParam, BaseSchema
from app.core.validator import menu_request_validator from app.core.validator import menu_request_validator
@@ -202,42 +201,21 @@ class MenuTreeOutSchema(MenuOutSchema):
class MenuQueryParam(BaseQueryParam): class MenuQueryParam(BaseQueryParam):
"""菜单管理查询参数(菜单为平台级资源,无用户归属)""" """菜单管理查询参数(菜单为平台级资源,无用户归属)"""
name: str | tuple[str, str] | None = Field(None, description="菜单名称") name: str | None = Field(None, description="菜单名称")
route_path: str | tuple[str, str] | None = Field(None, description="路由地址") route_path: str | None = Field(None, description="路由地址")
component_path: str | tuple[str, str] | None = Field(None, description="组件路径") component_path: str | None = Field(None, description="组件路径")
type: int | tuple[str, int] | None = Field(None, description="菜单类型(1:目录 2:菜单 3:按钮 4:外链)") type: int | None = Field(None, description="菜单类型(1:目录 2:菜单 3:按钮 4:外链)")
permission: str | tuple[str, str] | None = Field(None, description="权限标识") permission: str | None = Field(None, description="权限标识")
description: str | tuple[str, str] | None = Field(None, description="描述") description: str | None = Field(None, description="描述")
status: int | tuple[str, int] | None = Field(None, description="是否启用") status: int | None = Field(None, description="是否启用")
menu_client: Literal["pc", "app"] | None = Field( client: str | None = Field(
None, None,
description="管理端 Tab:pc=桌面端菜单 app=移动端菜单;不传则不过滤终端", description="终端(pc:桌面端菜单 app:移动端菜单);不传则不过滤终端",
) )
scope: str | tuple[str, str] | None = Field( scope: str | None = Field(
None, None,
description="菜单范围过滤:tenant=仅租户可用菜单", description="菜单范围过滤:tenant=仅租户可用菜单",
json_schema_extra={"q": "eq"},
) )
client: str | tuple[str, str] | None = Field(None)
@model_validator(mode="after")
def validate_query_params(self) -> "MenuQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.route_path, str):
self.route_path = (QueueEnum.like.value, self.route_path)
if isinstance(self.component_path, str):
self.component_path = (QueueEnum.like.value, self.component_path)
if isinstance(self.permission, str):
self.permission = (QueueEnum.like.value, self.permission)
if isinstance(self.description, str):
self.description = (QueueEnum.like.value, self.description)
if isinstance(self.type, int):
self.type = (QueueEnum.eq.value, self.type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
if self.menu_client in ("pc", "app"):
self.client = (QueueEnum.eq.value, self.menu_client)
self.menu_client = None
if isinstance(self.scope, str):
self.scope = (QueueEnum.eq.value, self.scope)
return self
@@ -1,6 +1,7 @@
from typing import Any from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.core.base_schema import AuthSchema, BatchSetAvailable from app.core.base_schema import AuthSchema, BatchSetAvailable
from app.core.exceptions import CustomException from app.core.exceptions import CustomException
@@ -76,7 +77,12 @@ class MenuService:
search: MenuQueryParam | None = None, search: MenuQueryParam | None = None,
order_by: list[dict] | None = None, order_by: list[dict] | None = None,
) -> list[dict]: ) -> list[dict]:
menu_list = await MenuCRUD(self.auth, self.db).tree_list(search=search_to_dict(search), order_by=order_by) # 递归预加载所有层级 children(避免 Pydantic 校验时异步懒加载失败)
from .model import MenuModel
_loader = selectinload(MenuModel.children)
for _ in range(10):
_loader = _loader.selectinload(MenuModel.children)
menu_list = await MenuCRUD(self.auth, self.db).tree_list(search=search_to_dict(search), order_by=order_by, preload=[_loader])
menu_dict_list = [MenuTreeOutSchema.model_validate(menu).model_dump() for menu in menu_list] menu_dict_list = [MenuTreeOutSchema.model_validate(menu).model_dump() for menu in menu_list]
return traversal_to_tree(menu_dict_list) return traversal_to_tree(menu_dict_list)
@@ -56,7 +56,7 @@ async def order_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:order:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:order:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[OrderQueryParam, Query(description="查询参数")], search: Annotated[OrderQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
items, total = await OrderService.get_list( items, total = await OrderService.get_list(
auth=auth, auth=auth,
@@ -3,7 +3,6 @@ from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantBySchema
@@ -103,25 +102,13 @@ class OrderOutSchema(BaseSchema, TenantBySchema):
class OrderQueryParam(BaseQueryParam): class OrderQueryParam(BaseQueryParam):
"""订单查询参数""" """订单查询参数"""
tenant_id: int | tuple[str, int] | None = Field(None, description="租户ID") tenant_id: int | None = Field(None, description="租户ID")
status: int | tuple[str, int] | None = Field(None, description="订单状态(0:待支付 1:已支付 2:已取消)") status: int | None = Field(None, description="订单状态(0:待支付 1:已支付 2:已取消)")
refund_status: int | tuple[str, int] | None = Field(None, description="退款状态(1:申请中 2:已退款 3:已驳回)") refund_status: int | None = Field(None, description="退款状态(1:申请中 2:已退款 3:已驳回)")
order_type: str | tuple[str, str] | None = Field(None, description="订单类型") order_type: str | None = Field(None, description="订单类型", json_schema_extra={"q": "eq"})
order_no: str | tuple[str, str] | None = Field(None, description="订单号") order_no: str | None = Field(None, description="订单号")
@model_validator(mode="after")
def validate_query_params(self) -> "OrderQueryParam":
if isinstance(self.tenant_id, int):
self.tenant_id = (QueueEnum.eq.value, self.tenant_id)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
if isinstance(self.refund_status, int):
self.refund_status = (QueueEnum.eq.value, self.refund_status)
if isinstance(self.order_type, str):
self.order_type = (QueueEnum.eq.value, self.order_type)
if isinstance(self.order_no, str):
self.order_no = (QueueEnum.like.value, self.order_no)
return self
class PaymentCallbackSchema(BaseModel): class PaymentCallbackSchema(BaseModel):
@@ -44,7 +44,7 @@ async def get_obj_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_package:package:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_package:package:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[PackageQueryParam, Query(description="查询参数")], search: Annotated[PackageQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await PackageService(auth, db).page( result_dict = await PackageService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -1,6 +1,5 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema from app.core.base_schema import BaseQueryParam, BaseSchema
@@ -93,19 +92,9 @@ class PackageOutSchema(PackageCreateSchema, BaseSchema):
class PackageQueryParam(BaseQueryParam): class PackageQueryParam(BaseQueryParam):
"""套餐查询参数""" """套餐查询参数"""
name: str | tuple[str, str] | None = Field(None, description="套餐名称") name: str | None = Field(None, description="套餐名称")
code: str | tuple[str, str] | None = Field(None, description="套餐编码") code: str | None = Field(None, description="套餐编码", json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "PackageQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.code, str):
self.code = (QueueEnum.like.value, self.code)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
class PackageMenuSetSchema(BaseModel): class PackageMenuSetSchema(BaseModel):
@@ -44,11 +44,12 @@ class PackageService:
search: PackageQueryParam | None = None, search: PackageQueryParam | None = None,
order_by: list[dict[str, str]] | None = None, order_by: list[dict[str, str]] | None = None,
) -> PageResultSchema[PackageOutSchema]: ) -> PageResultSchema[PackageOutSchema]:
search_dict = search_to_dict(search)
return await PackageCRUD(self.auth, self.db).page( return await PackageCRUD(self.auth, self.db).page(
offset=(page_no - 1) * page_size, offset=(page_no - 1) * page_size,
limit=page_size, limit=page_size,
order_by=order_by or [{"sort": "asc"}, {"id": "asc"}], order_by=order_by or [{"sort": "asc"}, {"id": "asc"}],
search=search_to_dict(search), search=search_dict,
out_schema=PackageOutSchema, out_schema=PackageOutSchema,
) )
@@ -54,7 +54,7 @@ async def get_obj_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:tenant:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_platform:tenant:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[TenantQueryParam, Depends()], search: Annotated[TenantQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await TenantService(auth, db).page( result_dict = await TenantService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -2,7 +2,7 @@ from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import OrderTypeEnum, QueueEnum from app.common.enums import OrderTypeEnum
from app.core.base_schema import BaseQueryParam, BaseSchema from app.core.base_schema import BaseQueryParam, BaseSchema
from app.core.validator import DateTimeStr, email_validator, mobile_validator from app.core.validator import DateTimeStr, email_validator, mobile_validator
@@ -172,19 +172,9 @@ class TenantCreateResult(BaseModel):
class TenantQueryParam(BaseQueryParam): class TenantQueryParam(BaseQueryParam):
"""租户查询参数""" """租户查询参数"""
name: str | tuple[str, str] | None = Field(None, description="租户名称") name: str | None = Field(None, description="租户名称")
code: str | tuple[str, str] | None = Field(None, description="租户编码") code: str | None = Field(None, description="租户编码", json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "TenantQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.code, str):
self.code = (QueueEnum.like.value, self.code)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
class TenantUserAddSchema(BaseModel): class TenantUserAddSchema(BaseModel):
@@ -36,13 +36,12 @@ async def create_token_controller(
result = await ApiTokenService(auth, db).create(data=data) result = await ApiTokenService(auth, db).create(data=data)
return SuccessResponse(data=result, msg="创建 token 成功") return SuccessResponse(data=result, msg="创建 token 成功")
@ApiTokenRouter.get("/list", summary="查询 token 列表", response_model=ResponseSchema[PageResultSchema[ApiTokenOutSchema]]) @ApiTokenRouter.get("/list", summary="查询 token 列表", response_model=ResponseSchema[PageResultSchema[ApiTokenOutSchema]])
async def get_token_list_controller( async def get_token_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:api_token:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:api_token:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[ApiTokenQueryParam, Query(description="查询参数")], search: Annotated[ApiTokenQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result = await ApiTokenService(auth, db).page( result = await ApiTokenService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -24,7 +24,7 @@ _DEPT_NS = "dept"
async def get_dept_tree_controller( async def get_dept_tree_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dept:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dept:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
search: Annotated[DeptQueryParam, Query(description="部门查询参数")], search: Annotated[DeptQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
order_by = [{"order": "asc"}] order_by = [{"order": "asc"}]
result_dict_tree = await DeptService(auth, db).tree(search=search, order_by=order_by) result_dict_tree = await DeptService(auth, db).tree(search=search, order_by=order_by)
@@ -1,6 +1,5 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import validate_required_code from app.core.validator import validate_required_code
@@ -62,13 +61,5 @@ class DeptTreeOutSchema(DeptOutSchema):
class DeptQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class DeptQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""部门管理查询参数""" """部门管理查询参数"""
name: str | tuple[str, str] | None = Field(None, description="部门名称") name: str | None = Field(None, description="部门名称")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "DeptQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -44,7 +44,7 @@ async def get_type_detail_controller(
async def get_type_list_controller( async def get_type_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_type:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_type:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[DictTypeQueryParam, Query(description="字典类型查询参数")], search: Annotated[DictTypeQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await DictTypeService(auth, db).page( result_dict = await DictTypeService(auth, db).page(
@@ -117,7 +117,7 @@ async def batch_set_available_dict_type_controller(
@DictRouter.post("/type/export", summary="导出字典类型") @DictRouter.post("/type/export", summary="导出字典类型")
async def export_type_list_controller( async def export_type_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_type:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_type:export"]))],
search: Annotated[DictTypeQueryParam, Query(description="字典类型查询参数")], search: Annotated[DictTypeQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> StreamingResponse: ) -> StreamingResponse:
# 获取全量数据并转为dict列表 # 获取全量数据并转为dict列表
@@ -146,7 +146,7 @@ async def get_data_detail_controller(
async def get_data_list_controller( async def get_data_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_data:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_data:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[DictDataQueryParam, Query(description="字典数据查询参数")], search: Annotated[DictDataQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
order_by = [{"order": "asc"}] order_by = [{"order": "asc"}]
@@ -209,7 +209,7 @@ async def batch_set_available_dict_data_controller(
async def export_data_list_controller( async def export_data_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_data:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:dict_data:export"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[DictDataQueryParam, Query(description="字典数据查询参数")], search: Annotated[DictDataQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> StreamingResponse: ) -> StreamingResponse:
result_dict_list = await DictDataService(auth, db).get_list(search=search, order_by=page.order_by) result_dict_list = await DictDataService(auth, db).get_list(search=search, order_by=page.order_by)
@@ -8,7 +8,6 @@ from pydantic import (
model_validator, model_validator,
) )
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -81,19 +80,9 @@ class DictTypeOutSchema(DictTypeCreateSchema, BaseSchema, UserBySchema, TenantBy
class DictTypeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class DictTypeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""字典类型查询参数""" """字典类型查询参数"""
dict_name: str | tuple[str, str] | None = Field(default=None, description="字典名称", max_length=100) dict_name: str | None = Field(default=None, description="字典名称", max_length=100)
dict_type: str | tuple[str, str] | None = Field(default=None, description="字典类型", max_length=100) dict_type: str | None = Field(default=None, description="字典类型", max_length=100, json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(default=None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(default=None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "DictTypeQueryParam":
if isinstance(self.dict_name, str):
self.dict_name = (QueueEnum.like.value, self.dict_name)
if isinstance(self.dict_type, str):
self.dict_type = (QueueEnum.eq.value, self.dict_type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
class DictDataCreateSchema(BaseModel): class DictDataCreateSchema(BaseModel):
@@ -158,19 +147,7 @@ class DictDataOutSchema(DictDataCreateSchema, BaseSchema, UserBySchema, TenantBy
class DictDataQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class DictDataQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""字典数据查询参数""" """字典数据查询参数"""
dict_label: str | tuple[str, str] | None = Field(default=None, description="字典标签", max_length=100) dict_label: str | None = Field(default=None, description="字典标签", max_length=100)
dict_type: str | tuple[str, str] | None = Field(default=None, description="字典类型", max_length=100) dict_type: str | None = Field(default=None, description="字典类型", max_length=100, json_schema_extra={"q": "eq"})
dict_type_id: int | tuple[str, int] | None = Field(default=None, description="字典类型ID") dict_type_id: int | None = Field(default=None, description="字典类型ID")
status: int | tuple[str, int] | None = Field(default=None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(default=None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "DictDataQueryParam":
if isinstance(self.dict_label, str):
self.dict_label = (QueueEnum.like.value, self.dict_label)
if isinstance(self.dict_type, str):
self.dict_type = (QueueEnum.eq.value, self.dict_type)
if isinstance(self.dict_type_id, int):
self.dict_type_id = (QueueEnum.eq.value, self.dict_type_id)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -37,7 +37,7 @@ async def get_log_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:login_log:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:login_log:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[LoginLogQueryParam, Query(description="登录日志查询参数")], search: Annotated[LoginLogQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await LoginLogService(auth, db).page( result_dict = await LoginLogService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -75,7 +75,7 @@ async def get_operation_log_list_controller(
auth: Annotated[AuthSchema, Depends(get_current_user)], auth: Annotated[AuthSchema, Depends(get_current_user)],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[OperationLogQueryParam, Query(description="操作日志查询参数")], search: Annotated[OperationLogQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await OperationLogService(auth, db).page( result_dict = await OperationLogService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
+5 -19
View File
@@ -63,25 +63,11 @@ class LoginLogQueryParam(BaseQueryParam, TenantByQueryParam):
class OperationLogQueryParam(BaseQueryParam, TenantByQueryParam): class OperationLogQueryParam(BaseQueryParam, TenantByQueryParam):
"""操作日志查询参数""" """操作日志查询参数"""
request_path: str | tuple[str, str] | None = Field(None, description="请求路径") request_path: str | None = Field(None, description="请求路径")
request_method: str | tuple[str, str] | None = Field(None, description="请求方式") request_method: str | None = Field(None, description="请求方式", json_schema_extra={"q": "eq"})
username: str | tuple[str, str] | None = Field(None, description="用户名") username: str | None = Field(None, description="用户名")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:成功 1:失败)") status: int | None = Field(None, ge=0, le=1, description="状态(0:成功 1:失败)")
request_ip: str | tuple[str, str] | None = Field(None, description="请求IP") request_ip: str | None = Field(None, description="请求IP")
@model_validator(mode="after")
def validate_query_params(self) -> "OperationLogQueryParam":
if isinstance(self.request_path, str):
self.request_path = (QueueEnum.like.value, self.request_path)
if isinstance(self.request_method, str):
self.request_method = (QueueEnum.eq.value, self.request_method)
if isinstance(self.username, str):
self.username = (QueueEnum.like.value, self.username)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
if isinstance(self.request_ip, str):
self.request_ip = (QueueEnum.eq.value, self.request_ip)
return self
class OperationLogOutSchema(BaseSchema, TenantBySchema): class OperationLogOutSchema(BaseSchema, TenantBySchema):
@@ -34,7 +34,7 @@ async def get_notice_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:notice:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:notice:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[NoticeQueryParam, Query(description="公告查询参数")], search: Annotated[NoticeQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await NoticeService(auth, db).page( result_dict = await NoticeService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -6,7 +6,6 @@ from pydantic import (
model_validator, model_validator,
) )
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.utils.xss_util import sanitize_html from app.utils.xss_util import sanitize_html
@@ -63,16 +62,6 @@ class NoticeOutSchema(NoticeCreateSchema, BaseSchema, UserBySchema, TenantBySche
class NoticeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class NoticeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""公告通知查询参数""" """公告通知查询参数"""
notice_title: str | tuple[str, str] | None = Field(None, description="公告标题") notice_title: str | None = Field(None, description="公告标题")
notice_type: str | tuple[str, str] | None = Field(None, description="公告类型") notice_type: str | None = Field(None, description="公告类型", json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "NoticeQueryParam":
if isinstance(self.notice_title, str):
self.notice_title = (QueueEnum.like.value, self.notice_title)
if isinstance(self.notice_type, str):
self.notice_type = (QueueEnum.eq.value, self.notice_type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -32,7 +32,7 @@ async def get_param_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:param:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:param:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[ParamsQueryParam, Query(description="参数查询参数")], search: Annotated[ParamsQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await ParamsService(auth, db).page( result_dict = await ParamsService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -92,7 +92,7 @@ async def batch_set_status_controller(
async def export_param_list_controller( async def export_param_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:param:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:param:export"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
search: Annotated[ParamsQueryParam, Query(description="参数查询参数")], search: Annotated[ParamsQueryParam, Query()],
) -> StreamingResponse: ) -> StreamingResponse:
result_dict_list = await ParamsService(auth, db).get_list(search=search) result_dict_list = await ParamsService(auth, db).get_list(search=search)
export_data = [item.model_dump() for item in result_dict_list] export_data = [item.model_dump() for item in result_dict_list]
@@ -1,8 +1,7 @@
import re import re
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -51,19 +50,7 @@ class ParamsQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""参数管理查询参数 """参数管理查询参数
""" """
config_name: str | tuple[str, str] | None = Field(None, description="参数名称") config_name: str | None = Field(None, description="参数名称")
config_key: str | tuple[str, str] | None = Field(None, description="参数键名") config_key: str | None = Field(None, description="参数键名", json_schema_extra={"q": "eq"})
config_type: bool | tuple[str, bool] | None = Field(None, description="是否系统内置(True:是 False:否)") config_type: bool | None = Field(None, description="是否系统内置(True:是 False:否)")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "ParamsQueryParam":
if isinstance(self.config_name, str):
self.config_name = (QueueEnum.like.value, self.config_name)
if isinstance(self.config_key, str):
self.config_key = (QueueEnum.like.value, self.config_key)
if isinstance(self.config_type, bool):
self.config_type = (QueueEnum.eq.value, self.config_type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -26,7 +26,7 @@ async def get_obj_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:position:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:position:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[PositionQueryParam, Query(description="岗位查询参数")], search: Annotated[PositionQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await PositionService(auth, db).page( result_dict = await PositionService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -105,7 +105,7 @@ async def get_position_options_controller(
async def export_obj_list_controller( async def export_obj_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:position:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:position:export"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
search: Annotated[PositionQueryParam, Query(description="岗位查询参数")], search: Annotated[PositionQueryParam, Query()],
) -> StreamingResponse: ) -> StreamingResponse:
position_query_result = await PositionService(auth, db).get_list(search=search) position_query_result = await PositionService(auth, db).get_list(search=search)
position_export_result = PositionService.export_list(position_list=[item.model_dump() for item in position_query_result]) position_export_result = PositionService.export_list(position_list=[item.model_dump() for item in position_query_result])
@@ -1,6 +1,5 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -50,13 +49,5 @@ class PositionOutSchema(PositionCreateSchema, BaseSchema, UserBySchema, TenantBy
class PositionQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class PositionQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""岗位管理查询参数""" """岗位管理查询参数"""
name: str | tuple[str, str] | None = Field(None, description="岗位名称") name: str | None = Field(None, description="岗位名称")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "PositionQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -26,7 +26,7 @@ async def get_role_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:role:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:role:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[RoleQueryParam, Query(description="角色查询参数")], search: Annotated[RoleQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await RoleService(auth, db).page( result_dict = await RoleService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -116,7 +116,7 @@ async def get_role_options_controller(
async def export_role_list_controller( async def export_role_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:role:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:role:export"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
search: Annotated[RoleQueryParam, Query(description="角色查询参数")], search: Annotated[RoleQueryParam, Query()],
) -> StreamingResponse: ) -> StreamingResponse:
role_query_result = await RoleService(auth, db).get_list(search=search) role_query_result = await RoleService(auth, db).get_list(search=search)
role_export_result = RoleService.export_list(role_list=[item.model_dump() for item in role_query_result]) role_export_result = RoleService.export_list(role_list=[item.model_dump() for item in role_query_result])
@@ -8,7 +8,6 @@ from pydantic import (
from app.api.v1.module_platform.menu.schema import MenuOutSchema from app.api.v1.module_platform.menu.schema import MenuOutSchema
from app.api.v1.module_system.dept.schema import DeptOutSchema from app.api.v1.module_system.dept.schema import DeptOutSchema
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import ( from app.core.validator import (
role_permission_request_validator, role_permission_request_validator,
@@ -99,16 +98,6 @@ class RoleQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""角色管理查询参数 """角色管理查询参数
""" """
name: str | tuple[str, str] | None = Field(None, description="角色名称") name: str | None = Field(None, description="角色名称")
code: str | tuple[str, str] | None = Field(None, description="角色编码") code: str | None = Field(None, description="角色编码", json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(None, description="状态(0:启动 1:停用)") status: int | None = Field(None, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "RoleQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.code, str):
self.code = (QueueEnum.like.value, self.code)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -20,7 +20,7 @@ async def ticket_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:ticket:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:ticket:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[TicketQueryParam, Query(description="工单查询参数")], search: Annotated[TicketQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result = await TicketService(auth, db).page( result = await TicketService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -1,6 +1,6 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.common.enums import QueueEnum, TicketTypeEnum from app.common.enums import TicketTypeEnum
from app.core.base_schema import ( from app.core.base_schema import (
BaseQueryParam, BaseQueryParam,
BaseSchema, BaseSchema,
@@ -86,22 +86,10 @@ class TicketBatchSchema(BaseModel):
class TicketQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class TicketQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""工单查询参数""" """工单查询参数"""
title: str | tuple[str, str] | None = Field(None, description="工单标题") title: str | None = Field(None, description="工单标题")
ticket_type: str | tuple[str, str] | None = Field(None, description="工单类型") ticket_type: str | None = Field(None, description="工单类型", json_schema_extra={"q": "eq"})
assigned_id: int | tuple[str, int] | None = Field(None, description="处理人ID") assigned_id: int | None = Field(None, description="处理人ID")
status: int | tuple[str, int] | None = Field(None, ge=0, le=3, description="状态(0:待处理 1:处理中 2:已完成 3:已关闭)") status: int | None = Field(None, ge=0, le=3, description="状态(0:待处理 1:处理中 2:已完成 3:已关闭)")
@model_validator(mode="after")
def validate_query_params(self) -> "TicketQueryParam":
if isinstance(self.title, str):
self.title = (QueueEnum.like.value, self.title)
if isinstance(self.ticket_type, str):
self.ticket_type = (QueueEnum.like.value, self.ticket_type)
if isinstance(self.assigned_id, int):
self.assigned_id = (QueueEnum.eq.value, self.assigned_id)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
class TicketCommentCreateSchema(BaseModel): class TicketCommentCreateSchema(BaseModel):
@@ -98,7 +98,7 @@ async def get_user_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:user:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:user:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[UserQueryParam, Query(description="用户查询参数")], search: Annotated[UserQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await UserService(auth, db).page( result_dict = await UserService(auth, db).page(
page_no=page.page_no, page_no=page.page_no,
@@ -179,7 +179,7 @@ async def export_user_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:user:export"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:user:export"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[UserQueryParam, Query(description="用户查询参数")], search: Annotated[UserQueryParam, Query()],
) -> StreamingResponse: ) -> StreamingResponse:
user_list = await UserService(auth, db).get_list(search=search, order_by=page.order_by) user_list = await UserService(auth, db).get_list(search=search, order_by=page.order_by)
user_export_result = UserService.export_list(user_list=[item.model_dump() for item in user_list]) user_export_result = UserService.export_list(user_list=[item.model_dump() for item in user_list])
@@ -11,7 +11,6 @@ from pydantic import (
from app.api.v1.module_platform.menu.schema import MenuTreeOutSchema from app.api.v1.module_platform.menu.schema import MenuTreeOutSchema
from app.api.v1.module_system.role.schema import RoleOutSchema from app.api.v1.module_system.role.schema import RoleOutSchema
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, CommonSchema, CoreUserSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, CommonSchema, CoreUserSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import email_validator, mobile_validator from app.core.validator import email_validator, mobile_validator
@@ -262,29 +261,13 @@ class UserQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
- 业务字段:用户名、名称、手机号、邮箱、部门、状态 - 业务字段:用户名、名称、手机号、邮箱、部门、状态
""" """
username: str | tuple[str, str] | None = Field(None, description="用户名") username: str | None = Field(None, description="用户名")
name: str | tuple[str, str] | None = Field(None, description="名称") name: str | None = Field(None, description="名称")
mobile: str | tuple[str, str] | None = Field(None, description="手机号", pattern=r"^1[3-9]\d{9}$") mobile: str | None = Field(None, description="手机号", pattern=r"^1[3-9]\d{9}$")
email: str | tuple[str, str] | None = Field( email: str | None = Field(
None, None,
description="邮箱", description="邮箱",
pattern=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$", pattern=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$",
) )
dept_id: int | tuple[str, int] | None = Field(None, description="部门ID") dept_id: int | None = Field(None, description="部门ID")
status: int | tuple[str, int] | None = Field(None, description="是否可用") status: int | None = Field(None, description="是否可用")
@model_validator(mode="after")
def validate_query_params(self) -> "UserQueryParam":
if isinstance(self.username, str):
self.username = (QueueEnum.like.value, self.username)
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.mobile, str):
self.mobile = (QueueEnum.like.value, self.mobile)
if isinstance(self.email, str):
self.email = (QueueEnum.like.value, self.email)
if isinstance(self.dept_id, int):
self.dept_id = (QueueEnum.eq.value, self.dept_id)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -26,7 +26,7 @@ async def get_version_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:version:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_system:version:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[VersionQueryParam, Query(description="查询参数")], search: Annotated[VersionQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
service = VersionService(auth, db) service = VersionService(auth, db)
result = await service.page(page_no=page.page_no, page_size=page.page_size, search=search) result = await service.page(page_no=page.page_no, page_size=page.page_size, search=search)
@@ -100,7 +100,7 @@ async def remove_job_controller(
async def get_job_log_list_controller( async def get_job_log_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:job:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[JobQueryParam, Query(description="查询参数")], search: Annotated[JobQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await JobService(auth, db).get_job_log_page( result_dict = await JobService(auth, db).get_job_log_page(
@@ -3,10 +3,8 @@ from pydantic import (
ConfigDict, ConfigDict,
Field, Field,
field_validator, field_validator,
model_validator,
) )
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -61,19 +59,9 @@ class JobOutSchema(JobCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
class JobQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class JobQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""执行日志查询参数""" """执行日志查询参数"""
job_id: str | tuple[str, str] | None = Field(None, description="任务ID") job_id: str | None = Field(None, description="任务ID")
job_name: str | tuple[str, str] | None = Field(None, description="任务名称") job_name: str | None = Field(None, description="任务名称")
trigger_type: str | tuple[str, str] | None = Field(None, description="触发方式") trigger_type: str | None = Field(None, description="触发方式", json_schema_extra={"q": "eq"})
status: int | tuple[str, int] | None = Field(None, ge=0, le=5, description="执行状态(0:待执行 1:执行中 2:成功 3:失败 4:超时 5:已取消)") status: int | None = Field(None, ge=0, le=5, description="执行状态(0:待执行 1:执行中 2:成功 3:失败 4:超时 5:已取消)")
@model_validator(mode="after")
def validate_query_params(self) -> "JobQueryParam":
if self.job_id is not None:
self.job_id = (QueueEnum.eq.value, str(self.job_id))
if isinstance(self.job_name, str):
self.job_name = (QueueEnum.like.value, self.job_name)
if isinstance(self.trigger_type, str):
self.trigger_type = (QueueEnum.eq.value, self.trigger_type)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
@@ -40,7 +40,7 @@ async def get_obj_detail_controller(
async def get_obj_list_controller( async def get_obj_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:cronjob:node:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[NodeQueryParam, Query(description="查询参数")], search: Annotated[NodeQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
service = NodeService(auth, db) service = NodeService(auth, db)
@@ -8,7 +8,6 @@ from pydantic import (
model_validator, model_validator,
) )
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import datetime_validator from app.core.validator import datetime_validator
@@ -72,16 +71,8 @@ class NodeOutSchema(NodeCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
class NodeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class NodeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""节点查询参数""" """节点查询参数"""
name: str | tuple[str, str] | None = Field(None, description="节点名称") name: str | None = Field(None, description="节点名称")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "NodeQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
class NodeExecuteSchema(BaseModel): class NodeExecuteSchema(BaseModel):
@@ -30,7 +30,7 @@ async def get_workflow_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:flow:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[WorkflowQueryParam, Query(description="查询参数")], search: Annotated[WorkflowQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
result_dict = await WorkflowService(auth, db).get_workflow_page( result_dict = await WorkflowService(auth, db).get_workflow_page(
page_no=page.page_no, page_no=page.page_no,
@@ -3,7 +3,6 @@ from typing import Any
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import DateTimeStr from app.core.validator import DateTimeStr
@@ -94,16 +93,8 @@ class WorkflowOutSchema(BaseSchema, UserBySchema, TenantBySchema):
class WorkflowQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class WorkflowQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""工作流查询""" """工作流查询"""
name: str | tuple[str, str] | None = Field(None, description="流程名称") name: str | None = Field(None, description="流程名称")
code: str | tuple[str, str] | None = Field(None, description="流程编码") code: str | None = Field(None, description="流程编码")
@model_validator(mode="after")
def validate_query_params(self) -> "WorkflowQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.code, str):
self.code = (QueueEnum.like.value, self.code)
return self
class WorkflowExecuteSchema(BaseModel): class WorkflowExecuteSchema(BaseModel):
@@ -41,7 +41,7 @@ async def get_workflow_node_type_list_controller(
auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:nodes:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["module_task:workflow:nodes:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[WorkflowNodeTypeQueryParam, Query(description="查询参数")], search: Annotated[WorkflowNodeTypeQueryParam, Query()],
) -> JSONResponse: ) -> JSONResponse:
service = WorkflowNodeTypeService(auth, db) service = WorkflowNodeTypeService(auth, db)
result_dict = await service.get_page( result_dict = await service.get_page(
@@ -2,7 +2,6 @@ import re
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
@@ -65,22 +64,10 @@ class WorkflowNodeTypeOutSchema(WorkflowNodeTypeCreateSchema, BaseSchema, UserBy
class WorkflowNodeTypeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class WorkflowNodeTypeQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""查询""" """查询"""
name: str | tuple[str, str] | None = Field(None, description="名称") name: str | None = Field(None, description="名称")
code: str | tuple[str, str] | None = Field(None, description="编码") code: str | None = Field(None, description="编码", json_schema_extra={"q": "eq"})
category: str | tuple[str, str] | None = Field(None, description="分类") category: str | None = Field(None, description="分类", json_schema_extra={"q": "eq"})
is_active: bool | tuple[str, bool] | None = Field(None, description="是否启用") is_active: bool | None = Field(None, description="是否启用")
status: int | tuple[str, int] | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)") status: int | None = Field(None, ge=0, le=1, description="状态(0:启动 1:停用)")
@model_validator(mode="after")
def validate_query_params(self) -> "WorkflowNodeTypeQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.code, str):
self.code = (QueueEnum.like.value, self.code)
if isinstance(self.category, str):
self.category = (QueueEnum.eq.value, self.category)
if isinstance(self.is_active, bool):
self.is_active = (QueueEnum.eq.value, self.is_active)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
+1 -1
View File
@@ -111,8 +111,8 @@ class SchedulerUtil:
scheduler.resume() scheduler.resume()
# 注册系统级定时任务 # 注册系统级定时任务
from app.api.v1.module_platform.tenant.service import TenantService
from app.api.v1.module_platform.order.service import OrderService from app.api.v1.module_platform.order.service import OrderService
from app.api.v1.module_platform.tenant.service import TenantService
from app.api.v1.module_system.log.service import OperationLogService from app.api.v1.module_system.log.service import OperationLogService
cls.register_system_job( cls.register_system_job(
+21 -11
View File
@@ -496,6 +496,12 @@ class CRUDBase[ModelType: ModelMixin, CreateSchemaType, UpdateSchemaType]:
attr = getattr(self.model, key) attr = getattr(self.model, key)
if isinstance(value, tuple): if isinstance(value, tuple):
conditions.extend(self._resolve_condition(attr, value)) conditions.extend(self._resolve_condition(attr, value))
elif isinstance(value, str):
conditions.append(attr.like(f"%{value}%"))
elif isinstance(value, (int, bool)):
conditions.append(attr == value)
elif isinstance(value, list) and len(value) == 2:
conditions.append(attr.between(value[0], value[1]))
else: else:
conditions.append(attr == value) conditions.append(attr == value)
return conditions return conditions
@@ -571,22 +577,26 @@ class CRUDBase[ModelType: ModelMixin, CreateSchemaType, UpdateSchemaType]:
返回: 返回:
- 预加载选项列表 - 预加载选项列表
""" """
options: list[Any] = []
model_loader_options = getattr(self.model, "__loader_options__", []) model_loader_options = getattr(self.model, "__loader_options__", [])
all_preloads: set[str | Any] = set(model_loader_options) if preload == []:
return []
# 收集所有需要预加载的关系名
names: set[str] = set(model_loader_options)
if preload: if preload:
for opt in preload: for opt in preload:
if isinstance(opt, str): if isinstance(opt, str):
all_preloads.add(opt) names.add(opt)
elif preload == []:
all_preloads = set()
for opt in all_preloads: # 字符串名 → selectinload
if isinstance(opt, str): options: list[Any] = []
if hasattr(self.model, opt): for name in names:
options.append(selectinload(getattr(self.model, opt))) if hasattr(self.model, name):
else: options.append(selectinload(getattr(self.model, name)))
options.append(opt)
# 非字符串预加载项直接追加(如递归 selectinload)
if preload:
options.extend(opt for opt in preload if not isinstance(opt, str))
return options return options
+5 -29
View File
@@ -178,53 +178,29 @@ class PaginationQueryParam(BaseModel):
class BaseQueryParam(BaseModel): class BaseQueryParam(BaseModel):
"""created_time + updated_time —— 子类自动继承""" """created_time + updated_time —— 子类自动继承"""
created_time: list[DateTimeStr] | tuple[str, tuple[DateTimeStr, DateTimeStr]] | None = Field( created_time: list[DateTimeStr] | None = Field(
None, None,
description="创建时间范围", description="创建时间范围",
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"], examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
) )
updated_time: list[DateTimeStr] | tuple[str, tuple[DateTimeStr, DateTimeStr]] | None = Field( updated_time: list[DateTimeStr] | None = Field(
None, None,
description="更新时间范围", description="更新时间范围",
examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"], examples=["2025-01-01 00:00:00", "2025-12-31 23:59:59"],
) )
@model_validator(mode="after")
def validate_query_params(self) -> "BaseQueryParam":
ct = self.created_time
if isinstance(ct, list) and len(ct) == 2:
self.created_time = ("between", (ct[0], ct[1]))
ut = self.updated_time
if isinstance(ut, list) and len(ut) == 2:
self.updated_time = ("between", (ut[0], ut[1]))
return self
class UserByQueryParam(BaseModel): class UserByQueryParam(BaseModel):
"""created_id + updated_id —— 子类自动继承""" """created_id + updated_id —— 子类自动继承"""
created_id: int | tuple[str, int] | None = Field(None, description="创建人") created_id: int | None = Field(None, description="创建人")
updated_id: int | tuple[str, int] | None = Field(None, description="更新人") updated_id: int | None = Field(None, description="更新人")
@model_validator(mode="after")
def validate_query_params(self) -> "UserByQueryParam":
if isinstance(self.created_id, int):
self.created_id = ("eq", self.created_id)
if isinstance(self.updated_id, int):
self.updated_id = ("eq", self.updated_id)
return self
class TenantByQueryParam(BaseModel): class TenantByQueryParam(BaseModel):
"""tenant_id —— 子类自动继承""" """tenant_id —— 子类自动继承"""
tenant_id: int | tuple[str, int] | None = Field(None, description="租户ID") tenant_id: int | None = Field(None, description="租户ID")
@model_validator(mode="after")
def validate_query_params(self) -> "TenantByQueryParam":
if isinstance(self.tenant_id, int):
self.tenant_id = ("eq", self.tenant_id)
return self
class OptionSchema(BaseModel): class OptionSchema(BaseModel):
+3
View File
@@ -58,5 +58,8 @@ def setup_logger() -> None:
for name in ("apscheduler", "apscheduler.schedulers", "apscheduler.jobstores"): for name in ("apscheduler", "apscheduler.schedulers", "apscheduler.jobstores"):
logging.getLogger(name).setLevel(logging.WARNING) logging.getLogger(name).setLevel(logging.WARNING)
# uvicorn.access 日志与 RequestLogMiddleware 重复,关闭以避免重复
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
setup_logger() setup_logger()
@@ -32,7 +32,7 @@ async def get_obj_detail_controller(
async def get_obj_list_controller( async def get_obj_list_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_example:demo:query"]))], auth: Annotated[AuthSchema, Depends(AuthPermission(["module_example:demo:query"]))],
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[DemoQueryParam, Query(description="查询参数")], search: Annotated[DemoQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
service = DemoService(auth, db) service = DemoService(auth, db)
@@ -93,7 +93,7 @@ async def batch_set_available_obj_controller(
@DemoRouter.post("/export", summary="导出示例") @DemoRouter.post("/export", summary="导出示例")
async def export_obj_list_controller( async def export_obj_list_controller(
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_example:demo:export"]))], auth: Annotated[AuthSchema, Depends(AuthPermission(["module_example:demo:export"]))],
search: Annotated[DemoQueryParam, Query(description="查询参数")], search: Annotated[DemoQueryParam, Query()],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> StreamingResponse: ) -> StreamingResponse:
service = DemoService(auth, db) service = DemoService(auth, db)
@@ -6,7 +6,6 @@ from pydantic import (
model_validator, model_validator,
) )
from app.common.enums import QueueEnum
from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema from app.core.base_schema import BaseQueryParam, BaseSchema, TenantByQueryParam, TenantBySchema, UserByQueryParam, UserBySchema
from app.core.validator import DateStr, DateTimeStr, TimeStr from app.core.validator import DateStr, DateTimeStr, TimeStr
@@ -78,16 +77,8 @@ class DemoOutSchema(DemoCreateSchema, BaseSchema, UserBySchema, TenantBySchema):
class DemoQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam): class DemoQueryParam(BaseQueryParam, UserByQueryParam, TenantByQueryParam):
"""示例查询参数(演示 Mixin 继承用法)""" """示例查询参数(演示 Mixin 继承用法)"""
name: str | tuple[str, str] | None = Field(None, description="名称") name: str | None = Field(None, description="名称")
description: str | tuple[str, str] | None = Field(None, description="描述") description: str | None = Field(None, description="描述")
status: int | tuple[str, int] | None = Field(None, description="是否启用") status: int | None = Field(None, description="是否启用")
@model_validator(mode="after")
def validate_query_params(self) -> "DemoQueryParam":
if isinstance(self.name, str):
self.name = (QueueEnum.like.value, self.name)
if isinstance(self.description, str):
self.description = (QueueEnum.like.value, self.description)
if isinstance(self.status, int):
self.status = (QueueEnum.eq.value, self.status)
return self
+1 -1
View File
@@ -16,7 +16,7 @@ from app.api.v1.module_system.role.model import RoleModel
from app.api.v1.module_system.user.model import UserModel, UserRolesModel from app.api.v1.module_system.user.model import UserModel, UserRolesModel
from app.api.v1.module_system.versions.model import VersionModel from app.api.v1.module_system.versions.model import VersionModel
from app.config.path_conf import SCRIPT_DIR from app.config.path_conf import SCRIPT_DIR
from app.core.database import async_db_session, check_db, create_tables, drop_tables from app.core.database import async_db_session, check_db, create_tables
from app.core.logger import logger from app.core.logger import logger
+12 -2
View File
@@ -87,7 +87,8 @@ def search_to_dict(search: Any, default: Any = None) -> dict | None:
"""将 Pydantic 查询模型转为参数字典。 """将 Pydantic 查询模型转为参数字典。
- search 为 None 时返回 default(默认 None)。 - search 为 None 时返回 default(默认 None)。
- search 非空时调用 vars() 提取字段字典。 - search 非空时调用 model_dump() 提取字段字典,排除 None 值。
- 自动将 json_schema_extra={"q": "eq"} 标记的字符串字段转为 ("eq", value) 元组。
参数: 参数:
- search: Pydantic 查询模型或 None。 - search: Pydantic 查询模型或 None。
@@ -96,7 +97,16 @@ def search_to_dict(search: Any, default: Any = None) -> dict | None:
返回: 返回:
- dict | None: 查询参数字典或 None。 - dict | None: 查询参数字典或 None。
""" """
return vars(search) if search else default if not search:
return default
d = search.model_dump(exclude_none=True)
for field_name, field_info in search.model_fields.items():
if field_name not in d or not isinstance(d[field_name], str):
continue
q_op = (field_info.json_schema_extra or {}).get("q")
if q_op == "eq":
d[field_name] = ("eq", d[field_name])
return d
def get_parent_id_map(model_list: Sequence[DeclarativeBase]) -> dict[int, int]: def get_parent_id_map(model_list: Sequence[DeclarativeBase]) -> dict[int, int]:
-1
View File
@@ -9,7 +9,6 @@ from fastapi import FastAPI
from alembic import command from alembic import command
from app.common.enums import EnvironmentEnum from app.common.enums import EnvironmentEnum
from app.config.setting import settings from app.config.setting import settings
from app.core.logger import logger
from app.utils.banner import worship from app.utils.banner import worship
fastapiadmin_cli = typer.Typer() fastapiadmin_cli = typer.Typer()
@@ -51,7 +51,7 @@ async def get_obj_detail_controller(
) )
async def get_obj_list_controller( async def get_obj_list_controller(
page: Annotated[PaginationQueryParam, Depends()], page: Annotated[PaginationQueryParam, Depends()],
search: Annotated[{{ class_name }}QueryParam, Depends()], search: Annotated[{{ class_name }}QueryParam, Query()],
auth: Annotated[AuthSchema, Security(AuthPermission(["{{ permission_prefix }}:query"]))], auth: Annotated[AuthSchema, Security(AuthPermission(["{{ permission_prefix }}:query"]))],
db: Annotated[AsyncSession, Depends(db_getter)], db: Annotated[AsyncSession, Depends(db_getter)],
) -> JSONResponse: ) -> JSONResponse:
+1 -15
View File
@@ -4,11 +4,10 @@
from typing import List from typing import List
{% endif %} {% endif %}
from pydantic import BaseModel, ConfigDict, Field, model_validator from pydantic import BaseModel, ConfigDict, Field
{% for import_stmt in schema_import_list %} {% for import_stmt in schema_import_list %}
{{ import_stmt }} {{ import_stmt }}
{% endfor %} {% endfor %}
from app.common.enums import QueueEnum
from app.core.base_schema import BaseSchema, TenantBySchema, UserBySchema, BaseQueryParam, TenantByQueryParam, UserByQueryParam from app.core.base_schema import BaseSchema, TenantBySchema, UserBySchema, BaseQueryParam, TenantByQueryParam, UserByQueryParam
class {{ class_name }}CreateSchema(BaseModel): class {{ class_name }}CreateSchema(BaseModel):
@@ -54,16 +53,3 @@ class {{ class_name }}QueryParam(BaseQueryParam, UserByQueryParam, TenantByQuery
{{ column.column_name }}: {{ column.python_type }} | None = Field(None, description="{{ column.column_comment }}") {{ column.column_name }}: {{ column.python_type }} | None = Field(None, description="{{ column.column_comment }}")
{% endif %} {% endif %}
{% endfor %} {% endfor %}
@model_validator(mode="after")
def validate_query_params(self) -> "{{ class_name }}QueryParam":
{% for column in columns %}
{% if column.is_query and column.query_type == 'LIKE' and column.column_name not in ['created_time', 'updated_time', 'created_id', 'updated_id', 'tenant_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
if self.{{ column.column_name }}:
self.{{ column.column_name }} = (QueueEnum.like.value, self.{{ column.column_name }})
{% elif column.is_query and column.query_type == 'EQ' and column.column_name not in ['created_time', 'updated_time', 'created_id', 'updated_id', 'tenant_id', 'is_deleted', 'deleted_time', 'deleted_id'] %}
if self.{{ column.column_name }} is not None:
self.{{ column.column_name }} = (QueueEnum.eq.value, self.{{ column.column_name }})
{% endif %}
{% endfor %}
return self
+4
View File
@@ -181,6 +181,10 @@ async def _test_lifespan(app) -> AsyncGenerator[Any, None]:
await db.execute(update(UserModel).where(UserModel.username == "admin").values(password=PwdUtil.hash_password("admin123"))) await db.execute(update(UserModel).where(UserModel.username == "admin").values(password=PwdUtil.hash_password("admin123")))
await db.commit() await db.commit()
# 注册限流器(否则中间件访问 app.state.limiter 报 AttributeError)
from app.core.http_limit import limiter as _limiter
app.state.limiter = _limiter
yield yield
@@ -184,6 +184,9 @@ const onCreateEditor = (editor: IDomEditor) => {
applyCustomIcons(); applyCustomIcons();
}; };
// 图标重试定时器(提升到顶层以便 onUnmounted 访问)
let _iconTimerId: ReturnType<typeof setTimeout> | null = null;
// 应用自定义图标(带重试机制) // 应用自定义图标(带重试机制)
// //
// 注意:递归 setTimeout 用于等待 wangEditor 工具栏 DOM 渲染完成。 // 注意:递归 setTimeout 用于等待 wangEditor 工具栏 DOM 渲染完成。
@@ -192,14 +195,13 @@ const applyCustomIcons = () => {
let retryCount = 0; let retryCount = 0;
const maxRetries = 10; const maxRetries = 10;
const retryDelay = 100; const retryDelay = 100;
let timerId: ReturnType<typeof setTimeout> | null = null;
const tryApplyIcons = () => { const tryApplyIcons = () => {
const editor = editorRef.value; const editor = editorRef.value;
if (!editor) { if (!editor) {
if (retryCount < maxRetries) { if (retryCount < maxRetries) {
retryCount++; retryCount++;
timerId = setTimeout(tryApplyIcons, retryDelay); _iconTimerId = setTimeout(tryApplyIcons, retryDelay);
} }
return; return;
} }
@@ -209,7 +211,7 @@ const applyCustomIcons = () => {
if (!editorContainer) { if (!editorContainer) {
if (retryCount < maxRetries) { if (retryCount < maxRetries) {
retryCount++; retryCount++;
timerId = setTimeout(tryApplyIcons, retryDelay); _iconTimerId = setTimeout(tryApplyIcons, retryDelay);
} }
return; return;
} }
@@ -224,7 +226,7 @@ const applyCustomIcons = () => {
// 如果工具栏还没渲染完成,继续重试 // 如果工具栏还没渲染完成,继续重试
if (retryCount < maxRetries) { if (retryCount < maxRetries) {
retryCount++; retryCount++;
timerId = setTimeout(tryApplyIcons, retryDelay); _iconTimerId = setTimeout(tryApplyIcons, retryDelay);
} else { } else {
console.warn("工具栏渲染超时,无法应用自定义图标 - 编辑器实例:", editor.id); console.warn("工具栏渲染超时,无法应用自定义图标 - 编辑器实例:", editor.id);
} }
@@ -232,16 +234,16 @@ const applyCustomIcons = () => {
// 使用 requestAnimationFrame 确保在下一帧执行 // 使用 requestAnimationFrame 确保在下一帧执行
requestAnimationFrame(tryApplyIcons); requestAnimationFrame(tryApplyIcons);
// 组件卸载时清理挂起的重试定时器
onUnmounted(() => {
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
});
}; };
// 组件卸载时清理挂起的重试定时器(需在 setup 顶层注册)
onUnmounted(() => {
if (_iconTimerId !== null) {
clearTimeout(_iconTimerId);
_iconTimerId = null;
}
});
// 暴露编辑器实例和方法 // 暴露编辑器实例和方法
defineExpose({ defineExpose({
/** 获取编辑器实例 */ /** 获取编辑器实例 */
@@ -244,7 +244,7 @@
</ElForm> </ElForm>
<div class="px-5 pb-4"> <div class="px-5 pb-4">
<ElButton text size="small" @click="router.push('/module_system/log')"> <ElButton text size="small" @click="router.push('/system/log')">
<FaSvgIcon icon="ri:history-line" class="mr-1" />查看登录日志 <FaSvgIcon icon="ri:history-line" class="mr-1" />查看登录日志
</ElButton> </ElButton>
</div> </div>
@@ -44,7 +44,7 @@
<FaDialog <FaDialog
v-model="dialogVisible.visible" v-model="dialogVisible.visible"
:title="dialogVisible.title" :title="dialogVisible.title"
width="680px" width="760px"
dialog-class="crud-embed-dialog" dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog" modal-class="crud-embed-dialog"
:form-mode="dialogVisible.type" :form-mode="dialogVisible.type"