#!/usr/bin/env python # -*- coding: utf-8 -*- """ 延时节点处理器 处理流程延时等待 """ import logging from datetime import datetime, timedelta from typing import Dict, TYPE_CHECKING from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler if TYPE_CHECKING: from online_dev.workflow.engine.base import ExecutionContext logger = logging.getLogger(__name__) class DelayNodeHandler(BaseNodeHandler): """ 延时节点处理器 支持延时单位: - minute: 分钟 - hour: 小时 - day: 天 - workday: 工作日(跳过周六日) """ async def execute(self, context: 'ExecutionContext', node: Dict) -> None: """ 执行延时节点:计算到期时间,注册定时任务 """ node_id = self.get_node_id(node) node_name = self.get_node_name(node) node_config = self.get_node_config(node) duration = node_config.get('duration', 1) unit = node_config.get('unit', 'hour') unit_names = { 'minute': '分钟', 'hour': '小时', 'day': '天', 'workday': '工作日', } unit_name = unit_names.get(unit, unit) logger.info(f"延时节点 - 等待 {duration} {unit_name}") # 更新实例当前节点 await self.update_instance_node(context, node) # 计算到期时间 delay_until = self._calculate_delay_until(duration, unit) # 更新实例延时状态 context.instance.delay_node_id = node_id context.instance.delay_until = delay_until context.db.add(context.instance) await context.db.flush() # 记录延时开始日志 await self.create_log( context, node, 'delay_start', comment=f'开始延时等待 {duration} {unit_name},预计 {delay_until.strftime("%Y-%m-%d %H:%M:%S")} 恢复', extra_data={ 'duration': duration, 'unit': unit, 'delay_until': delay_until.isoformat(), }, operator_id='', ) # 注册定时任务 from online_dev.workflow.engine.delay_callback import _register_delay_job instance_id = str(context.instance.id) success = await _register_delay_job(instance_id, node_id, delay_until) if not success: # 调度器不可用时,直接跳过延时继续推进(降级处理) logger.warning(f"调度器不可用,延时节点直接跳过: instance={instance_id}, node={node_id}") # 清除延时状态 context.instance.delay_node_id = "" context.instance.delay_until = None context.db.add(context.instance) await context.db.flush() await self.create_log( context, node, 'delay_skip', comment='调度器不可用,延时跳过', operator_id='', ) await self.advance_to_next(context, node) @staticmethod def _calculate_delay_until(duration: int, unit: str) -> datetime: """ 计算延时到期时间 Args: duration: 延时时长 unit: 延时单位 (minute/hour/day/workday) Returns: 到期时间 """ 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) elif unit == 'workday': return DelayNodeHandler._add_workdays(now, duration) else: # 默认按小时处理 return now + timedelta(hours=duration) @staticmethod def _add_workdays(start: datetime, days: int) -> datetime: """ 添加工作日(跳过周六日) Args: start: 起始时间 days: 工作日天数 Returns: 目标时间(保持原始时分秒) """ current = start added = 0 while added < days: current += timedelta(days=1) # weekday(): 0=周一, 5=周六, 6=周日 if current.weekday() < 5: added += 1 return current