#!/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)