feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user