Files
FastapiAdmin/backend/app/plugin/module_task/workflow/service.py
T
zhangtao 1f7e9e8d67 feat(workflow): 重构工作流模块并迁移到module_task目录
- 将工作流相关模块从module_application迁移到module_task目录
- 新增节点类型模型、服务和API接口
- 添加动态节点组件和节点操作工具函数
- 更新前端工作流编辑器支持动态节点配置
- 移除旧的module_application工作流相关文件
- 升级element-plus到2.11.0版本
- 优化性能工具函数(防抖和节流)
- 清理不再使用的依赖项(langchain-anthropic和langchain-mcp-adapters)
2026-02-17 03:24:53 +08:00

1073 lines
36 KiB
Python

from datetime import datetime
from typing import Any
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.exceptions import CustomException
from .crud import WorkflowCRUD, WorkflowRunCRUD, WorkflowRunLogCRUD
from .schema import (
WorkflowCreateSchema,
WorkflowExecuteSchema,
WorkflowOutSchema,
WorkflowPublishSchema,
WorkflowQueryParam,
WorkflowRunCreateSchema,
WorkflowRunLogCreateSchema,
WorkflowRunLogOutSchema,
WorkflowRunOutSchema,
WorkflowRunUpdateSchema,
WorkflowUpdateSchema,
WorkflowValidateResultSchema,
WorkflowValidateSchema,
)
class WorkflowService:
"""
工作流管理模块服务层
"""
@classmethod
async def detail_service(cls, auth: AuthSchema, id: int) -> dict:
"""
详情
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
返回:
- dict: 工作流模型实例字典
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="该工作流不存在")
return WorkflowOutSchema.model_validate(obj).model_dump()
@classmethod
async def list_service(
cls,
auth: AuthSchema,
search: WorkflowQueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> list[dict]:
"""
列表查询
参数:
- auth (AuthSchema): 认证信息模型
- search (WorkflowQueryParam | None): 查询参数
- order_by (list[dict[str, str]] | None): 排序参数
返回:
- list[dict]: 工作流模型实例字典列表
"""
search_dict = search.__dict__ if search else None
obj_list = await WorkflowCRUD(auth).list_crud(search=search_dict, order_by=order_by)
return [WorkflowOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_service(
cls,
auth: AuthSchema,
page_no: int,
page_size: int,
search: WorkflowQueryParam | None = None,
order_by: list[dict[str, str]] | None = None,
) -> dict:
"""
分页查询
参数:
- auth (AuthSchema): 认证信息模型
- page_no (int): 页码
- page_size (int): 每页数量
- search (WorkflowQueryParam | None): 查询参数
- order_by (list[dict[str, str]] | None): 排序参数
返回:
- dict: 分页数据
"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{"id": "asc"}]
offset = (page_no - 1) * page_size
result = await WorkflowCRUD(auth).page_crud(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict,
)
return result
@classmethod
async def create_service(cls, auth: AuthSchema, data: WorkflowCreateSchema) -> dict:
"""
创建
参数:
- auth (AuthSchema): 认证信息模型
- data (WorkflowCreateSchema): 工作流创建模型
返回:
- dict: 工作流模型实例字典
"""
obj = await WorkflowCRUD(auth).get_by_code_crud(code=data.code)
if obj:
raise CustomException(msg="创建失败,流程编码已存在")
if data.template_id:
template = await WorkflowCRUD(auth).get_by_id_crud(id=data.template_id)
if not template or not template.is_template:
raise CustomException(msg="创建失败,模板不存在或不是模板")
obj = await WorkflowCRUD(auth).create_crud(data=data)
return WorkflowOutSchema.model_validate(obj).model_dump()
@classmethod
async def update_service(cls, auth: AuthSchema, id: int, data: WorkflowUpdateSchema) -> dict:
"""
更新
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
- data (WorkflowUpdateSchema): 工作流更新模型
返回:
- dict: 工作流模型实例字典
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="更新失败,该工作流不存在")
if obj.status == "published":
raise CustomException(msg="更新失败,已发布的工作流不能直接修改,请先归档")
if data.code:
exist_obj = await WorkflowCRUD(auth).get_by_code_crud(code=data.code)
if exist_obj and exist_obj.id != id:
raise CustomException(msg="更新失败,流程编码重复")
if data.template_id:
template = await WorkflowCRUD(auth).get_by_id_crud(id=data.template_id)
if not template or not template.is_template:
raise CustomException(msg="更新失败,模板不存在或不是模板")
obj = await WorkflowCRUD(auth).update_crud(id=id, data=data)
return WorkflowOutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""
删除
参数:
- auth (AuthSchema): 认证信息模型
- ids (list[int]): 工作流ID列表
返回:
- None
"""
if len(ids) < 1:
raise CustomException(msg="删除失败,删除对象不能为空")
obj_list = await WorkflowCRUD(auth).list_crud(search={"id": (None, ids)})
for obj in obj_list:
if obj.status == "published":
raise CustomException(msg=f"删除失败,ID为{obj.id}的工作流已发布,无法删除")
if len(obj_list) != len(ids):
existing_ids = {obj.id for obj in obj_list}
missing_ids = set(ids) - existing_ids
raise CustomException(msg=f"删除失败,ID为{missing_ids}的工作流不存在")
await WorkflowCRUD(auth).delete_crud(ids=ids)
@classmethod
async def publish_service(cls, auth: AuthSchema, id: int, data: WorkflowPublishSchema) -> dict:
"""
发布工作流
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
- data (WorkflowPublishSchema): 发布模型
返回:
- dict: 工作流模型实例字典
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="发布失败,该工作流不存在")
if obj.status == "published":
raise CustomException(msg="发布失败,该工作流已发布")
update_data = WorkflowUpdateSchema(
status="published",
version=data.version or obj.version,
published_at=datetime.now().isoformat(),
published_by=auth.user.id if auth.user else None,
)
obj = await WorkflowCRUD(auth).update_crud(id=id, data=update_data)
return WorkflowOutSchema.model_validate(obj).model_dump()
@classmethod
async def validate_service(cls, auth: AuthSchema, data: WorkflowValidateSchema) -> WorkflowValidateResultSchema:
"""
验证工作流
参数:
- auth (AuthSchema): 认证信息模型
- data (WorkflowValidateSchema): 验证模型
返回:
- WorkflowValidateResultSchema: 验证结果
"""
errors = []
warnings = []
stats = {}
nodes = data.nodes if isinstance(data.nodes, list) else []
edges = data.edges if isinstance(data.edges, list) else []
stats["totalNodes"] = len(nodes)
stats["totalEdges"] = len(edges)
stats["nodeTypes"] = {}
for node in nodes:
node_type = node.get("type", "unknown")
stats["nodeTypes"][node_type] = stats["nodeTypes"].get(node_type, 0) + 1
if len(nodes) == 0:
errors.append("流程中没有节点")
start_nodes = [n for n in nodes if n.get("type") == "input"]
end_nodes = [n for n in nodes if n.get("type") == "output"]
if len(start_nodes) == 0:
errors.append("流程缺少开始节点")
elif len(start_nodes) > 1:
warnings.append("流程有多个开始节点")
if len(end_nodes) == 0:
errors.append("流程缺少结束节点")
elif len(end_nodes) > 1:
warnings.append("流程有多个结束节点")
node_ids = {n.get("id") for n in nodes}
for edge in edges:
source = edge.get("source")
target = edge.get("target")
if source not in node_ids:
errors.append(f"连线 {edge.get('label') or edge.get('id')} 的源节点不存在")
if target not in node_ids:
errors.append(f"连线 {edge.get('label') or edge.get('id')} 的目标节点不存在")
is_valid = len(errors) == 0
return WorkflowValidateResultSchema(
is_valid=is_valid,
errors=errors,
warnings=warnings,
stats=stats,
)
@classmethod
async def templates_service(cls, auth: AuthSchema) -> list[dict]:
"""
获取模板列表
参数:
- auth (AuthSchema): 认证信息模型
返回:
- list[dict]: 模板列表
"""
obj_list = await WorkflowCRUD(auth).list_templates_crud(order_by=[{"id": "asc"}])
return [WorkflowOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def export_service(cls, auth: AuthSchema, id: int) -> dict:
"""
导出工作流
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
返回:
- dict: 工作流数据
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="导出失败,该工作流不存在")
return {
"id": obj.id,
"name": obj.name,
"code": obj.code,
"description": obj.description,
"nodes": obj.nodes,
"edges": obj.edges,
"version": obj.version,
"category": obj.category,
"metadata": obj.meta_data,
"exportedAt": datetime.now().isoformat(),
}
@classmethod
async def import_service(cls, auth: AuthSchema, data: dict) -> dict:
"""
导入工作流
参数:
- auth (AuthSchema): 认证信息模型
- data (dict): 工作流数据
返回:
- dict: 工作流模型实例字典
"""
code = data.get("code")
if code:
exist_obj = await WorkflowCRUD(auth).get_by_code_crud(code=code)
if exist_obj:
raise CustomException(msg="导入失败,流程编码已存在")
create_data = WorkflowCreateSchema(
name=data.get("name", "导入的工作流"),
code=data.get("code", f"imported_{datetime.now().timestamp()}"),
description=data.get("description"),
nodes=data.get("nodes", []),
edges=data.get("edges", []),
version=data.get("version", "1.0.0"),
category=data.get("category"),
meta_data=data.get("metadata"),
)
obj = await WorkflowCRUD(auth).create_crud(data=create_data)
return WorkflowOutSchema.model_validate(obj).model_dump()
@classmethod
async def execute_service(cls, auth: AuthSchema, data: WorkflowExecuteSchema) -> dict:
"""
执行工作流
参数:
- auth (AuthSchema): 认证信息模型
- data (WorkflowExecuteSchema): 执行模型
返回:
- dict: 执行结果
"""
task_run_id = await WorkflowRunService.execute_workflow(
auth=auth,
workflow_id=data.workflow_id,
variables=data.variables,
business_key=data.business_key,
initiator=auth.user.id if auth.user else None,
initiator_name=auth.user.username if auth.user else None,
job_id=data.job_id,
)
return {
"task_run_id": task_run_id,
"workflow_id": data.workflow_id,
"status": "running",
"message": "任务执行已启动",
}
@classmethod
async def pause_service(cls, auth: AuthSchema, id: int) -> None:
"""
暂停工作流
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
返回:
- None
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="工作流不存在")
await WorkflowRunService.pause_service(auth=auth, id=id)
@classmethod
async def resume_service(cls, auth: AuthSchema, id: int) -> None:
"""
恢复工作流
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
返回:
- None
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="工作流不存在")
await WorkflowRunService.resume_service(auth=auth, id=id)
@classmethod
async def terminate_service(cls, auth: AuthSchema, id: int) -> None:
"""
终止工作流
参数:
- auth (AuthSchema): 认证信息模型
- id (int): 工作流ID
返回:
- None
"""
obj = await WorkflowCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="工作流不存在")
await WorkflowRunService.terminate_service(auth=auth, id=id)
class WorkflowRunService:
"""工作流运行记录管理模块服务层"""
@classmethod
async def list_service(cls, auth: AuthSchema, search: dict | None = None, order_by: list[dict] | None = None) -> list[dict]:
"""获取工作流运行记录列表"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{"id": "desc"}]
obj_list = await WorkflowRunCRUD(auth).list_crud(search=search_dict, order_by=order_by_list)
return [WorkflowRunOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_service(
cls,
auth: AuthSchema,
page_no: int,
page_size: int,
search: dict | None = None,
order_by: list[dict] | None = None,
) -> dict:
"""分页查询工作流运行记录"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{"id": "desc"}]
offset = (page_no - 1) * page_size
result = await WorkflowRunCRUD(auth).page_crud(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict,
)
return result
@classmethod
async def detail_service(cls, auth: AuthSchema, id: int) -> dict:
"""获取工作流运行记录详情"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="查询失败,该工作流运行记录不存在")
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def create_service(cls, auth: AuthSchema, data: WorkflowRunCreateSchema) -> dict:
"""创建工作流运行记录"""
obj = await WorkflowRunCRUD(auth).create_crud(data=data)
if not obj:
raise CustomException(msg="创建失败,工作流运行记录创建失败")
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def update_service(cls, auth: AuthSchema, id: int, data: WorkflowRunUpdateSchema) -> dict:
"""更新工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="更新失败,该工作流运行记录不存在")
obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=data)
if not obj:
raise CustomException(msg="更新失败,工作流运行记录更新失败")
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def delete_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""删除工作流运行记录"""
if len(ids) < 1:
raise CustomException(msg="删除失败,删除对象不能为空")
obj_list = await WorkflowRunCRUD(auth).list_crud(search={"id": (None, ids)})
if not obj_list or len(obj_list) != len(ids):
raise CustomException(msg="删除失败,部分工作流运行记录不存在")
await WorkflowRunCRUD(auth).delete_crud(ids=ids)
@classmethod
async def clear_service(cls, auth: AuthSchema) -> None:
"""清除所有工作流运行记录"""
await WorkflowRunCRUD(auth).clear_crud()
@classmethod
async def cancel_service(cls, auth: AuthSchema, id: int) -> dict:
"""取消工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="取消失败,该工作流运行记录不存在")
if obj.status in ["completed", "failed", "cancelled"]:
raise CustomException(msg="取消失败,该工作流运行记录已完成或已取消")
update_data = WorkflowRunUpdateSchema(
status="cancelled",
end_time=datetime.now().isoformat(),
)
obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=update_data)
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def pause_service(cls, auth: AuthSchema, id: int) -> dict:
"""暂停工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="暂停失败,该工作流运行记录不存在")
if obj.status not in ["running", "pending"]:
raise CustomException(msg="暂停失败,只能暂停运行中或待执行的工作流运行记录")
update_data = WorkflowRunUpdateSchema(
status="paused",
)
obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=update_data)
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def resume_service(cls, auth: AuthSchema, id: int) -> dict:
"""恢复工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="恢复失败,该工作流运行记录不存在")
if obj.status != "paused":
raise CustomException(msg="恢复失败,只能恢复已暂停的工作流运行记录")
update_data = WorkflowRunUpdateSchema(
status="running",
)
obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=update_data)
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def terminate_service(cls, auth: AuthSchema, id: int) -> dict:
"""终止工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="终止失败,该工作流运行记录不存在")
if obj.status in ["completed", "failed", "cancelled", "terminated"]:
raise CustomException(msg="终止失败,该工作流运行记录已完成或已终止")
update_data = WorkflowRunUpdateSchema(
status="terminated",
end_time=datetime.now().isoformat(),
)
obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=update_data)
return WorkflowRunOutSchema.model_validate(obj).model_dump()
@classmethod
async def retry_service(cls, auth: AuthSchema, id: int, retry_count: int = 1) -> dict:
"""重试工作流运行记录"""
obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not obj:
raise CustomException(msg="重试失败,该工作流运行记录不存在")
if obj.status not in ["failed", "cancelled"]:
raise CustomException(msg="重试失败,只能重试失败或已取消的工作流运行记录")
if obj.retry_count >= obj.max_retry:
raise CustomException(msg=f"重试失败,已达到最大重试次数{obj.max_retry}")
update_data = WorkflowRunUpdateSchema(
status="pending",
retry_count=obj.retry_count + retry_count,
error_message=None,
start_time=None,
end_time=None,
duration=None,
)
updated_obj = await WorkflowRunCRUD(auth).update_crud(id=id, data=update_data)
if not updated_obj:
raise CustomException(msg="重试失败,更新工作流运行记录失败")
try:
await cls.execute_workflow(
auth=auth,
workflow_id=obj.workflow_id,
variables=obj.variables,
business_key=obj.business_key,
initiator=obj.initiator,
initiator_name=obj.initiator_name,
job_id=obj.job_id,
task_run_id=id,
)
except Exception:
pass
final_obj = await WorkflowRunCRUD(auth).get_by_id_crud(id=id)
if not final_obj:
raise CustomException(msg="重试失败,获取工作流运行记录失败")
return WorkflowRunOutSchema.model_validate(final_obj).model_dump()
@classmethod
async def execute_workflow(
cls,
auth: AuthSchema,
workflow_id: int,
variables: dict,
business_key: str | None,
initiator: int | None,
initiator_name: str | None,
job_id: int | None,
task_run_id: int | None = None,
) -> int:
"""执行工作流"""
workflow = await WorkflowCRUD(auth).get_by_id_crud(id=workflow_id)
if not workflow:
raise CustomException(msg="工作流不存在")
nodes = workflow.nodes if isinstance(workflow.nodes, list) else []
edges = workflow.edges if isinstance(workflow.edges, list) else []
if not nodes:
raise CustomException(msg="工作流没有节点")
start_time = datetime.now()
if not task_run_id:
create_data = WorkflowRunCreateSchema(
workflow_id=workflow_id,
workflow_name=workflow.name,
workflow_version=workflow.version,
business_key=business_key,
initiator=initiator,
initiator_name=initiator_name,
variables=variables,
job_id=job_id,
)
task_run = await WorkflowRunCRUD(auth).create_crud(data=create_data)
if not task_run:
raise CustomException(msg="创建工作流运行记录失败")
task_run_id = task_run.id
if task_run_id is None:
raise CustomException(msg="工作流运行记录ID不能为空")
update_data = WorkflowRunUpdateSchema(
status="running",
start_time=start_time.isoformat(),
)
await WorkflowRunCRUD(auth).update_crud(id=task_run_id, data=update_data)
try:
await cls._execute_nodes(
auth=auth,
task_run_id=task_run_id,
nodes=nodes,
edges=edges,
variables=variables,
)
end_time = datetime.now()
duration = int((end_time - start_time).total_seconds())
update_data = WorkflowRunUpdateSchema(
status="completed",
end_time=end_time.isoformat(),
duration=duration,
)
await WorkflowRunCRUD(auth).update_crud(id=task_run_id, data=update_data)
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
message=f"工作流执行完成,总耗时: {duration}秒",
)
except Exception as e:
end_time = datetime.now()
duration = int((end_time - start_time).total_seconds())
update_data = WorkflowRunUpdateSchema(
status="failed",
end_time=end_time.isoformat(),
duration=duration,
error_message=str(e),
)
await WorkflowRunCRUD(auth).update_crud(id=task_run_id, data=update_data)
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="error",
message=f"工作流执行失败: {e!s}",
)
raise
return task_run_id
@classmethod
async def _execute_nodes(
cls,
auth: AuthSchema,
task_run_id: int,
nodes: list[dict[str, Any]],
edges: list[dict[str, Any]],
variables: dict,
) -> None:
"""执行节点"""
node_map = {node.get("id"): node for node in nodes}
edge_map = {}
for edge in edges:
source = edge.get("source")
if source not in edge_map:
edge_map[source] = []
edge_map[source].append(edge)
start_nodes = [node for node in nodes if node.get("type") == "input"]
if not start_nodes:
raise CustomException(msg="工作流缺少开始节点")
executed_nodes = set()
queue = start_nodes.copy()
while queue:
current_node = queue.pop(0)
node_id = current_node.get("id")
if node_id in executed_nodes:
continue
executed_nodes.add(node_id)
await cls._execute_node(
auth=auth,
task_run_id=task_run_id,
node=current_node,
variables=variables,
)
if node_id in edge_map:
for edge in edge_map[node_id]:
target_id = edge.get("target")
target_node = node_map.get(target_id)
if target_node and target_id not in executed_nodes:
target_node_type = target_node.get("type")
if target_node_type == "condition":
condition = target_node.get("data", {}).get("condition", "True")
try:
result = bool(eval(condition, {}, variables))
except Exception:
result = False
for next_edge in edge_map.get(target_id, []):
next_target_id = next_edge.get("target")
next_target_node = node_map.get(next_target_id)
edge_label = next_edge.get("label", "")
if next_target_node and next_target_id not in executed_nodes:
if edge_label == "true" and result:
queue.append(next_target_node)
elif edge_label == "false" and not result:
queue.append(next_target_node)
else:
queue.append(target_node)
@classmethod
async def _execute_node(
cls,
auth: AuthSchema,
task_run_id: int,
node: dict[str, Any],
variables: dict,
) -> None:
"""执行单个节点"""
node_id = str(node.get("id", ""))
node_type = node.get("type", "")
node_name = node.get("data", {}).get("label", "未知节点")
node_config = node.get("data", {})
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"开始执行节点: {node_name}",
)
try:
if node_type == "task":
await cls._execute_task_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type == "approval":
await cls._execute_approval_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type == "condition":
await cls._execute_condition_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type == "notification":
await cls._execute_notification_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type == "timer":
await cls._execute_timer_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type == "parallel":
await cls._execute_parallel_node(auth, task_run_id, node_id, node_name, node_config, variables)
elif node_type in ["input", "output"]:
pass
else:
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="warning",
node_id=node_id,
node_name=node_name,
message=f"未知节点类型: {node_type}",
)
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"节点执行完成: {node_name}",
)
except Exception as e:
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="error",
node_id=node_id,
node_name=node_name,
message=f"节点执行失败: {node_name}, 错误: {e!s}",
)
raise
@classmethod
async def _execute_task_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行任务节点"""
task_type = node_config.get("task_type", "default")
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"执行任务类型: {task_type}",
)
variables[f"{node_id}_result"] = {"status": "success", "output": "任务执行成功"}
@classmethod
async def _execute_approval_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行审批节点"""
approvers = node_config.get("approvers", [])
approval_type = node_config.get("approval_type", "or")
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"审批类型: {approval_type}, 审批人: {approvers}",
)
variables[f"{node_id}_result"] = {"status": "approved", "output": "审批通过"}
@classmethod
async def _execute_condition_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行条件节点"""
condition = node_config.get("condition", "True")
try:
result = bool(eval(condition, {}, variables))
except Exception as e:
result = False
raise CustomException(msg=f"条件表达式执行失败: {e!s}")
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"条件表达式: {condition}, 结果: {result}",
)
variables[f"{node_id}_result"] = {"status": "success", "result": result}
@classmethod
async def _execute_notification_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行通知节点"""
notification_type = node_config.get("notification_type", "email")
recipients = node_config.get("recipients", [])
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"发送通知: {notification_type}, 收件人: {recipients}",
)
variables[f"{node_id}_result"] = {"status": "sent", "output": "通知发送成功"}
@classmethod
async def _execute_timer_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行定时节点"""
timer_type = node_config.get("timer_type", "delay")
timer_value = node_config.get("timer_value", 0)
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"定时器类型: {timer_type}, 值: {timer_value}",
)
variables[f"{node_id}_result"] = {"status": "completed", "output": "定时器执行完成"}
@classmethod
async def _execute_parallel_node(
cls,
auth: AuthSchema,
task_run_id: int,
node_id: str,
node_name: str,
node_config: dict,
variables: dict,
) -> None:
"""执行并行节点"""
parallel_count = node_config.get("parallel_count", 1)
await cls._add_log(
auth=auth,
task_run_id=task_run_id,
level="info",
node_id=node_id,
node_name=node_name,
message=f"并行执行数量: {parallel_count}",
)
variables[f"{node_id}_result"] = {"status": "completed", "output": "并行执行完成"}
@classmethod
async def _add_log(
cls,
auth: AuthSchema,
task_run_id: int,
level: str,
message: str,
node_id: str | None = None,
node_name: str | None = None,
data: dict | None = None,
) -> None:
"""添加日志"""
log_data = WorkflowRunLogCreateSchema(
task_run_id=task_run_id,
level=level,
node_id=node_id,
node_name=node_name,
message=message,
data=data or {},
)
await WorkflowRunLogCRUD(auth).create_crud(data=log_data)
class WorkflowRunLogService:
"""工作流运行日志管理模块服务层"""
@classmethod
async def list_service(cls, auth: AuthSchema, search: dict | None = None, order_by: list[dict] | None = None) -> list[dict]:
"""获取工作流运行日志列表"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{"id": "desc"}]
obj_list = await WorkflowRunLogCRUD(auth).list_crud(search=search_dict, order_by=order_by_list)
return [WorkflowRunLogOutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod
async def page_service(
cls,
auth: AuthSchema,
page_no: int,
page_size: int,
search: dict | None = None,
order_by: list[dict] | None = None,
) -> dict:
"""分页查询工作流运行日志"""
search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{"id": "desc"}]
offset = (page_no - 1) * page_size
result = await WorkflowRunLogCRUD(auth).page_crud(
offset=offset,
limit=page_size,
order_by=order_by_list,
search=search_dict,
)
return result