feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
节点处理器基类
|
||||
定义所有节点处理器的通用接口和方法
|
||||
"""
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from online_dev.workflow.engine.base import ExecutionContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 与前端 NODE_TYPE_CONFIGS 默认名称保持一致
|
||||
DEFAULT_NODE_NAMES = {
|
||||
'start': '发起人',
|
||||
'approval': '审批人',
|
||||
'handle': '办理人',
|
||||
'copy': '抄送人',
|
||||
'delay': '延时等待',
|
||||
'notify': '发送通知',
|
||||
'service': '服务调用',
|
||||
'subflow': '子流程',
|
||||
'data_update': '字段更新',
|
||||
'condition': '条件分支',
|
||||
'parallel': '并行分支',
|
||||
'route': '路由',
|
||||
'end': '结束',
|
||||
}
|
||||
|
||||
|
||||
class BaseNodeHandler(ABC):
|
||||
"""
|
||||
节点处理器基类
|
||||
|
||||
所有节点处理器都应继承此类并实现 execute 方法
|
||||
"""
|
||||
|
||||
def __init__(self, engine: Any):
|
||||
"""
|
||||
初始化处理器
|
||||
|
||||
Args:
|
||||
engine: 工作流引擎实例,用于调用引擎方法
|
||||
"""
|
||||
self.engine = engine
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, context: 'ExecutionContext', node: Dict) -> None:
|
||||
"""
|
||||
执行节点逻辑
|
||||
|
||||
Args:
|
||||
context: 执行上下文
|
||||
node: 节点配置
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_node_config(self, node: Dict) -> Dict:
|
||||
"""获取节点配置"""
|
||||
return node.get('config', {})
|
||||
|
||||
def get_node_id(self, node: Dict) -> str:
|
||||
"""获取节点ID"""
|
||||
return node.get('id', '')
|
||||
|
||||
def get_node_name(self, node: Dict) -> str:
|
||||
"""获取节点名称(空名时回退为类型默认名)"""
|
||||
name = (node.get('name') or '').strip()
|
||||
if name:
|
||||
return name
|
||||
node_type = node.get('type', '')
|
||||
return DEFAULT_NODE_NAMES.get(node_type, node_type or '节点')
|
||||
|
||||
def get_node_type(self, node: Dict) -> str:
|
||||
"""获取节点类型"""
|
||||
return node.get('type', '')
|
||||
|
||||
async def update_instance_node(self, context: 'ExecutionContext', node: Dict) -> None:
|
||||
"""更新实例当前节点"""
|
||||
context.instance.current_node_id = self.get_node_id(node)
|
||||
context.instance.current_node_name = self.get_node_name(node)
|
||||
context.db.add(context.instance)
|
||||
await context.db.flush()
|
||||
|
||||
async def create_log(
|
||||
self,
|
||||
context: 'ExecutionContext',
|
||||
node: Dict,
|
||||
action: str,
|
||||
comment: str = '',
|
||||
extra_data: Dict = None,
|
||||
operator_id: str = None,
|
||||
) -> None:
|
||||
"""
|
||||
创建流程日志
|
||||
|
||||
Args:
|
||||
context: 执行上下文
|
||||
node: 节点配置
|
||||
action: 操作类型
|
||||
comment: 备注
|
||||
extra_data: 额外数据
|
||||
operator_id: 操作人ID,None时使用context.current_user_id,传空字符串表示系统自动执行
|
||||
"""
|
||||
from online_dev.workflow.model import WorkflowLog
|
||||
|
||||
log = WorkflowLog(
|
||||
instance_id=str(context.instance.id),
|
||||
node_id=self.get_node_id(node),
|
||||
node_name=self.get_node_name(node),
|
||||
action=action,
|
||||
operator_id=operator_id if operator_id is not None else (context.current_user_id or ''),
|
||||
comment=comment,
|
||||
extra_data=extra_data or {},
|
||||
)
|
||||
context.db.add(log)
|
||||
await context.db.flush()
|
||||
|
||||
async def create_task(
|
||||
self,
|
||||
context: 'ExecutionContext',
|
||||
node: Dict,
|
||||
assignee_id: str,
|
||||
task_type: str,
|
||||
) -> Any:
|
||||
"""
|
||||
创建任务
|
||||
|
||||
Args:
|
||||
context: 执行上下文
|
||||
node: 节点配置
|
||||
assignee_id: 处理人ID
|
||||
task_type: 任务类型
|
||||
|
||||
Returns:
|
||||
创建的任务对象
|
||||
"""
|
||||
from online_dev.workflow.model import WorkflowTask
|
||||
from core.user.model import User
|
||||
|
||||
# 验证用户存在
|
||||
stmt = select(User).where(User.id == assignee_id)
|
||||
result = await context.db.execute(stmt)
|
||||
assignee = result.scalar_one_or_none()
|
||||
|
||||
if not assignee:
|
||||
logger.warning(f"用户不存在: {assignee_id}")
|
||||
return None
|
||||
|
||||
# 计算超时时间
|
||||
node_config = self.get_node_config(node)
|
||||
timeout_config = node_config.get('timeout', {})
|
||||
timeout_at = None
|
||||
timeout_action = ''
|
||||
|
||||
if timeout_config.get('enabled'):
|
||||
duration = timeout_config.get('duration', 24)
|
||||
unit = timeout_config.get('unit', 'hour')
|
||||
timeout_action = timeout_config.get('action', 'notify')
|
||||
timeout_at = self._calculate_timeout(duration, unit)
|
||||
|
||||
task = WorkflowTask(
|
||||
instance_id=str(context.instance.id),
|
||||
node_id=self.get_node_id(node),
|
||||
node_name=self.get_node_name(node),
|
||||
task_type=task_type,
|
||||
status='pending',
|
||||
assignee_id=assignee_id,
|
||||
timeout_at=timeout_at,
|
||||
timeout_action=timeout_action,
|
||||
)
|
||||
context.db.add(task)
|
||||
await context.db.flush()
|
||||
|
||||
# 发送通知
|
||||
await self._send_task_notification(context, task, assignee_id, task_type, node)
|
||||
|
||||
return task
|
||||
|
||||
def _calculate_timeout(self, duration: int, unit: str) -> datetime:
|
||||
"""计算超时时间"""
|
||||
now = datetime.now()
|
||||
if unit == 'minute':
|
||||
return now + timedelta(minutes=duration)
|
||||
elif unit == 'hour':
|
||||
return now + timedelta(hours=duration)
|
||||
elif unit == 'day':
|
||||
return now + timedelta(days=duration)
|
||||
return now + timedelta(hours=duration)
|
||||
|
||||
async def _send_task_notification(
|
||||
self,
|
||||
context: 'ExecutionContext',
|
||||
task: Any,
|
||||
assignee_id: str,
|
||||
task_type: str,
|
||||
node: Dict,
|
||||
) -> None:
|
||||
"""发送任务通知"""
|
||||
from online_dev.workflow.engine.handlers.notification_service import WorkflowNotificationService
|
||||
|
||||
try:
|
||||
await WorkflowNotificationService.send_task_notification(
|
||||
context=context,
|
||||
task=task,
|
||||
assignee_id=assignee_id,
|
||||
task_type=task_type,
|
||||
node=node,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"发送任务通知失败: {e}")
|
||||
|
||||
async def advance_to_next(self, context: 'ExecutionContext', current_node: Dict) -> None:
|
||||
"""推进到下一节点(委托给引擎)"""
|
||||
await self.engine._advance_to_next(context, current_node)
|
||||
Reference in New Issue
Block a user