#!/usr/bin/env python # -*- coding: utf-8 -*- """ 工作流延时节点处理器 使用 APScheduler 实现延时等待功能 """ import json import logging from datetime import datetime, timedelta from typing import Optional from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession logger = logging.getLogger(__name__) def calculate_delay_datetime(duration: int, unit: str) -> datetime: """ 计算延时后的执行时间 Args: duration: 延时时长 unit: 延时单位 - minute/hour/day/workday Returns: 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) elif unit == 'workday': # 工作日计算(跳过周末) result = now days_added = 0 while days_added < duration: result += timedelta(days=1) # 周一到周五是工作日 (0-4) if result.weekday() < 5: days_added += 1 return result else: # 默认按小时 return now + timedelta(hours=duration) async def create_delay_job( db: AsyncSession, instance_id: str, node_id: str, duration: int, unit: str, user_id: str, ) -> Optional[str]: """ 创建延时任务 Args: db: 数据库会话 instance_id: 流程实例ID node_id: 延时节点ID duration: 延时时长 unit: 延时单位 user_id: 操作用户ID Returns: str: 任务编码,失败返回 None """ try: from scheduler.model import SchedulerJob from scheduler.service import SchedulerService # 计算执行时间 run_date = calculate_delay_datetime(duration, unit) # 生成唯一的任务编码 job_code = f"workflow_delay_{instance_id}_{node_id}" # 检查是否已存在 stmt = select(SchedulerJob).where(SchedulerJob.code == job_code) result = await db.execute(stmt) existing = result.scalar_one_or_none() if existing: # 更新执行时间 existing.run_date = run_date existing.status = 1 # 启用 db.add(existing) await db.flush() # 更新调度器中的任务 scheduler_service = SchedulerService() if scheduler_service.is_running(): await scheduler_service.modify_job(existing) logger.info(f"更新延时任务: {job_code}, 执行时间: {run_date}") return job_code # 创建新任务 job = SchedulerJob( name=f"工作流延时-{instance_id[:8]}", code=job_code, description=f"流程实例 {instance_id} 的延时节点 {node_id}", group='workflow_delay', trigger_type='date', run_date=run_date, task_func='core.workflow.engine.delay_process.execute_delay_complete', task_kwargs=json.dumps({ 'instance_id': instance_id, 'node_id': node_id, 'user_id': user_id, }), status=1, # 启用 max_instances=1, coalesce=True, ) db.add(job) await db.flush() # 添加到调度器 scheduler_service = SchedulerService() if scheduler_service.is_running(): await scheduler_service.add_job(job) logger.info(f"创建延时任务: {job_code}, 执行时间: {run_date}") return job_code except Exception as e: logger.error(f"创建延时任务失败: {e}") return None async def cancel_delay_job(db: AsyncSession, instance_id: str, node_id: str) -> bool: """ 取消延时任务 Args: db: 数据库会话 instance_id: 流程实例ID node_id: 延时节点ID Returns: bool: 是否成功 """ try: from scheduler.model import SchedulerJob from scheduler.service import SchedulerService job_code = f"workflow_delay_{instance_id}_{node_id}" # 从调度器移除 scheduler_service = SchedulerService() if scheduler_service.is_running(): await scheduler_service.remove_job(job_code) # 禁用数据库记录 stmt = update(SchedulerJob).where( SchedulerJob.code == job_code ).values(status=0) await db.execute(stmt) await db.flush() logger.info(f"取消延时任务: {job_code}") return True except Exception as e: logger.error(f"取消延时任务失败: {e}") return False async def execute_delay_complete(db: AsyncSession, instance_id: str, node_id: str, user_id: str): """ 延时完成后执行的任务 Args: db: 数据库会话 instance_id: 流程实例ID node_id: 延时节点ID user_id: 操作用户ID """ from online_dev.workflow.model import WorkflowInstance, WorkflowDefinition, WorkflowLog from online_dev.workflow.engine.workflow_engine import WorkflowEngine from online_dev.workflow.engine.base import ExecutionContext from online_dev.workflow.engine.utils import FlowUtils from core.user.model import User logger.info(f"延时任务执行: instance={instance_id}, node={node_id}") try: # 获取流程实例 stmt = select(WorkflowInstance).where( WorkflowInstance.id == instance_id, WorkflowInstance.is_deleted == False, ) result = await db.execute(stmt) instance = result.scalar_one_or_none() if not instance: logger.error(f"流程实例不存在: {instance_id}") return f"流程实例不存在: {instance_id}" # 检查流程状态 if instance.status != 'pending': logger.warning(f"流程实例 {instance_id} 状态不是 pending,跳过延时完成") return f"流程状态不是 pending: {instance.status}" # 检查当前节点 if instance.current_node_id != node_id: logger.warning(f"流程实例 {instance_id} 当前节点不是 {node_id},跳过延时完成") return f"当前节点不匹配: {instance.current_node_id}" # 获取流程定义 stmt = select(WorkflowDefinition).where(WorkflowDefinition.id == instance.workflow_id) result = await db.execute(stmt) workflow = result.scalar_one_or_none() if not workflow: logger.error(f"流程定义不存在: {instance.workflow_id}") return f"流程定义不存在" # 获取用户 stmt = select(User).where(User.id == user_id) result = await db.execute(stmt) user = result.scalar_one_or_none() if not user: # 使用发起人 stmt = select(User).where(User.id == instance.initiator_id) result = await db.execute(stmt) user = result.scalar_one_or_none() # 记录延时完成日志 log = WorkflowLog( instance_id=str(instance.id), node_id=node_id, node_name=instance.current_node_name, action='delay_complete', operator_id=str(user.id) if user else '', comment='延时等待完成', ) db.add(log) await db.flush() # 创建执行上下文 engine = WorkflowEngine() context = ExecutionContext( instance=instance, form_data={}, current_user=user, flow_definition=workflow.flow_definition, db=db, ) # 查找延时节点并推进 delay_node = FlowUtils.find_node_by_id(context.flow_definition, node_id) if delay_node: await engine._advance_to_next(context, delay_node) logger.info(f"延时节点 {node_id} 完成,流程继续推进") return "延时完成,流程已推进" else: logger.error(f"找不到延时节点: {node_id}") return f"找不到延时节点: {node_id}" except Exception as e: logger.exception(f"延时任务执行失败: {e}") raise