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

273 lines
8.3 KiB
Python

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