feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
#!/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}」尚未发布'
|
||||
)
|
||||
Reference in New Issue
Block a user