mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-24 05:26:58 +00:00
refactor: 完成项目大规模代码重构与依赖清理
这是一次综合性的重构更新,包含以下主要变更: 1. 升级Python版本到3.12,更新依赖配置 2. 替换旧的.j2模板为.jinja2格式,新增代码生成模板 3. 重构权限过滤策略,更新权限枚举与模型配置 4. 移除Prefect依赖,替换为自研拓扑并行执行引擎 5. 重构认证与上下文管理,拆分租户/请求上下文 6. 简化响应模型、CRUD与服务层代码 7. 清理废弃的支付网关模块,重构订单定时任务 8. 更新在线用户、监控等模块的接口与路由 9. 优化邮件模板与工具类,新增邮件模板文件 10. 修复数据库会话配置与类型提示
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
|
||||
- ``definition``: 工作流定义(画布 CRUD、发布、执行 API)
|
||||
- ``node_type``: 编排节点类型(palette / 与 task_node 分离)
|
||||
- ``engine``: Prefect DAG 执行引擎
|
||||
- ``engine``: 拓扑分层并行执行引擎
|
||||
|
||||
动态路由仍统一挂在 ``/task`` 下(见各子包 ``controller.py`` 的 ``prefix``)。
|
||||
"""
|
||||
|
||||
@@ -19,7 +19,7 @@ from .schema import (
|
||||
)
|
||||
from .service import WorkflowService
|
||||
|
||||
WorkflowRouter = APIRouter(route_class=OperationLogRoute, prefix="/workflow/definition", tags=["工作流"])
|
||||
WorkflowRouter = APIRouter(route_class=OperationLogRoute, prefix="/workflow/definition", tags=["任务调度", "工作流"])
|
||||
|
||||
|
||||
@WorkflowRouter.get(
|
||||
@@ -31,17 +31,7 @@ async def get_workflow_detail_controller(
|
||||
id: Annotated[int, Path(description="工作流ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:detail"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
根据 ID 获取工作流详情(含画布 nodes/edges)。
|
||||
|
||||
参数:
|
||||
- id (int): 工作流 ID。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为详情字典。
|
||||
"""
|
||||
result_dict = await WorkflowService.get_workflow_detail_service(auth=auth, id=id)
|
||||
result_dict = await WorkflowService(auth).get_workflow_detail(id=id)
|
||||
return SuccessResponse(data=result_dict, msg="获取工作流详情成功")
|
||||
|
||||
|
||||
@@ -55,19 +45,7 @@ async def get_workflow_list_controller(
|
||||
search: Annotated[WorkflowQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
分页查询工作流列表。
|
||||
|
||||
参数:
|
||||
- page (PaginationQueryParam): 分页与排序参数。
|
||||
- search (WorkflowQueryParam): 查询条件。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为分页结果。
|
||||
"""
|
||||
result_dict = await WorkflowService.get_workflow_page_service(
|
||||
auth=auth,
|
||||
result_dict = await WorkflowService(auth).get_workflow_page(
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
@@ -85,17 +63,7 @@ async def create_workflow_controller(
|
||||
data: WorkflowCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:create"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
创建草稿工作流。
|
||||
|
||||
参数:
|
||||
- data (WorkflowCreateSchema): 创建体。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为新建工作流。
|
||||
"""
|
||||
result_dict = await WorkflowService.create_workflow_service(auth=auth, data=data)
|
||||
result_dict = await WorkflowService(auth).create_workflow(data=data)
|
||||
return SuccessResponse(data=result_dict, msg="创建工作流成功")
|
||||
|
||||
|
||||
@@ -109,18 +77,7 @@ async def update_workflow_controller(
|
||||
data: WorkflowUpdateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
更新工作流及画布。
|
||||
|
||||
参数:
|
||||
- id (int): 工作流 ID。
|
||||
- data (WorkflowUpdateSchema): 更新体。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为更新后的工作流。
|
||||
"""
|
||||
result_dict = await WorkflowService.update_workflow_service(auth=auth, id=id, data=data)
|
||||
result_dict = await WorkflowService(auth).update_workflow(id=id, data=data)
|
||||
return SuccessResponse(data=result_dict, msg="更新工作流成功")
|
||||
|
||||
|
||||
@@ -133,17 +90,7 @@ async def delete_workflow_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:delete"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
批量删除工作流。
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 工作流 ID 列表。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功提示响应。
|
||||
"""
|
||||
await WorkflowService.delete_workflow_service(auth=auth, ids=ids)
|
||||
await WorkflowService(auth).delete_workflow(ids=ids)
|
||||
return SuccessResponse(msg="删除工作流成功")
|
||||
|
||||
|
||||
@@ -156,17 +103,7 @@ async def publish_workflow_controller(
|
||||
id: Annotated[int, Path(description="工作流ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
校验 DAG 无环后发布工作流。
|
||||
|
||||
参数:
|
||||
- id (int): 工作流 ID。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为发布后工作流。
|
||||
"""
|
||||
result_dict = await WorkflowService.publish_workflow_service(auth=auth, id=id)
|
||||
result_dict = await WorkflowService(auth).publish_workflow(id=id)
|
||||
return SuccessResponse(data=result_dict, msg="发布工作流成功")
|
||||
|
||||
|
||||
@@ -179,15 +116,5 @@ async def execute_workflow_controller(
|
||||
body: WorkflowExecuteSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:definition:execute"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
使用 Prefect 按拓扑顺序执行已发布工作流。
|
||||
|
||||
参数:
|
||||
- body (WorkflowExecuteSchema): 工作流 ID 与变量等。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为执行结果摘要。
|
||||
"""
|
||||
result_dict = await WorkflowService.execute_workflow_service(auth=auth, body=body)
|
||||
result_dict = await WorkflowService(auth).execute_workflow(body=body)
|
||||
return SuccessResponse(data=result_dict, msg="执行工作流完成")
|
||||
|
||||
@@ -6,7 +6,7 @@ from app.core.base_model import ModelMixin, TenantMixin, UserMixin
|
||||
|
||||
class WorkflowModel(ModelMixin, TenantMixin, UserMixin):
|
||||
"""
|
||||
工作流定义:Vue Flow 画布序列化 + Prefect 运行时执行
|
||||
工作流定义:Vue Flow 画布序列化 + 拓扑分层并行执行
|
||||
"""
|
||||
|
||||
__tablename__: str = "task_workflow"
|
||||
|
||||
@@ -4,8 +4,8 @@ from typing import Any
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
|
||||
from ..engine.prefect_engine import run_prefect_workflow_sync, utc_now_iso, validate_workflow_graph
|
||||
from ..node_type.crud import WorkflowNodeTypeCRUD
|
||||
from ..handlers.workflow_engine import run_workflow_sync, utc_now_iso, validate_workflow_graph
|
||||
from ..nodes.crud import WorkflowNodeTypeCRUD
|
||||
from .crud import WorkflowCRUD
|
||||
from .schema import (
|
||||
WorkflowCreateSchema,
|
||||
@@ -18,83 +18,43 @@ from .schema import (
|
||||
|
||||
|
||||
class WorkflowService:
|
||||
"""工作流:画布存储 + 发布校验 + Prefect 执行"""
|
||||
"""工作流:画布存储 + 发布校验 + 分层并行执行"""
|
||||
|
||||
@classmethod
|
||||
def _out(cls, obj: Any) -> WorkflowOutSchema:
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
self.auth = auth
|
||||
|
||||
def _out(self, obj: Any) -> WorkflowOutSchema:
|
||||
return WorkflowOutSchema.model_validate(obj)
|
||||
|
||||
@classmethod
|
||||
async def get_workflow_detail_service(cls, auth: AuthSchema, id: int) -> WorkflowOutSchema:
|
||||
"""
|
||||
获取工作流详情。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- id (int): 工作流 ID。
|
||||
|
||||
返回:
|
||||
- dict: 序列化后的工作流详情。
|
||||
|
||||
异常:
|
||||
- CustomException: 不存在时抛出。
|
||||
"""
|
||||
obj = await WorkflowCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
async def get_workflow_detail(self, id: int) -> WorkflowOutSchema:
|
||||
obj = await WorkflowCRUD(self.auth).get_obj_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="工作流不存在")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def get_workflow_list_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
async def get_workflow_list(
|
||||
self,
|
||||
search: WorkflowQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[WorkflowOutSchema]:
|
||||
"""
|
||||
获取工作流列表(非分页)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- search (WorkflowQueryParam | None): 查询条件。
|
||||
- order_by (list[dict[str, str]] | None): 排序。
|
||||
|
||||
返回:
|
||||
- list[dict]: 工作流字典列表。
|
||||
"""
|
||||
if order_by is None:
|
||||
order_by = [{"updated_time": "desc"}]
|
||||
obj_list = await WorkflowCRUD(auth).get_obj_list_crud(
|
||||
obj_list = await WorkflowCRUD(self.auth).get_obj_list_crud(
|
||||
search=vars(search) if search else None,
|
||||
order_by=order_by,
|
||||
)
|
||||
return [cls._out(o) for o in obj_list]
|
||||
return [self._out(o) for o in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def get_workflow_page_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
async def get_workflow_page(
|
||||
self,
|
||||
page_no: int,
|
||||
page_size: int,
|
||||
search: WorkflowQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
分页查询工作流(数据库 OFFSET/LIMIT)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- page_no (int): 页码。
|
||||
- page_size (int): 每页条数。
|
||||
- search (WorkflowQueryParam | None): 查询条件。
|
||||
- order_by (list[dict[str, str]] | None): 排序。
|
||||
|
||||
返回:
|
||||
- dict: 分页结果(items 已 JSON 友好序列化)。
|
||||
"""
|
||||
offset = (page_no - 1) * page_size
|
||||
order = order_by or [{"updated_time": "desc"}]
|
||||
result = await WorkflowCRUD(auth).page(
|
||||
result = await WorkflowCRUD(self.auth).page(
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order,
|
||||
@@ -104,92 +64,35 @@ class WorkflowService:
|
||||
result.items = [WorkflowOutSchema.model_validate(item).model_dump(mode="json") for item in result.items]
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def create_workflow_service(cls, auth: AuthSchema, data: WorkflowCreateSchema) -> WorkflowOutSchema:
|
||||
"""
|
||||
创建工作流草稿。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- data (WorkflowCreateSchema): 创建体。
|
||||
|
||||
返回:
|
||||
- dict: 新建工作流字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 编码重复或创建失败。
|
||||
"""
|
||||
exist = await WorkflowCRUD(auth).get(code=data.code)
|
||||
async def create_workflow(self, data: WorkflowCreateSchema) -> WorkflowOutSchema:
|
||||
exist = await WorkflowCRUD(self.auth).get(code=data.code)
|
||||
if exist:
|
||||
raise CustomException(msg="流程编码已存在")
|
||||
obj = await WorkflowCRUD(auth).create_obj_crud(data=data)
|
||||
obj = await WorkflowCRUD(self.auth).create_obj_crud(data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="创建工作流失败")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def update_workflow_service(cls, auth: AuthSchema, id: int, data: WorkflowUpdateSchema) -> WorkflowOutSchema:
|
||||
"""
|
||||
更新工作流。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- id (int): 工作流 ID。
|
||||
- data (WorkflowUpdateSchema): 更新体。
|
||||
|
||||
返回:
|
||||
- dict: 更新后工作流字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 不存在、编码冲突或更新失败。
|
||||
"""
|
||||
exist = await WorkflowCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
async def update_workflow(self, id: int, data: WorkflowUpdateSchema) -> WorkflowOutSchema:
|
||||
exist = await WorkflowCRUD(self.auth).get_obj_by_id_crud(id=id)
|
||||
if not exist:
|
||||
raise CustomException(msg="工作流不存在")
|
||||
if exist.code != data.code:
|
||||
other = await WorkflowCRUD(auth).get(code=data.code)
|
||||
other = await WorkflowCRUD(self.auth).get(code=data.code)
|
||||
if other:
|
||||
raise CustomException(msg="流程编码已存在")
|
||||
obj = await WorkflowCRUD(auth).update_obj_crud(id=id, data=data)
|
||||
obj = await WorkflowCRUD(self.auth).update_obj_crud(id=id, data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新工作流失败")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def delete_workflow_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除工作流。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- ids (list[int]): ID 列表。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: ID 为空时抛出。
|
||||
"""
|
||||
async def delete_workflow(self, ids: list[int]) -> None:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除ID不能为空")
|
||||
await WorkflowCRUD(auth).delete_obj_crud(ids=ids)
|
||||
await WorkflowCRUD(self.auth).delete_obj_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def publish_workflow_service(cls, auth: AuthSchema, id: int) -> WorkflowOutSchema:
|
||||
"""
|
||||
校验 DAG 后发布工作流。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- id (int): 工作流 ID。
|
||||
|
||||
返回:
|
||||
- dict: 发布后工作流字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 不存在、图无效或发布失败。
|
||||
"""
|
||||
obj = await WorkflowCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
async def publish_workflow(self, id: int) -> WorkflowOutSchema:
|
||||
obj = await WorkflowCRUD(self.auth).get_obj_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="工作流不存在")
|
||||
nodes = obj.nodes or []
|
||||
@@ -208,27 +111,13 @@ class WorkflowService:
|
||||
edges=obj.edges,
|
||||
workflow_status="published",
|
||||
)
|
||||
updated = await WorkflowCRUD(auth).update_obj_crud(id=id, data=data)
|
||||
updated = await WorkflowCRUD(self.auth).update_obj_crud(id=id, data=data)
|
||||
if not updated:
|
||||
raise CustomException(msg="发布失败")
|
||||
return cls._out(updated)
|
||||
return self._out(updated)
|
||||
|
||||
@classmethod
|
||||
async def execute_workflow_service(cls, auth: AuthSchema, body: WorkflowExecuteSchema) -> WorkflowExecuteResultSchema:
|
||||
"""
|
||||
执行已发布工作流(Prefect 同步入口在线程池中运行)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- body (WorkflowExecuteSchema): 工作流 ID 与变量。
|
||||
|
||||
返回:
|
||||
- dict: 执行结果摘要(成功或失败结构)。
|
||||
|
||||
异常:
|
||||
- CustomException: 未发布、缺节点、节点类型未注册等。
|
||||
"""
|
||||
obj = await WorkflowCRUD(auth).get_obj_by_id_crud(id=body.workflow_id)
|
||||
async def execute_workflow(self, body: WorkflowExecuteSchema) -> WorkflowExecuteResultSchema:
|
||||
obj = await WorkflowCRUD(self.auth).get_obj_by_id_crud(id=body.workflow_id)
|
||||
if not obj:
|
||||
raise CustomException(msg="工作流不存在")
|
||||
if obj.workflow_status != "published":
|
||||
@@ -242,7 +131,7 @@ class WorkflowService:
|
||||
codes_set = {n.get("type") for n in nodes if n.get("type")}
|
||||
code_list = list(codes_set)
|
||||
templates: dict[str, dict[str, Any]] = {}
|
||||
type_objs = await WorkflowNodeTypeCRUD(auth).get_obj_list_crud(search={"code": ("in", code_list)})
|
||||
type_objs = await WorkflowNodeTypeCRUD(self.auth).get_obj_list_crud(search={"code": ("in", code_list)})
|
||||
type_map = {t.code: t for t in type_objs}
|
||||
for code in codes_set:
|
||||
node_type = type_map.get(code)
|
||||
@@ -260,7 +149,7 @@ class WorkflowService:
|
||||
start = utc_now_iso()
|
||||
try:
|
||||
raw = await asyncio.to_thread(
|
||||
run_prefect_workflow_sync,
|
||||
run_workflow_sync,
|
||||
nodes,
|
||||
edges,
|
||||
templates,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Prefect 编排执行(DAG 校验、拓扑排序、Flow/Task)。"""
|
||||
"""工作流执行引擎(DAG 校验、拓扑排序、分层并行执行)。"""
|
||||
|
||||
from .prefect_engine import run_prefect_workflow_sync, utc_now_iso, validate_workflow_graph
|
||||
from .workflow_engine import run_workflow_sync, utc_now_iso, validate_workflow_graph
|
||||
|
||||
__all__ = [
|
||||
"run_prefect_workflow_sync",
|
||||
"run_workflow_sync",
|
||||
"utc_now_iso",
|
||||
"validate_workflow_graph",
|
||||
]
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
"""
|
||||
将 Vue Flow 画布(nodes/edges)转为 DAG,按拓扑顺序用 Prefect 编排执行。
|
||||
|
||||
画布节点 `type` 对应表 task_workflow_node_type.code(与定时任务 task_node 无关),
|
||||
执行时加载该类型的 `func` 代码块,经 SchedulerUtil._task_wrapper 运行。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections import defaultdict, deque
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from prefect import flow, task
|
||||
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
def _parse_args(args_str: str | None) -> list[Any]:
|
||||
if not args_str or not str(args_str).strip():
|
||||
return []
|
||||
return [a.strip() for a in str(args_str).split(",") if a.strip()]
|
||||
|
||||
|
||||
def _parse_kwargs(kwargs_str: str | None) -> dict[str, Any]:
|
||||
if not kwargs_str or not str(kwargs_str).strip():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(kwargs_str)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
|
||||
def validate_workflow_graph(nodes: list[dict], edges: list[dict]) -> None:
|
||||
"""
|
||||
校验画布图有效且无环。
|
||||
|
||||
参数:
|
||||
- nodes (list[dict]): 节点列表(须含 id)。
|
||||
- edges (list[dict]): 边列表(source/target)。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- ValueError: 图为空、边引用非法或存在环。
|
||||
"""
|
||||
if not nodes:
|
||||
raise ValueError("工作流至少需要一个节点")
|
||||
ids = {n["id"] for n in nodes}
|
||||
for e in edges:
|
||||
if e.get("source") not in ids or e.get("target") not in ids:
|
||||
raise ValueError("连线引用了不存在的节点")
|
||||
in_degree: dict[str, int] = dict.fromkeys(ids, 0)
|
||||
adj: dict[str, list[str]] = defaultdict(list)
|
||||
for e in edges:
|
||||
adj[e["source"]].append(e["target"])
|
||||
in_degree[e["target"]] += 1
|
||||
q: deque[str] = deque([nid for nid in ids if in_degree[nid] == 0])
|
||||
visited = 0
|
||||
while q:
|
||||
u = q.popleft()
|
||||
visited += 1
|
||||
for v in adj[u]:
|
||||
in_degree[v] -= 1
|
||||
if in_degree[v] == 0:
|
||||
q.append(v)
|
||||
if visited != len(ids):
|
||||
raise ValueError("工作流图存在环路,无法执行")
|
||||
|
||||
|
||||
def _topological_levels(nodes: list[dict], edges: list[dict]) -> list[list[dict]]:
|
||||
"""
|
||||
按拓扑层级分组节点:同一层级内节点互不依赖,可并行执行。
|
||||
|
||||
参数:
|
||||
- nodes (list[dict]): 节点列表。
|
||||
- edges (list[dict]): 边列表。
|
||||
|
||||
返回:
|
||||
- list[list[dict]]: 按层级分组的节点列表,顺序保证层间依赖。
|
||||
"""
|
||||
id_to_node = {n["id"]: n for n in nodes}
|
||||
in_degree: dict[str, int] = {n["id"]: 0 for n in nodes}
|
||||
adj: dict[str, list[str]] = defaultdict(list)
|
||||
for e in edges:
|
||||
adj[e["source"]].append(e["target"])
|
||||
in_degree[e["target"]] += 1
|
||||
levels: list[list[dict]] = []
|
||||
current = [nid for nid in in_degree if in_degree[nid] == 0]
|
||||
while current:
|
||||
levels.append([id_to_node[nid] for nid in current])
|
||||
next_level: list[str] = []
|
||||
for nid in current:
|
||||
for target in adj[nid]:
|
||||
in_degree[target] -= 1
|
||||
if in_degree[target] == 0:
|
||||
next_level.append(target)
|
||||
current = next_level
|
||||
return levels
|
||||
|
||||
|
||||
@task(name="workflow-node", retries=0)
|
||||
def prefect_node_task(
|
||||
vue_node_id: str,
|
||||
node_type_code: str,
|
||||
code_block: str,
|
||||
args_str: str | None,
|
||||
kwargs_str: str | None,
|
||||
upstream: dict[str, Any],
|
||||
flow_variables: dict[str, Any],
|
||||
) -> Any:
|
||||
"""
|
||||
单个画布节点的 Prefect Task:通过 SchedulerUtil 执行用户代码块。
|
||||
|
||||
参数:
|
||||
- vue_node_id (str): 画布节点 id。
|
||||
- node_type_code (str): 节点类型编码。
|
||||
- code_block (str): 可执行代码字符串。
|
||||
- args_str (str | None): 逗号分隔位置参数说明。
|
||||
- kwargs_str (str | None): JSON 关键字参数。
|
||||
- upstream (dict[str, Any]): 上游节点输出。
|
||||
- flow_variables (dict[str, Any]): 流程级变量。
|
||||
|
||||
返回:
|
||||
- Any: 任务执行结果。
|
||||
"""
|
||||
job_id = f"wfnode-{vue_node_id}"
|
||||
args = _parse_args(args_str)
|
||||
kw = _parse_kwargs(kwargs_str)
|
||||
kw.setdefault("upstream", upstream)
|
||||
kw.setdefault("variables", flow_variables)
|
||||
return SchedulerUtil._task_wrapper(job_id, code_block, *args, **kw)
|
||||
|
||||
|
||||
@flow(name="workflow-run", log_prints=True)
|
||||
def run_workflow_prefect_flow(
|
||||
ordered_nodes: list[dict],
|
||||
edges: list[dict],
|
||||
node_templates: dict[str, dict[str, Any]],
|
||||
flow_variables: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Prefect Flow:按拓扑层级并行提交,层间串行收集结果。
|
||||
|
||||
同层级节点互不依赖,使用 submit() 批量提交后统一收集,
|
||||
避免 .submit() → .result() 逐节点串行阻塞。
|
||||
|
||||
参数:
|
||||
- ordered_nodes (list[dict]): 已排序节点列表。
|
||||
- edges (list[dict]): 边列表。
|
||||
- node_templates (dict[str, dict[str, Any]]): 类型编码到 {func, args, kwargs}。
|
||||
- flow_variables (dict[str, Any]): 流程变量。
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: 含 node_results、status 等。
|
||||
"""
|
||||
levels = _topological_levels(ordered_nodes, edges)
|
||||
results: dict[str, Any] = {}
|
||||
for level in levels:
|
||||
futures: dict[str, Any] = {}
|
||||
for node in level:
|
||||
nid = node["id"]
|
||||
ntype = node.get("type") or ""
|
||||
tpl = node_templates.get(ntype)
|
||||
if not tpl or not tpl.get("func"):
|
||||
raise ValueError(f"未知或未配置节点类型: {ntype}")
|
||||
data = node.get("data") or {}
|
||||
args_str = data.get("args") if data.get("args") is not None else tpl.get("args")
|
||||
kwargs_str = data.get("kwargs") if data.get("kwargs") is not None else tpl.get("kwargs")
|
||||
upstream: dict[str, Any] = {}
|
||||
for e in edges:
|
||||
if e.get("target") == nid and e.get("source") in results:
|
||||
upstream[e["source"]] = results[e["source"]]
|
||||
futures[nid] = prefect_node_task.submit(
|
||||
nid,
|
||||
ntype,
|
||||
tpl["func"],
|
||||
args_str,
|
||||
kwargs_str,
|
||||
upstream,
|
||||
flow_variables,
|
||||
)
|
||||
for nid, fut in futures.items():
|
||||
results[nid] = fut.result()
|
||||
logger.info(
|
||||
"Prefect workflow 完成: nodes=%s",
|
||||
list(results.keys()),
|
||||
)
|
||||
return {
|
||||
"node_results": results,
|
||||
"status": "completed",
|
||||
}
|
||||
|
||||
|
||||
def run_prefect_workflow_sync(
|
||||
nodes: list[dict],
|
||||
edges: list[dict],
|
||||
node_templates: dict[str, dict[str, Any]],
|
||||
flow_variables: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
同步入口:校验 DAG 后执行 Prefect Flow(Flow 内部按层级并行调度)。
|
||||
|
||||
参数:
|
||||
- nodes (list[dict]): 画布节点。
|
||||
- edges (list[dict]): 画布边。
|
||||
- node_templates (dict[str, dict[str, Any]]): 节点类型模板。
|
||||
- flow_variables (dict[str, Any]): 流程变量。
|
||||
|
||||
返回:
|
||||
- dict[str, Any]: Flow 执行汇总结果。
|
||||
"""
|
||||
validate_workflow_graph(nodes, edges)
|
||||
return run_workflow_prefect_flow(
|
||||
ordered_nodes=nodes,
|
||||
edges=edges,
|
||||
node_templates=node_templates,
|
||||
flow_variables=flow_variables or {},
|
||||
)
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
"""
|
||||
当前 UTC 时间的 ISO 8601 字符串。
|
||||
|
||||
返回:
|
||||
- str: ISO 格式时间戳。
|
||||
"""
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
@@ -0,0 +1,136 @@
|
||||
"""工作流 DAG 执行引擎 — 拓扑分层 + 并行执行"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections import defaultdict, deque
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from app.core.ap_scheduler import SchedulerUtil
|
||||
from app.core.logger import logger
|
||||
|
||||
|
||||
def _parse_args(args_str: str | None) -> list[Any]:
|
||||
if not args_str or not str(args_str).strip():
|
||||
return []
|
||||
return [a.strip() for a in str(args_str).split(",") if a.strip()]
|
||||
|
||||
|
||||
def _parse_kwargs(kwargs_str: str | None) -> dict[str, Any]:
|
||||
if not kwargs_str or not str(kwargs_str).strip():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(kwargs_str)
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
|
||||
def validate_workflow_graph(nodes: list[dict], edges: list[dict]) -> None:
|
||||
if not nodes:
|
||||
raise ValueError("工作流至少需要一个节点")
|
||||
ids = {n["id"] for n in nodes}
|
||||
for e in edges:
|
||||
if e.get("source") not in ids or e.get("target") not in ids:
|
||||
raise ValueError("连线引用了不存在的节点")
|
||||
in_degree: dict[str, int] = dict.fromkeys(ids, 0)
|
||||
adj: dict[str, list[str]] = defaultdict(list)
|
||||
for e in edges:
|
||||
adj[e["source"]].append(e["target"])
|
||||
in_degree[e["target"]] += 1
|
||||
q: deque[str] = deque([nid for nid in ids if in_degree[nid] == 0])
|
||||
visited = 0
|
||||
while q:
|
||||
u = q.popleft()
|
||||
visited += 1
|
||||
for v in adj[u]:
|
||||
in_degree[v] -= 1
|
||||
if in_degree[v] == 0:
|
||||
q.append(v)
|
||||
if visited != len(ids):
|
||||
raise ValueError("工作流图存在环路,无法执行")
|
||||
|
||||
|
||||
def _topological_levels(nodes: list[dict], edges: list[dict]) -> list[list[dict]]:
|
||||
id_to_node = {n["id"]: n for n in nodes}
|
||||
in_degree: dict[str, int] = {n["id"]: 0 for n in nodes}
|
||||
adj: dict[str, list[str]] = defaultdict(list)
|
||||
for e in edges:
|
||||
adj[e["source"]].append(e["target"])
|
||||
in_degree[e["target"]] += 1
|
||||
levels: list[list[dict]] = []
|
||||
current = [nid for nid in in_degree if in_degree[nid] == 0]
|
||||
while current:
|
||||
levels.append([id_to_node[nid] for nid in current])
|
||||
next_level: list[str] = []
|
||||
for nid in current:
|
||||
for target in adj[nid]:
|
||||
in_degree[target] -= 1
|
||||
if in_degree[target] == 0:
|
||||
next_level.append(target)
|
||||
current = next_level
|
||||
return levels
|
||||
|
||||
|
||||
def _execute_node(
|
||||
vue_node_id: str,
|
||||
node_type_code: str,
|
||||
code_block: str,
|
||||
args_str: str | None,
|
||||
kwargs_str: str | None,
|
||||
upstream: dict[str, Any],
|
||||
flow_variables: dict[str, Any],
|
||||
) -> Any:
|
||||
job_id = f"wfnode-{vue_node_id}"
|
||||
args = _parse_args(args_str)
|
||||
kw = _parse_kwargs(kwargs_str)
|
||||
kw.setdefault("upstream", upstream)
|
||||
kw.setdefault("variables", flow_variables)
|
||||
return SchedulerUtil._task_wrapper(job_id, code_block, *args, **kw)
|
||||
|
||||
|
||||
def run_workflow_sync(
|
||||
nodes: list[dict],
|
||||
edges: list[dict],
|
||||
node_templates: dict[str, dict[str, Any]],
|
||||
flow_variables: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""同步执行工作流:按拓扑层级分组,同层节点并行执行。"""
|
||||
validate_workflow_graph(nodes, edges)
|
||||
levels = _topological_levels(nodes, edges)
|
||||
results: dict[str, Any] = {}
|
||||
for level in levels:
|
||||
with ThreadPoolExecutor(max_workers=len(level)) as executor:
|
||||
futures: dict[str, Any] = {}
|
||||
for node in level:
|
||||
nid = node["id"]
|
||||
ntype = node.get("type") or ""
|
||||
tpl = node_templates.get(ntype)
|
||||
if not tpl or not tpl.get("func"):
|
||||
raise ValueError(f"未知或未配置节点类型: {ntype}")
|
||||
data = node.get("data") or {}
|
||||
args_str = data.get("args") if data.get("args") is not None else tpl.get("args")
|
||||
kwargs_str = data.get("kwargs") if data.get("kwargs") is not None else tpl.get("kwargs")
|
||||
upstream: dict[str, Any] = {}
|
||||
for e in edges:
|
||||
if e.get("target") == nid and e.get("source") in results:
|
||||
upstream[e["source"]] = results[e["source"]]
|
||||
futures[nid] = executor.submit(
|
||||
_execute_node,
|
||||
nid,
|
||||
ntype,
|
||||
tpl["func"],
|
||||
args_str,
|
||||
kwargs_str,
|
||||
upstream,
|
||||
flow_variables,
|
||||
)
|
||||
for nid, fut in futures.items():
|
||||
results[nid] = fut.result()
|
||||
logger.info("工作流执行完成: nodes=%s", list(results.keys()))
|
||||
return {"node_results": results, "status": "completed"}
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
return datetime.now(UTC).isoformat()
|
||||
@@ -32,16 +32,8 @@ WorkflowNodeTypeRouter = APIRouter(
|
||||
async def get_workflow_node_type_options_controller(
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
获取画布用编排节点类型选项(仅启用项)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为选项列表。
|
||||
"""
|
||||
result = await WorkflowNodeTypeService.get_options_service(auth=auth)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result = await service.get_options()
|
||||
return SuccessResponse(data=result, msg="获取编排节点类型选项成功")
|
||||
|
||||
|
||||
@@ -54,17 +46,8 @@ async def get_workflow_node_type_detail_controller(
|
||||
id: Annotated[int, Path(description="ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
获取编排节点类型详情。
|
||||
|
||||
参数:
|
||||
- id (int): 主键。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为详情。
|
||||
"""
|
||||
result_dict = await WorkflowNodeTypeService.get_detail_service(auth=auth, id=id)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result_dict = await service.get_detail(id=id)
|
||||
return SuccessResponse(data=result_dict, msg="获取编排节点类型详情成功")
|
||||
|
||||
|
||||
@@ -78,22 +61,11 @@ async def get_workflow_node_type_list_controller(
|
||||
search: Annotated[WorkflowNodeTypeQueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
分页查询编排节点类型列表。
|
||||
|
||||
参数:
|
||||
- page (PaginationQueryParam): 分页与排序。
|
||||
- search (WorkflowNodeTypeQueryParam): 查询条件。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为分页结果。
|
||||
"""
|
||||
order_by = [{"sort_order": "asc"}, {"id": "asc"}]
|
||||
if page.order_by:
|
||||
order_by = page.order_by
|
||||
result_dict = await WorkflowNodeTypeService.get_page_service(
|
||||
auth=auth,
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result_dict = await service.get_page(
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
@@ -111,17 +83,8 @@ async def create_workflow_node_type_controller(
|
||||
data: WorkflowNodeTypeCreateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:create"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
创建编排节点类型。
|
||||
|
||||
参数:
|
||||
- data (WorkflowNodeTypeCreateSchema): 创建体。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为新记录。
|
||||
"""
|
||||
result_dict = await WorkflowNodeTypeService.create_service(auth=auth, data=data)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result_dict = await service.create(data=data)
|
||||
return SuccessResponse(data=result_dict, msg="创建编排节点类型成功")
|
||||
|
||||
|
||||
@@ -135,18 +98,8 @@ async def update_workflow_node_type_controller(
|
||||
data: WorkflowNodeTypeUpdateSchema,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
更新编排节点类型。
|
||||
|
||||
参数:
|
||||
- id (int): 主键。
|
||||
- data (WorkflowNodeTypeUpdateSchema): 更新体。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为更新后记录。
|
||||
"""
|
||||
result_dict = await WorkflowNodeTypeService.update_service(auth=auth, id=id, data=data)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result_dict = await service.update(id=id, data=data)
|
||||
return SuccessResponse(data=result_dict, msg="更新编排节点类型成功")
|
||||
|
||||
|
||||
@@ -159,17 +112,8 @@ async def delete_workflow_node_type_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:delete"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
批量删除编排节点类型。
|
||||
|
||||
参数:
|
||||
- ids (list[int]): ID 列表。
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功提示响应。
|
||||
"""
|
||||
await WorkflowNodeTypeService.delete_service(auth=auth, ids=ids)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
await service.delete(ids=ids)
|
||||
return SuccessResponse(msg="删除编排节点类型成功")
|
||||
|
||||
|
||||
@@ -181,14 +125,6 @@ async def delete_workflow_node_type_controller(
|
||||
async def get_workflow_node_type_select_controller(
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["module_task:workflow:node-type:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
获取编排节点类型选择列表。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- JSONResponse: 成功响应,data 为选择列表。
|
||||
"""
|
||||
result = await WorkflowNodeTypeService.get_select_service(auth=auth)
|
||||
service = WorkflowNodeTypeService(auth)
|
||||
result = await service.get_select()
|
||||
return SuccessResponse(data=result, msg="获取编排节点类型选择列表成功")
|
||||
|
||||
@@ -6,7 +6,7 @@ from app.core.base_model import ModelMixin, TenantMixin, UserMixin
|
||||
|
||||
class WorkflowNodeTypeModel(ModelMixin, TenantMixin, UserMixin):
|
||||
"""
|
||||
编排节点类型:用于 Vue Flow 左侧 palette 与 Prefect 运行时解析。
|
||||
编排节点类型:用于 Vue Flow 左侧 palette 与执行引擎解析。
|
||||
"""
|
||||
|
||||
__tablename__: str = "task_workflow_node_type"
|
||||
|
||||
@@ -13,22 +13,15 @@ from .schema import (
|
||||
class WorkflowNodeTypeService:
|
||||
"""工作流编排节点类型(与定时任务 task_node 无关)"""
|
||||
|
||||
@classmethod
|
||||
def _out(cls, obj) -> WorkflowNodeTypeOutSchema:
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
self.auth = auth
|
||||
|
||||
@staticmethod
|
||||
def _out(obj) -> WorkflowNodeTypeOutSchema:
|
||||
return WorkflowNodeTypeOutSchema.model_validate(obj)
|
||||
|
||||
@classmethod
|
||||
async def get_options_service(cls, auth: AuthSchema) -> list[dict]:
|
||||
"""
|
||||
画布左侧 palette:仅返回启用项,结构与前端 Node options 对齐。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- list[dict]: 选项字典列表。
|
||||
"""
|
||||
objs = await WorkflowNodeTypeCRUD(auth).list_active_options_crud()
|
||||
async def get_options(self) -> list[dict]:
|
||||
objs = await WorkflowNodeTypeCRUD(self.auth).list_active_options_crud()
|
||||
return [
|
||||
{
|
||||
"id": o.id,
|
||||
@@ -41,77 +34,35 @@ class WorkflowNodeTypeService:
|
||||
for o in objs
|
||||
]
|
||||
|
||||
@classmethod
|
||||
async def get_detail_service(cls, auth: AuthSchema, id: int) -> WorkflowNodeTypeOutSchema:
|
||||
"""
|
||||
获取编排节点类型详情。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- id (int): 主键。
|
||||
|
||||
返回:
|
||||
- dict: 序列化后的详情。
|
||||
|
||||
异常:
|
||||
- CustomException: 不存在时抛出。
|
||||
"""
|
||||
obj = await WorkflowNodeTypeCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
async def get_detail(self, id: int) -> WorkflowNodeTypeOutSchema:
|
||||
obj = await WorkflowNodeTypeCRUD(self.auth).get_obj_by_id_crud(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="编排节点类型不存在")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def get_list_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
async def get_list(
|
||||
self,
|
||||
search: WorkflowNodeTypeQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> list[WorkflowNodeTypeOutSchema]:
|
||||
"""
|
||||
获取编排节点类型列表(非分页)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- search (WorkflowNodeTypeQueryParam | None): 查询条件。
|
||||
- order_by (list[dict[str, str]] | None): 排序。
|
||||
|
||||
返回:
|
||||
- list[dict]: 字典列表。
|
||||
"""
|
||||
if order_by is None:
|
||||
order_by = [{"sort_order": "asc"}, {"id": "asc"}]
|
||||
obj_list = await WorkflowNodeTypeCRUD(auth).get_obj_list_crud(
|
||||
obj_list = await WorkflowNodeTypeCRUD(self.auth).get_obj_list_crud(
|
||||
search=vars(search) if search else None,
|
||||
order_by=order_by,
|
||||
)
|
||||
return [cls._out(o) for o in obj_list]
|
||||
return [self._out(o) for o in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def get_page_service(
|
||||
cls,
|
||||
auth: AuthSchema,
|
||||
async def get_page(
|
||||
self,
|
||||
page_no: int,
|
||||
page_size: int,
|
||||
search: WorkflowNodeTypeQueryParam | None = None,
|
||||
order_by: list[dict[str, str]] | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
分页查询编排节点类型(数据库 OFFSET/LIMIT)。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- page_no (int): 页码。
|
||||
- page_size (int): 每页条数。
|
||||
- search (WorkflowNodeTypeQueryParam | None): 查询条件。
|
||||
- order_by (list[dict[str, str]] | None): 排序。
|
||||
|
||||
返回:
|
||||
- dict: 分页结果(items 已 JSON 友好序列化)。
|
||||
"""
|
||||
offset = (page_no - 1) * page_size
|
||||
order = order_by or [{"sort_order": "asc"}, {"id": "asc"}]
|
||||
result = await WorkflowNodeTypeCRUD(auth).page(
|
||||
result = await WorkflowNodeTypeCRUD(self.auth).page(
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order,
|
||||
@@ -121,86 +72,33 @@ class WorkflowNodeTypeService:
|
||||
result.items = [WorkflowNodeTypeOutSchema.model_validate(item).model_dump(mode="json") for item in result.items]
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
async def create_service(cls, auth: AuthSchema, data: WorkflowNodeTypeCreateSchema) -> WorkflowNodeTypeOutSchema:
|
||||
"""
|
||||
创建编排节点类型。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- data (WorkflowNodeTypeCreateSchema): 创建体。
|
||||
|
||||
返回:
|
||||
- dict: 新建记录字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 编码重复或创建失败。
|
||||
"""
|
||||
exist = await WorkflowNodeTypeCRUD(auth).get(code=data.code)
|
||||
async def create(self, data: WorkflowNodeTypeCreateSchema) -> WorkflowNodeTypeOutSchema:
|
||||
exist = await WorkflowNodeTypeCRUD(self.auth).get(code=data.code)
|
||||
if exist:
|
||||
raise CustomException(msg="节点编码已存在")
|
||||
obj = await WorkflowNodeTypeCRUD(auth).create_obj_crud(data=data)
|
||||
obj = await WorkflowNodeTypeCRUD(self.auth).create_obj_crud(data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="创建失败")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def update_service(cls, auth: AuthSchema, id: int, data: WorkflowNodeTypeUpdateSchema) -> WorkflowNodeTypeOutSchema:
|
||||
"""
|
||||
更新编排节点类型。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- id (int): 主键。
|
||||
- data (WorkflowNodeTypeUpdateSchema): 更新体。
|
||||
|
||||
返回:
|
||||
- dict: 更新后字典。
|
||||
|
||||
异常:
|
||||
- CustomException: 不存在、编码冲突或更新失败。
|
||||
"""
|
||||
exist = await WorkflowNodeTypeCRUD(auth).get_obj_by_id_crud(id=id)
|
||||
async def update(self, id: int, data: WorkflowNodeTypeUpdateSchema) -> WorkflowNodeTypeOutSchema:
|
||||
exist = await WorkflowNodeTypeCRUD(self.auth).get_obj_by_id_crud(id=id)
|
||||
if not exist:
|
||||
raise CustomException(msg="编排节点类型不存在")
|
||||
if exist.code != data.code:
|
||||
other = await WorkflowNodeTypeCRUD(auth).get(code=data.code)
|
||||
other = await WorkflowNodeTypeCRUD(self.auth).get(code=data.code)
|
||||
if other:
|
||||
raise CustomException(msg="节点编码已存在")
|
||||
obj = await WorkflowNodeTypeCRUD(auth).update_obj_crud(id=id, data=data)
|
||||
obj = await WorkflowNodeTypeCRUD(self.auth).update_obj_crud(id=id, data=data)
|
||||
if not obj:
|
||||
raise CustomException(msg="更新失败")
|
||||
return cls._out(obj)
|
||||
return self._out(obj)
|
||||
|
||||
@classmethod
|
||||
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除编排节点类型。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
- ids (list[int]): ID 列表。
|
||||
|
||||
返回:
|
||||
- None
|
||||
|
||||
异常:
|
||||
- CustomException: ID 为空时抛出。
|
||||
"""
|
||||
async def delete(self, ids: list[int]) -> None:
|
||||
if not ids:
|
||||
raise CustomException(msg="删除ID不能为空")
|
||||
await WorkflowNodeTypeCRUD(auth).delete_obj_crud(ids=ids)
|
||||
await WorkflowNodeTypeCRUD(self.auth).delete_obj_crud(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def get_select_service(cls, auth: AuthSchema) -> list[dict]:
|
||||
"""
|
||||
获取编排节点类型选择列表。
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息。
|
||||
|
||||
返回:
|
||||
- list[dict]: 选择列表,包含 id 和 name。
|
||||
"""
|
||||
objs = await WorkflowNodeTypeCRUD(auth).get_obj_list_crud()
|
||||
async def get_select(self) -> list[dict]:
|
||||
objs = await WorkflowNodeTypeCRUD(self.auth).get_obj_list_crud()
|
||||
return [{"id": o.id, "name": o.name} for o in objs]
|
||||
|
||||
Reference in New Issue
Block a user