mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor(api): 重构API路由组织结构并简化模块导入
- 将系统管理、监控管理、通用、资源管理、示例、应用及AI模块路由封装为统一Router - 精简顶层路由注册逻辑,提升维护性和可读性 - 按模块重构AI、应用、通用、示例、监控、资源、系统子模块初始化文件 - 采用SQLAlchemy 2.0新语法重写工单和版本模型,兼容MySQL和PostgreSQL - 完善工单和版本模块CRUD、服务、参数校验、Schema和控制器代码 - 增加工单和版本的权限验证、日志记录及分页支持 - 统一使用pydantic的新配置类ConfigDict增强Schema定义 - 删除废弃的工单和版本旧代码,提升代码一致性和健壮性
This commit is contained in:
@@ -2,101 +2,36 @@
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .module_system.auth.controller import AuthRouter
|
||||
from .module_system.menu.controller import MenuRouter
|
||||
from .module_system.dept.controller import DeptRouter
|
||||
from .module_system.role.controller import RoleRouter
|
||||
from .module_system.user.controller import UserRouter
|
||||
from .module_system.log.controller import LogRouter
|
||||
from .module_system.position.controller import PositionRouter
|
||||
from .module_system.notice.controller import NoticeRouter
|
||||
from .module_system.params.controller import ParamsRouter
|
||||
from .module_system.dict.controller import DictRouter
|
||||
from .module_system.ticket.controller import router as TicketRouter
|
||||
from .module_system.version.controller import router as VersionRouter
|
||||
# 系统管理模块
|
||||
from .module_system import SystemRouter
|
||||
|
||||
from .module_monitor.cache.controller import CacheRouter
|
||||
from .module_monitor.job.controller import JobRouter
|
||||
from .module_monitor.online.controller import OnlineRouter
|
||||
from .module_monitor.server.controller import ServerRouter
|
||||
# 监控管理模块
|
||||
from .module_monitor import MonitorRouter
|
||||
|
||||
from .module_common.file.controller import FileRouter
|
||||
# 通用模块
|
||||
from .module_common import CommonRouter
|
||||
|
||||
from .module_resource.resource.controller import ResourceRouter
|
||||
# 资源管理模块
|
||||
from .module_resource import ResourceRouter
|
||||
|
||||
from .module_example.demo.controller import DemoRouter
|
||||
# 示例模块
|
||||
from .module_example import ExampleRouter
|
||||
|
||||
from .module_application.myapp.controller import MyAppRouter
|
||||
# 应用模块
|
||||
from .module_application import ApplicationRouter
|
||||
|
||||
from .module_ai.mcp.controller import MCPRouter
|
||||
|
||||
|
||||
# 定义路由模块映射,按模块分组
|
||||
SYSTEM_MODULES = [
|
||||
{"router": AuthRouter},
|
||||
{"router": MenuRouter},
|
||||
{"router": DeptRouter},
|
||||
{"router": RoleRouter},
|
||||
{"router": UserRouter},
|
||||
{"router": LogRouter},
|
||||
{"router": PositionRouter},
|
||||
{"router": NoticeRouter},
|
||||
{"router": ParamsRouter},
|
||||
{"router": DictRouter},
|
||||
{"router": TicketRouter},
|
||||
{"router": VersionRouter},
|
||||
]
|
||||
|
||||
MONITOR_MODULES = [
|
||||
{"router": JobRouter},
|
||||
{"router": CacheRouter},
|
||||
{"router": OnlineRouter},
|
||||
{"router": ServerRouter},
|
||||
]
|
||||
|
||||
COMMON_MODULES = [{"router": FileRouter}]
|
||||
|
||||
RESOURCE_MODULES = [{"router": ResourceRouter}]
|
||||
|
||||
EXAMPLE_MODULES = [{"router": DemoRouter}]
|
||||
|
||||
APPLICATION_MODULES = [{"router": MyAppRouter}]
|
||||
|
||||
AI_MODULES = [{"router": MCPRouter}]
|
||||
# AI模块
|
||||
from .module_ai import AIRouter
|
||||
|
||||
|
||||
# 创建主路由
|
||||
router = APIRouter()
|
||||
for module in SYSTEM_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/system"
|
||||
)
|
||||
|
||||
for module in MONITOR_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/monitor"
|
||||
)
|
||||
|
||||
for module in COMMON_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/common"
|
||||
)
|
||||
|
||||
for module in EXAMPLE_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/example"
|
||||
)
|
||||
|
||||
for module in APPLICATION_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/application"
|
||||
)
|
||||
|
||||
for module in RESOURCE_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/resource"
|
||||
)
|
||||
|
||||
for module in AI_MODULES:
|
||||
router.include_router(
|
||||
router=module["router"], prefix="/ai"
|
||||
)
|
||||
# 注册各模块路由
|
||||
router.include_router(SystemRouter)
|
||||
router.include_router(MonitorRouter)
|
||||
router.include_router(CommonRouter)
|
||||
router.include_router(ResourceRouter)
|
||||
router.include_router(ExampleRouter)
|
||||
router.include_router(ApplicationRouter)
|
||||
router.include_router(AIRouter)
|
||||
@@ -2,3 +2,13 @@
|
||||
"""
|
||||
AI模块初始化文件
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .mcp.controller import MCPRouter
|
||||
|
||||
|
||||
AIRouter = APIRouter(prefix="/ai")
|
||||
|
||||
# 包含所有子路由
|
||||
AIRouter.include_router(MCPRouter)
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .myapp.controller import MyAppRouter
|
||||
|
||||
|
||||
ApplicationRouter = APIRouter(prefix="/application")
|
||||
|
||||
# 包含所有子路由
|
||||
ApplicationRouter.include_router(MyAppRouter)
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .file.controller import FileRouter
|
||||
|
||||
|
||||
CommonRouter = APIRouter(prefix="/common")
|
||||
|
||||
# 包含所有子路由
|
||||
CommonRouter.include_router(FileRouter)
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .demo.controller import DemoRouter
|
||||
|
||||
|
||||
ExampleRouter = APIRouter(prefix="/example")
|
||||
|
||||
# 包含所有子路由
|
||||
ExampleRouter.include_router(DemoRouter)
|
||||
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .cache.controller import CacheRouter
|
||||
from .job.controller import JobRouter
|
||||
from .online.controller import OnlineRouter
|
||||
from .server.controller import ServerRouter
|
||||
|
||||
|
||||
MonitorRouter = APIRouter(prefix="/monitor")
|
||||
|
||||
# 包含所有子路由
|
||||
MonitorRouter.include_router(CacheRouter)
|
||||
MonitorRouter.include_router(JobRouter)
|
||||
MonitorRouter.include_router(OnlineRouter)
|
||||
MonitorRouter.include_router(ServerRouter)
|
||||
@@ -1,5 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .resource import ResourceRouter
|
||||
|
||||
__all__ = ['ResourceRouter']
|
||||
|
||||
ResourceRouter = APIRouter(prefix="/resource")
|
||||
|
||||
# 包含所有子路由
|
||||
ResourceRouter.include_router(ResourceRouter)
|
||||
@@ -1 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from fastapi import APIRouter
|
||||
from .auth.controller import AuthRouter
|
||||
from .user.controller import UserRouter
|
||||
from .role.controller import RoleRouter
|
||||
from .menu.controller import MenuRouter
|
||||
from .dept.controller import DeptRouter
|
||||
from .position.controller import PositionRouter
|
||||
from .dict.controller import DictRouter
|
||||
from .params.controller import ParamsRouter
|
||||
from .notice.controller import NoticeRouter
|
||||
from .log.controller import LogRouter
|
||||
from .version.controller import VersionRouter
|
||||
from .ticket.controller import TicketRouter
|
||||
|
||||
|
||||
SystemRouter = APIRouter(prefix="/system")
|
||||
|
||||
# 包含所有子路由
|
||||
SystemRouter.include_router(AuthRouter)
|
||||
SystemRouter.include_router(UserRouter)
|
||||
SystemRouter.include_router(RoleRouter)
|
||||
SystemRouter.include_router(MenuRouter)
|
||||
SystemRouter.include_router(DeptRouter)
|
||||
SystemRouter.include_router(PositionRouter)
|
||||
SystemRouter.include_router(DictRouter)
|
||||
SystemRouter.include_router(ParamsRouter)
|
||||
SystemRouter.include_router(NoticeRouter)
|
||||
SystemRouter.include_router(LogRouter)
|
||||
SystemRouter.include_router(VersionRouter)
|
||||
SystemRouter.include_router(TicketRouter)
|
||||
@@ -22,7 +22,4 @@ class PositionUpdateSchema(PositionCreateSchema):
|
||||
class PositionOutSchema(PositionCreateSchema, BaseSchema):
|
||||
"""岗位信息响应模型"""
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int = Field(..., description="主键ID")
|
||||
created_at: DateTimeStr = Field(..., description="创建时间")
|
||||
updated_at: DateTimeStr = Field(..., description="更新时间")
|
||||
...
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# 工单管理模块
|
||||
@@ -1,4 +1,71 @@
|
||||
from fastapi import APIRouter
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
router = APIRouter(prefix="/tickets", tags=["工单管理"])
|
||||
from fastapi import APIRouter, Body, Depends, Path
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.common.response import SuccessResponse
|
||||
from app.common.request import PaginationService
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.base_params import PaginationQueryParams
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.logger import logger
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .param import TicketQueryParams
|
||||
from .service import TicketService
|
||||
from .schema import TicketCreateSchema, TicketUpdateSchema
|
||||
|
||||
|
||||
TicketRouter = APIRouter(route_class=OperationLogRoute, prefix="/tickets", tags=["工单管理"])
|
||||
|
||||
|
||||
@TicketRouter.get("/detail/{id}", summary="获取工单详情", description="获取工单详情")
|
||||
async def get_ticket_detail_controller(
|
||||
id: int = Path(..., description="工单ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await TicketService.get_ticket_detail_service(auth=auth, id=id)
|
||||
logger.info(f"获取工单详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取工单详情成功")
|
||||
|
||||
|
||||
@TicketRouter.get("/list", summary="查询工单列表", description="查询工单列表")
|
||||
async def get_ticket_list_controller(
|
||||
page: PaginationQueryParams = Depends(),
|
||||
search: TicketQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict_list = await TicketService.get_ticket_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||
logger.info("查询工单列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询工单列表成功")
|
||||
|
||||
|
||||
@TicketRouter.post("/create", summary="创建工单", description="创建工单")
|
||||
async def create_ticket_controller(
|
||||
data: TicketCreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:create"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await TicketService.create_ticket_service(auth=auth, data=data)
|
||||
logger.info(f"创建工单成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建工单成功")
|
||||
|
||||
|
||||
@TicketRouter.put("/update/{id}", summary="修改工单", description="修改工单")
|
||||
async def update_ticket_controller(
|
||||
data: TicketUpdateSchema,
|
||||
id: int = Path(..., description="工单ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:update"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await TicketService.update_ticket_service(auth=auth, id=id, data=data)
|
||||
logger.info(f"修改工单成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改工单成功")
|
||||
|
||||
|
||||
@TicketRouter.delete("/delete", summary="删除工单", description="删除工单")
|
||||
async def delete_ticket_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:delete"]))
|
||||
) -> JSONResponse:
|
||||
await TicketService.delete_ticket_service(auth=auth, ids=ids)
|
||||
logger.info(f"删除工单成功: {ids}")
|
||||
return SuccessResponse(msg="删除工单成功")
|
||||
@@ -1,16 +1,56 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from app.api.v1.module_system.ticket.model import Ticket
|
||||
from app.api.v1.module_system.ticket.schema import TicketCreate, TicketUpdate
|
||||
from app.core.base_crud import CRUDBase
|
||||
from ..auth.schema import AuthSchema
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
class CRUDTicket(CRUDBase[Ticket, TicketCreate, TicketUpdate]):
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .model import TicketModel
|
||||
from .schema import TicketCreateSchema, TicketUpdateSchema
|
||||
|
||||
|
||||
class TicketCRUD(CRUDBase[TicketModel, TicketCreateSchema, TicketUpdateSchema]):
|
||||
"""工单 CRUD 操作"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""初始化工单CRUD"""
|
||||
self.auth = auth
|
||||
super().__init__(model=Ticket, auth=auth)
|
||||
|
||||
pass
|
||||
super().__init__(model=TicketModel, auth=auth)
|
||||
|
||||
async def get_by_id_crud(self, id: int) -> Optional[TicketModel]:
|
||||
"""根据id获取工单信息
|
||||
|
||||
:param id: 工单ID
|
||||
:return: 工单信息
|
||||
"""
|
||||
return await self.get(id=id)
|
||||
|
||||
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[TicketModel]:
|
||||
"""获取工单列表
|
||||
|
||||
:param search: 搜索条件
|
||||
:param order_by: 排序字段
|
||||
:return: 工单列表
|
||||
"""
|
||||
return await self.list(search=search or {}, order_by=order_by or [])
|
||||
|
||||
async def create_crud(self, data: TicketCreateSchema) -> Optional[TicketModel]:
|
||||
"""创建工单
|
||||
|
||||
:param data: 工单创建数据
|
||||
:return: 创建的工单
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_crud(self, id: int, data: TicketUpdateSchema) -> Optional[TicketModel]:
|
||||
"""更新工单
|
||||
|
||||
:param id: 工单ID
|
||||
:param data: 工单更新数据
|
||||
:return: 更新的工单
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_crud(self, ids: List[int]) -> None:
|
||||
"""批量删除工单
|
||||
|
||||
:param ids: 工单ID列表
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
@@ -1,23 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Text, DateTime, ForeignKey
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from datetime import datetime
|
||||
from app.core.base_model import MappedBase, CreatorMixin
|
||||
|
||||
class Ticket(CreatorMixin):
|
||||
"""工单模型"""
|
||||
__tablename__ = "ticket"
|
||||
|
||||
title = Column(String(255), nullable=False, comment="工单标题")
|
||||
status = Column(String(50), default="1", comment="工单状态(1:待处理 2:处理中 3:已解决 4:已关闭)")
|
||||
priority = Column(String(50), default="medium", comment="优先级(low, medium, high, urgent)")
|
||||
type = Column(String(50), comment="工单类型(bug, feature, task)")
|
||||
assignee_id = Column(Integer, ForeignKey("system_user.id"), comment="指派给用户ID")
|
||||
reporter_id = Column(Integer, ForeignKey("system_user.id"), comment="报告人ID")
|
||||
project = Column(String(100), comment="所属项目")
|
||||
version = Column(String(50), comment="版本号")
|
||||
from app.core.base_model import CreatorMixin
|
||||
|
||||
|
||||
class TicketModel(CreatorMixin):
|
||||
"""
|
||||
工单模型 - SQLAlchemy 2.0 语法
|
||||
兼容 MySQL 和 PostgreSQL
|
||||
"""
|
||||
__tablename__ = 'system_ticket'
|
||||
__table_args__ = ({'comment': '工单表'})
|
||||
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='工单标题')
|
||||
ticket_status: Mapped[str] = mapped_column(String(50), default='1', comment='工单状态(1:待处理 2:处理中 3:已解决 4:已关闭)')
|
||||
priority: Mapped[str] = mapped_column(String(50), default='medium', comment='优先级(low, medium, high, urgent)')
|
||||
type: Mapped[Optional[str]] = mapped_column(String(50), comment='工单类型(bug, feature, task)')
|
||||
assignee_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='指派给用户ID')
|
||||
reporter_id: Mapped[Optional[int]] = mapped_column(ForeignKey('system_users.id'), comment='报告人ID')
|
||||
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目')
|
||||
version: Mapped[Optional[str]] = mapped_column(String(50), comment='版本号')
|
||||
|
||||
# 关系
|
||||
assignee = relationship("UserModel", foreign_keys=[assignee_id])
|
||||
reporter = relationship("UserModel", foreign_keys=[reporter_id])
|
||||
assignee = relationship('UserModel', foreign_keys=[assignee_id])
|
||||
reporter = relationship('UserModel', foreign_keys=[reporter_id])
|
||||
@@ -5,13 +5,17 @@ from typing import Optional
|
||||
from fastapi import Query
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
|
||||
class TicketQueryParams:
|
||||
"""工单管理查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: Optional[str] = Query(None, description="工单标题"),
|
||||
status: Optional[bool] = Query(None, description="是否可用"),
|
||||
status: Optional[bool] = Query(None, description="状态"),
|
||||
ticket_status: Optional[str] = Query(None, description="工单状态"),
|
||||
priority: Optional[str] = Query(None, description="优先级"),
|
||||
creator: Optional[int] = Query(None, description="创建人"),
|
||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||
@@ -24,9 +28,11 @@ class TicketQueryParams:
|
||||
# 精确查询字段
|
||||
self.creator_id = creator
|
||||
self.status = status
|
||||
self.ticket_status = ticket_status # 使用 ticket_status 而不是 status
|
||||
self.priority = priority
|
||||
|
||||
# 时间范围查询
|
||||
if start_time and end_time:
|
||||
start_datetime = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
@@ -1,49 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.base_schema import BaseSchema
|
||||
|
||||
class TicketCreate(BaseModel):
|
||||
|
||||
class TicketCreateSchema(BaseModel):
|
||||
"""创建工单"""
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
status: Optional[str] = "open"
|
||||
priority: Optional[str] = "medium"
|
||||
type: Optional[str] = "bug"
|
||||
assignee_id: Optional[int] = None
|
||||
reporter_id: int
|
||||
project: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
title: str = Field(..., max_length=255, description='工单标题')
|
||||
description: Optional[str] = Field(default=None, description='工单描述')
|
||||
status: Optional[bool] = Field(default=True, description='1、启动 2、停止')
|
||||
ticket_status: Optional[str] = Field(default='1', description='工单处理状态(1:待处理 2:处理中 3:已解决 4:已关闭)')
|
||||
priority: Optional[str] = Field(default='medium', description='优先级(low, medium, high, urgent)')
|
||||
type: Optional[str] = Field(default='bug', description='工单类型(bug, feature, task)')
|
||||
assignee_id: Optional[int] = Field(default=None, description='指派给用户ID')
|
||||
reporter_id: int = Field(..., description='报告人ID')
|
||||
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
|
||||
version: Optional[str] = Field(default=None, max_length=50, description='版本号')
|
||||
|
||||
|
||||
class TicketUpdate(BaseModel):
|
||||
class TicketUpdateSchema(TicketCreateSchema):
|
||||
"""更新工单"""
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
priority: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
assignee_id: Optional[int] = None
|
||||
project: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
...
|
||||
|
||||
|
||||
class TicketOut(BaseModel):
|
||||
class TicketOutSchema(TicketCreateSchema, BaseSchema):
|
||||
"""工单输出"""
|
||||
id: int
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
status: str
|
||||
priority: str
|
||||
type: str
|
||||
assignee_id: Optional[int] = None
|
||||
reporter_id: int
|
||||
project: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
...
|
||||
@@ -1,12 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
from typing import Dict, List, Optional, Union
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.api.v1.module_system.ticket.crud import TicketCRUD
|
||||
from app.api.v1.module_system.ticket.schema import TicketCreate, TicketUpdate
|
||||
from app.core.exceptions import CustomException
|
||||
from .crud import TicketCRUD
|
||||
from .schema import TicketCreateSchema, TicketUpdateSchema, TicketOutSchema
|
||||
from .param import TicketQueryParams
|
||||
|
||||
|
||||
class TicketService:
|
||||
"""工单模块服务层"""
|
||||
|
||||
pass
|
||||
@classmethod
|
||||
async def get_ticket_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""获取工单详情"""
|
||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
||||
return TicketOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_ticket_list_service(cls, auth: AuthSchema, search: Optional[TicketQueryParams] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
||||
"""获取工单列表"""
|
||||
# 处理排序参数
|
||||
processed_order_by = None
|
||||
if order_by:
|
||||
if isinstance(order_by, str):
|
||||
processed_order_by = eval(order_by)
|
||||
else:
|
||||
processed_order_by = order_by
|
||||
|
||||
obj_list = await TicketCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
|
||||
return [TicketOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_ticket_service(cls, auth: AuthSchema, data: TicketCreateSchema) -> Dict:
|
||||
"""创建工单"""
|
||||
obj = await TicketCRUD(auth).create_crud(data=data)
|
||||
return TicketOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_ticket_service(cls, auth: AuthSchema, id: int, data: TicketUpdateSchema) -> Dict:
|
||||
"""更新工单"""
|
||||
# 检查工单是否存在
|
||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该工单不存在')
|
||||
|
||||
obj = await TicketCRUD(auth).update_crud(id=id, data=data)
|
||||
return TicketOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_ticket_service(cls, auth: AuthSchema, ids: List[int]) -> None:
|
||||
"""删除工单"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await TicketCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该工单不存在')
|
||||
await TicketCRUD(auth).delete_crud(ids=ids)
|
||||
@@ -1 +0,0 @@
|
||||
# 版本管理模块
|
||||
@@ -1,4 +1,71 @@
|
||||
from fastapi import APIRouter
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
router = APIRouter(prefix="/versions", tags=["版本管理"])
|
||||
from fastapi import APIRouter, Body, Depends, Path
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.common.response import SuccessResponse
|
||||
from app.common.request import PaginationService
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.core.base_params import PaginationQueryParams
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.logger import logger
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .param import VersionQueryParams
|
||||
from .service import VersionService
|
||||
from .schema import VersionCreateSchema, VersionUpdateSchema
|
||||
|
||||
|
||||
VersionRouter = APIRouter(route_class=OperationLogRoute, prefix="/versions", tags=["版本管理"])
|
||||
|
||||
|
||||
@VersionRouter.get("/detail/{id}", summary="获取版本详情", description="获取版本详情")
|
||||
async def get_version_detail_controller(
|
||||
id: int = Path(..., description="版本ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await VersionService.get_version_detail_service(auth=auth, id=id)
|
||||
logger.info(f"获取版本详情成功 {id}")
|
||||
return SuccessResponse(data=result_dict, msg="获取版本详情成功")
|
||||
|
||||
|
||||
@VersionRouter.get("/list", summary="查询版本列表", description="查询版本列表")
|
||||
async def get_version_list_controller(
|
||||
page: PaginationQueryParams = Depends(),
|
||||
search: VersionQueryParams = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
||||
) -> JSONResponse:
|
||||
result_dict_list = await VersionService.get_version_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||
logger.info("查询版本列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询版本列表成功")
|
||||
|
||||
|
||||
@VersionRouter.post("/create", summary="创建版本", description="创建版本")
|
||||
async def create_version_controller(
|
||||
data: VersionCreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:create"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await VersionService.create_version_service(auth=auth, data=data)
|
||||
logger.info(f"创建版本成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="创建版本成功")
|
||||
|
||||
|
||||
@VersionRouter.put("/update/{id}", summary="修改版本", description="修改版本")
|
||||
async def update_version_controller(
|
||||
data: VersionUpdateSchema,
|
||||
id: int = Path(..., description="版本ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:update"]))
|
||||
) -> JSONResponse:
|
||||
result_dict = await VersionService.update_version_service(auth=auth, id=id, data=data)
|
||||
logger.info(f"修改版本成功: {result_dict}")
|
||||
return SuccessResponse(data=result_dict, msg="修改版本成功")
|
||||
|
||||
|
||||
@VersionRouter.delete("/delete", summary="删除版本", description="删除版本")
|
||||
async def delete_version_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:delete"]))
|
||||
) -> JSONResponse:
|
||||
await VersionService.delete_version_service(auth=auth, ids=ids)
|
||||
logger.info(f"删除版本成功: {ids}")
|
||||
return SuccessResponse(msg="删除版本成功")
|
||||
|
||||
@@ -1,51 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.api.v1.module_system.version.model import Version
|
||||
from app.api.v1.module_system.version.schema import VersionCreate, VersionUpdate
|
||||
from .model import VersionModel
|
||||
from .schema import VersionCreateSchema, VersionUpdateSchema
|
||||
|
||||
|
||||
class VersionCRUD(CRUDBase[Version, VersionCreate, VersionUpdate]):
|
||||
class VersionCRUD(CRUDBase[VersionModel, VersionCreateSchema, VersionUpdateSchema]):
|
||||
"""版本模块数据层"""
|
||||
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""初始化版本CRUD"""
|
||||
self.auth = auth
|
||||
super().__init__(model=Version, auth=auth)
|
||||
super().__init__(model=VersionModel, auth=auth)
|
||||
|
||||
async def get_by_id_crud(self, id: int) -> Optional[Version]:
|
||||
"""
|
||||
根据id获取版本信息
|
||||
async def get_by_id_crud(self, id: int) -> Optional[VersionModel]:
|
||||
"""根据id获取版本信息
|
||||
|
||||
:param id: 版本ID
|
||||
:return: 版本信息
|
||||
"""
|
||||
return await self.get(id=id)
|
||||
|
||||
async def get_list_crud(self, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[Version]:
|
||||
"""
|
||||
获取版本列表
|
||||
async def get_list_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
|
||||
"""获取版本列表
|
||||
|
||||
:param search: 搜索条件
|
||||
:param order_by: 排序字段
|
||||
:return: 版本列表
|
||||
"""
|
||||
return await self.list(search=search, order_by=order_by)
|
||||
return await self.list(search=search or {}, order_by=order_by or [])
|
||||
|
||||
async def create_crud(self, data: VersionCreate) -> Optional[Version]:
|
||||
"""
|
||||
创建版本
|
||||
async def create_crud(self, data: VersionCreateSchema) -> Optional[VersionModel]:
|
||||
"""创建版本
|
||||
|
||||
:param data: 版本创建数据
|
||||
:return: 创建的版本
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_crud(self, id: int, data: VersionUpdate) -> Optional[Version]:
|
||||
"""
|
||||
更新版本
|
||||
async def update_crud(self, id: int, data: VersionUpdateSchema) -> Optional[VersionModel]:
|
||||
"""更新版本
|
||||
|
||||
:param id: 版本ID
|
||||
:param data: 版本更新数据
|
||||
@@ -54,25 +51,22 @@ class VersionCRUD(CRUDBase[Version, VersionCreate, VersionUpdate]):
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_crud(self, ids: List[int]) -> None:
|
||||
"""
|
||||
批量删除版本
|
||||
"""批量删除版本
|
||||
|
||||
:param ids: 版本ID列表
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
async def get_by_number_crud(self, version_number: str) -> Optional[Version]:
|
||||
"""
|
||||
根据版本号获取版本
|
||||
async def get_by_number_crud(self, version_number: str) -> Optional[VersionModel]:
|
||||
"""根据版本号获取版本
|
||||
|
||||
:param version_number: 版本号
|
||||
:return: 版本信息
|
||||
"""
|
||||
return await self.get(version_number=version_number)
|
||||
|
||||
async def get_by_status_crud(self, status: str, search: Dict = None, order_by: List[Dict[str, str]] = None) -> Sequence[Version]:
|
||||
"""
|
||||
根据状态获取版本列表
|
||||
async def get_by_status_crud(self, status: str, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None) -> Sequence[VersionModel]:
|
||||
"""根据状态获取版本列表
|
||||
|
||||
:param status: 版本状态
|
||||
:param search: 搜索条件
|
||||
@@ -81,5 +75,5 @@ class VersionCRUD(CRUDBase[Version, VersionCreate, VersionUpdate]):
|
||||
"""
|
||||
if search is None:
|
||||
search = {}
|
||||
search['status'] = status
|
||||
return await self.list(search=search, order_by=order_by)
|
||||
search['version_status'] = status
|
||||
return await self.list(search=search, order_by=order_by or [])
|
||||
@@ -1,15 +1,24 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from app.core.base_model import BaseModel
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Optional
|
||||
from sqlalchemy import String, Text, DateTime
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.base_model import CreatorMixin
|
||||
|
||||
class Version(BaseModel):
|
||||
"""版本模型"""
|
||||
__tablename__ = "version"
|
||||
|
||||
version_number = Column(String(50), nullable=False, comment="版本号")
|
||||
title = Column(String(255), nullable=False, comment="版本标题")
|
||||
release_notes = Column(Text, comment="发布说明")
|
||||
status = Column(String(50), default="draft", comment="版本状态(draft, released, archived)")
|
||||
project = Column(String(100), comment="所属项目")
|
||||
released_at = Column(DateTime, comment="发布时间")
|
||||
|
||||
class VersionModel(CreatorMixin):
|
||||
"""
|
||||
版本模型 - SQLAlchemy 2.0 语法
|
||||
兼容 MySQL 和 PostgreSQL
|
||||
"""
|
||||
__tablename__ = 'system_version'
|
||||
__table_args__ = ({'comment': '版本表'})
|
||||
|
||||
version_number: Mapped[str] = mapped_column(String(50), nullable=False, comment='版本号')
|
||||
title: Mapped[str] = mapped_column(String(255), nullable=False, comment='版本标题')
|
||||
release_notes: Mapped[Optional[str]] = mapped_column(Text, comment='发布说明')
|
||||
version_status: Mapped[str] = mapped_column(String(50), default='draft', comment='版本状态(draft, released, archived)')
|
||||
project: Mapped[Optional[str]] = mapped_column(String(100), comment='所属项目')
|
||||
released_at: Mapped[Optional[datetime]] = mapped_column(DateTime, comment='发布时间')
|
||||
@@ -5,13 +5,16 @@ from typing import Optional
|
||||
from fastapi import Query
|
||||
|
||||
from app.core.validator import DateTimeStr
|
||||
|
||||
|
||||
class VersionQueryParams:
|
||||
"""版本管理查询参数"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: Optional[str] = Query(None, description="版本标题"),
|
||||
status: Optional[bool] = Query(None, description="是否可用"),
|
||||
status: Optional[bool] = Query(None, description="状态"),
|
||||
version_status: Optional[str] = Query(None, description="版本状态"),
|
||||
creator: Optional[int] = Query(None, description="创建人"),
|
||||
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||
@@ -23,10 +26,12 @@ class VersionQueryParams:
|
||||
|
||||
# 精确查询字段
|
||||
self.creator_id = creator
|
||||
|
||||
self.status = status
|
||||
self.version_status = version_status # 使用 version_status 而不是 status
|
||||
|
||||
# 时间范围查询
|
||||
if start_time and end_time:
|
||||
start_datetime = datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||
self.created_at = ("between", (start_datetime, end_datetime))
|
||||
@@ -1,42 +1,30 @@
|
||||
from pydantic import BaseModel
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from datetime import datetime
|
||||
|
||||
from app.core.base_schema import BaseSchema
|
||||
|
||||
class VersionCreate(BaseModel):
|
||||
|
||||
class VersionCreateSchema(BaseModel):
|
||||
"""创建版本"""
|
||||
version_number: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
release_notes: Optional[str] = None
|
||||
status: Optional[str] = "draft"
|
||||
project: Optional[str] = None
|
||||
released_at: Optional[datetime] = None
|
||||
version_number: str = Field(..., max_length=50, description='版本号')
|
||||
title: str = Field(..., max_length=255, description='版本标题')
|
||||
release_notes: Optional[str] = Field(default=None, description='发布说明')
|
||||
description: Optional[str] = Field(default=None, description='工单描述')
|
||||
status: Optional[bool] = Field(default=True, description='1、启动 2、停止')
|
||||
version_status: Optional[str] = Field(default='draft', description='版本状态(draft, released, archived)')
|
||||
project: Optional[str] = Field(default=None, max_length=100, description='所属项目')
|
||||
released_at: Optional[datetime] = Field(default=None, description='发布时间')
|
||||
|
||||
|
||||
class VersionUpdate(BaseModel):
|
||||
class VersionUpdateSchema(VersionCreateSchema):
|
||||
"""更新版本"""
|
||||
version_number: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
release_notes: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
project: Optional[str] = None
|
||||
released_at: Optional[datetime] = None
|
||||
...
|
||||
|
||||
|
||||
class VersionOut(BaseModel):
|
||||
class VersionOutSchema(VersionCreateSchema, BaseSchema):
|
||||
"""版本输出"""
|
||||
id: int
|
||||
version_number: str
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
release_notes: Optional[str] = None
|
||||
status: str
|
||||
project: Optional[str] = None
|
||||
released_at: Optional[datetime] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
...
|
||||
@@ -1,6 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Dict, List, Optional, Union
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from .crud import VersionCRUD
|
||||
from .schema import VersionCreateSchema, VersionUpdateSchema, VersionOutSchema
|
||||
from .param import VersionQueryParams
|
||||
|
||||
|
||||
class VersionService:
|
||||
"""版本服务类"""
|
||||
|
||||
pass
|
||||
"""版本模块服务层"""
|
||||
|
||||
@classmethod
|
||||
async def get_version_detail_service(cls, auth: AuthSchema, id: int) -> Dict:
|
||||
"""获取版本详情"""
|
||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
||||
return VersionOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def get_version_list_service(cls, auth: AuthSchema, search: Optional[VersionQueryParams] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
||||
"""获取版本列表"""
|
||||
# 处理排序参数
|
||||
processed_order_by = None
|
||||
if order_by:
|
||||
if isinstance(order_by, str):
|
||||
processed_order_by = eval(order_by)
|
||||
else:
|
||||
processed_order_by = order_by
|
||||
|
||||
obj_list = await VersionCRUD(auth).get_list_crud(search=search.__dict__ if search else None, order_by=processed_order_by)
|
||||
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def create_version_service(cls, auth: AuthSchema, data: VersionCreateSchema) -> Dict:
|
||||
"""创建版本"""
|
||||
# 检查版本号是否已存在
|
||||
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
|
||||
if exist_obj:
|
||||
raise CustomException(msg='创建失败,版本号已存在')
|
||||
obj = await VersionCRUD(auth).create_crud(data=data)
|
||||
return VersionOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def update_version_service(cls, auth: AuthSchema, id: int, data: VersionUpdateSchema) -> Dict:
|
||||
"""更新版本"""
|
||||
# 检查版本是否存在
|
||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该版本不存在')
|
||||
|
||||
# 检查版本号是否重复(如果提供了版本号)
|
||||
if data.version_number:
|
||||
exist_obj = await VersionCRUD(auth).get_by_number_crud(version_number=data.version_number)
|
||||
if exist_obj and exist_obj.id != id:
|
||||
raise CustomException(msg='更新失败,版本号已存在')
|
||||
|
||||
obj = await VersionCRUD(auth).update_crud(id=id, data=data)
|
||||
return VersionOutSchema.model_validate(obj).model_dump()
|
||||
|
||||
@classmethod
|
||||
async def delete_version_service(cls, auth: AuthSchema, ids: List[int]) -> None:
|
||||
"""删除版本"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await VersionCRUD(auth).get_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='删除失败,该版本不存在')
|
||||
await VersionCRUD(auth).delete_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def get_version_by_status_service(cls, auth: AuthSchema, status: str, search: Optional[VersionQueryParams] = None, order_by: Optional[Union[str, List[Dict[str, str]]]] = None) -> List[Dict]:
|
||||
"""根据状态获取版本列表"""
|
||||
# 处理排序参数
|
||||
processed_order_by = None
|
||||
if order_by:
|
||||
if isinstance(order_by, str):
|
||||
processed_order_by = eval(order_by)
|
||||
else:
|
||||
processed_order_by = order_by
|
||||
|
||||
obj_list = await VersionCRUD(auth).get_by_status_crud(
|
||||
status=status,
|
||||
search=search.__dict__ if search else None,
|
||||
order_by=processed_order_by
|
||||
)
|
||||
return [VersionOutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
Reference in New Issue
Block a user