Files
ai-agent-admin/backend-fastapi/online_dev/workflow/engine/handlers/base_handler.py
T

222 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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: 操作人IDNone时使用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)