Files
ai-agent-admin/backend-fastapi/online_dev/workflow/flow_definition_validator.py
T

192 lines
6.3 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""流程定义结构校验(名称长度、发布前结构校验等)"""
from typing import Dict, List, Optional
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
MAX_NODE_NAME_LENGTH = 100
_ASSIGNEE_TYPES_NEED_LIST = ('user', 'role', 'department')
def validate_flow_definition_names(flow_definition: dict) -> None:
"""
校验 flow_definition 中节点与分支名称长度。
Raises:
ValueError: 名称超过限制时
"""
if not flow_definition or not isinstance(flow_definition, dict):
return
nodes = flow_definition.get('nodes')
if not nodes or not isinstance(nodes, dict):
return
def check_name(name: str, label: str) -> None:
if name and len(name) > MAX_NODE_NAME_LENGTH:
raise ValueError(
f'{label}名称不能超过 {MAX_NODE_NAME_LENGTH} 个字符'
)
def walk(node: dict) -> None:
if not node or not isinstance(node, dict):
return
node_label = node.get('name') or node.get('id') or node.get('type') or '节点'
check_name(node.get('name') or '', node_label)
for branch in node.get('branches') or []:
if not isinstance(branch, dict):
continue
branch_label = branch.get('name') or branch.get('id') or '分支'
check_name(branch.get('name') or '', branch_label)
config = branch.get('config') or {}
if isinstance(config, dict):
config_name = config.get('name') or ''
check_name(config_name, branch_label)
child = branch.get('children')
if child:
walk(child)
child = node.get('children')
if child:
walk(child)
walk(nodes)
def _node_label(node: dict) -> str:
return node.get('name') or node.get('id') or node.get('type') or '节点'
def _find_nodes_of_type(node: Optional[dict], node_type: str) -> List[dict]:
if not node or not isinstance(node, dict):
return []
result: List[dict] = []
if node.get('type') == node_type:
result.append(node)
children = node.get('children')
if children:
result.extend(_find_nodes_of_type(children, node_type))
for branch in node.get('branches') or []:
if isinstance(branch, dict) and branch.get('children'):
result.extend(_find_nodes_of_type(branch['children'], node_type))
return result
def _assignees_configured(config: dict) -> bool:
if not isinstance(config, dict):
return False
assignee_type = config.get('assigneeType') or 'user'
assignees = config.get('assignees') or []
assignee_fields = config.get('assigneeFields') or []
assignee_field = config.get('assigneeField') or ''
if assignee_type in _ASSIGNEE_TYPES_NEED_LIST:
return len(assignees) > 0
if assignee_type == 'form_field':
return len(assignee_fields) > 0 or bool(assignee_field)
# superior / manager / initiator 等无需显式配置列表
return True
def validate_flow_definition_for_publish(flow_definition: dict) -> None:
"""
发布前校验流程定义结构(与设计器 useFlowValidation 核心规则对齐)。
Raises:
ValueError: 不满足发布条件时
"""
if not flow_definition or not isinstance(flow_definition, dict):
raise ValueError('流程定义为空')
root = flow_definition.get('nodes')
if not root or not isinstance(root, dict):
raise ValueError('流程定义缺少有效节点')
approval_nodes = _find_nodes_of_type(root, 'approval')
if not approval_nodes:
raise ValueError('流程必须包含至少一个审批节点')
for node in approval_nodes:
label = _node_label(node)
if not _assignees_configured(node.get('config') or {}):
raise ValueError(f'审批节点「{label}」未配置审批人')
for node in _find_nodes_of_type(root, 'handle'):
label = _node_label(node)
if not _assignees_configured(node.get('config') or {}):
raise ValueError(f'办理节点「{label}」未配置办理人')
for node in _find_nodes_of_type(root, 'condition'):
label = _node_label(node)
branches = node.get('branches') or []
if not branches:
raise ValueError(f'条件节点「{label}」未配置分支')
has_default = False
for branch in branches:
if not isinstance(branch, dict):
continue
config = branch.get('config') or {}
if config.get('isDefault'):
has_default = True
continue
groups = config.get('groups') or []
branch_label = branch.get('name') or branch.get('id') or '分支'
if not groups:
raise ValueError(
f'条件节点「{label}」的分支「{branch_label}」未配置条件'
)
if not has_default:
raise ValueError(f'条件节点「{label}」缺少默认分支')
async def validate_flow_definition_subflows_for_publish(
db: AsyncSession,
flow_definition: dict,
) -> None:
"""
校验子流程节点引用的流程已发布。
Raises:
ValueError: 子流程未配置或未发布时
"""
from online_dev.workflow.model import WorkflowDefinition
root = (flow_definition or {}).get('nodes')
if not root:
return
for node in _find_nodes_of_type(root, 'subflow'):
label = _node_label(node)
config = node.get('config') or {}
subflow_id = (config.get('subflowId') or '').strip()
if not subflow_id:
raise ValueError(f'子流程节点「{label}」未选择子流程')
stmt = select(WorkflowDefinition).where(
WorkflowDefinition.id == subflow_id,
WorkflowDefinition.is_deleted == False,
)
result = await db.execute(stmt)
child = result.scalar_one_or_none()
if not child:
raise ValueError(f'子流程节点「{label}」引用的流程不存在')
if child.status != 'published':
child_name = child.name or subflow_id
raise ValueError(
f'子流程节点「{label}」引用的流程「{child_name}」尚未发布'
)