222 lines
6.7 KiB
Python
222 lines
6.7 KiB
Python
#!/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)
|