478 lines
18 KiB
Python
478 lines
18 KiB
Python
|
"""
|
|||
|
子任务组件类
|
|||
|
提供调用任务相关逻辑组件工具
|
|||
|
"""
|
|||
|
import json
|
|||
|
import logging
|
|||
|
import uuid
|
|||
|
import asyncio
|
|||
|
from typing import Dict, Any, List, Optional
|
|||
|
from datetime import datetime
|
|||
|
from services.execution.task_context import TaskContext
|
|||
|
from .base import BlockHandler, register_handler
|
|||
|
from data.enum.task_record_enum import TaskStatus
|
|||
|
from data.enum.task_def_enum import EnableStatus, PeriodicTaskStatus, TaskStatusEnum
|
|||
|
from utils.logger import get_logger
|
|||
|
from .model.block_name import SubTaskBlockName
|
|||
|
logger = get_logger("services.execution.handlers.sub_task")
|
|||
|
|
|||
|
# 提取公共的任务提交函数
|
|||
|
async def submit_subtask(
|
|||
|
subtask_id: str,
|
|||
|
params_list: List[Dict[str, Any]],
|
|||
|
parent_task_id: str,
|
|||
|
root_task_id: str,
|
|||
|
source_info: Dict[str, Any],
|
|||
|
specified_task_record_id: str = None,
|
|||
|
is_async: bool = False
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""
|
|||
|
提交子任务到调度器
|
|||
|
|
|||
|
Args:
|
|||
|
subtask_id: 子任务定义ID
|
|||
|
params_list: 子任务参数列表
|
|||
|
parent_task_id: 父任务ID
|
|||
|
root_task_id: 根任务ID
|
|||
|
source_info: 来源信息
|
|||
|
specified_task_record_id: 指定的任务记录ID
|
|||
|
is_async: 是否异步执行
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 提交结果
|
|||
|
"""
|
|||
|
from sqlalchemy import select, update
|
|||
|
from data.models.taskdef import VWEDTaskDef
|
|||
|
from data.session import get_async_session
|
|||
|
from services.enhanced_scheduler import scheduler
|
|||
|
|
|||
|
try:
|
|||
|
task_record_id = None
|
|||
|
|
|||
|
# 查询子任务定义
|
|||
|
async with get_async_session() as session:
|
|||
|
task_def_result = await session.execute(
|
|||
|
select(VWEDTaskDef).where(VWEDTaskDef.id == subtask_id)
|
|||
|
)
|
|||
|
task_def = task_def_result.scalars().first()
|
|||
|
|
|||
|
if not task_def:
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"找不到子任务定义: {subtask_id}"
|
|||
|
}
|
|||
|
|
|||
|
# 检查是否为定时任务,如果是定时任务则确保启用
|
|||
|
if task_def.periodic_task == PeriodicTaskStatus.PERIODIC and task_def.if_enable != EnableStatus.ENABLED:
|
|||
|
logger.info(f"子任务 {subtask_id} 是定时任务但未启用,正在启用...")
|
|||
|
# 更新任务定义状态为启用
|
|||
|
await session.execute(
|
|||
|
update(VWEDTaskDef)
|
|||
|
.where(VWEDTaskDef.id == subtask_id)
|
|||
|
.values(if_enable=EnableStatus.ENABLED, status=TaskStatusEnum.RUNNING)
|
|||
|
)
|
|||
|
await session.commit()
|
|||
|
|
|||
|
# 通知调度器更新定时任务状态
|
|||
|
update_result = await scheduler.update_periodic_task(subtask_id, True)
|
|||
|
if not update_result.get("success", False):
|
|||
|
logger.warning(f"启用子任务定时任务失败: {update_result.get('message', '未知错误')}")
|
|||
|
|
|||
|
# 处理指定任务ID的情况
|
|||
|
if specified_task_record_id:
|
|||
|
create_result = await create_task_record(
|
|||
|
specified_task_record_id,
|
|||
|
subtask_id,
|
|||
|
task_def,
|
|||
|
params_list,
|
|||
|
parent_task_id,
|
|||
|
root_task_id,
|
|||
|
source_info
|
|||
|
)
|
|||
|
|
|||
|
if not create_result.get("success", False):
|
|||
|
return create_result
|
|||
|
|
|||
|
# 使用调度器提交任务
|
|||
|
task_record_id = specified_task_record_id
|
|||
|
submit_result = await scheduler.submit_task(task_record_id)
|
|||
|
else:
|
|||
|
# 让调度器创建并执行任务
|
|||
|
run_result = await scheduler.run_task(
|
|||
|
task_def_id=subtask_id,
|
|||
|
params=params_list,
|
|||
|
parent_task_id=parent_task_id,
|
|||
|
root_task_id=root_task_id,
|
|||
|
source_type=source_info.get("source_type"),
|
|||
|
source_system=source_info.get("source_system"),
|
|||
|
source_device=source_info.get("source_device"),
|
|||
|
source_ip=source_info.get("source_ip"),
|
|||
|
source_time=datetime.now(),
|
|||
|
source_client_info=source_info.get("source_client_info")
|
|||
|
)
|
|||
|
|
|||
|
if not run_result.get("success", False):
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"调度器创建子任务失败: {run_result.get('message', '未知错误')}"
|
|||
|
}
|
|||
|
|
|||
|
task_record_id = run_result.get("taskRecordId")
|
|||
|
submit_result = run_result.get("queueResult", {})
|
|||
|
|
|||
|
# 构造返回结果
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": f"子任务已提交到调度器: {task_record_id}",
|
|||
|
"taskRecordId": task_record_id,
|
|||
|
"submitResult": submit_result,
|
|||
|
"isAsync": is_async
|
|||
|
}
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"提交子任务异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"提交子任务异常: {str(e)}"
|
|||
|
}
|
|||
|
|
|||
|
async def create_task_record(
|
|||
|
task_record_id: str,
|
|||
|
subtask_id: str,
|
|||
|
task_def: Any,
|
|||
|
params_list: List[Dict[str, Any]],
|
|||
|
parent_task_id: str,
|
|||
|
root_task_id: str,
|
|||
|
source_info: Dict[str, Any]
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""
|
|||
|
创建任务记录
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务记录ID
|
|||
|
subtask_id: 子任务ID
|
|||
|
task_def: 任务定义对象
|
|||
|
params_list: 参数列表
|
|||
|
parent_task_id: 父任务ID
|
|||
|
root_task_id: 根任务ID
|
|||
|
source_info: 来源信息
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 创建结果
|
|||
|
"""
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from data.session import get_async_session
|
|||
|
|
|||
|
try:
|
|||
|
# 检查指定的任务记录是否已存在
|
|||
|
async with get_async_session() as session:
|
|||
|
from sqlalchemy import select
|
|||
|
existing_check = await session.execute(
|
|||
|
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
if existing_check.scalars().first():
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"指定的任务记录ID已存在: {task_record_id}"
|
|||
|
}
|
|||
|
|
|||
|
# 创建任务记录
|
|||
|
task_record = VWEDTaskRecord(
|
|||
|
id=task_record_id,
|
|||
|
def_id=subtask_id,
|
|||
|
def_label=task_def.label,
|
|||
|
def_version=task_def.version,
|
|||
|
parent_task_record_id=parent_task_id,
|
|||
|
root_task_record_id=root_task_id,
|
|||
|
status=TaskStatus.QUEUED, # 队列中
|
|||
|
created_on=datetime.now(),
|
|||
|
input_params=json.dumps(params_list, ensure_ascii=False) if params_list else None,
|
|||
|
periodic_task=task_def.periodic_task or PeriodicTaskStatus.NON_PERIODIC,
|
|||
|
task_def_detail=task_def.detail,
|
|||
|
source_type=source_info.get("source_type"),
|
|||
|
source_system=source_info.get("source_system"),
|
|||
|
source_device=source_info.get("source_device"),
|
|||
|
source_ip=source_info.get("source_ip"),
|
|||
|
source_client_info=source_info.get("source_client_info"),
|
|||
|
source_time=datetime.now()
|
|||
|
)
|
|||
|
session.add(task_record)
|
|||
|
await session.commit()
|
|||
|
|
|||
|
return {
|
|||
|
"success": True,
|
|||
|
"message": f"任务记录创建成功: {task_record_id}",
|
|||
|
"taskRecordId": task_record_id
|
|||
|
}
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建任务记录异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"创建任务记录异常: {str(e)}"
|
|||
|
}
|
|||
|
|
|||
|
async def wait_for_task_completion(task_record_id: str, max_wait_time: int = 3600, wait_interval: int = 3) -> Dict[str, Any]:
|
|||
|
"""
|
|||
|
等待任务完成
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 任务记录ID
|
|||
|
max_wait_time: 最大等待时间(秒)
|
|||
|
wait_interval: 检查间隔(秒)
|
|||
|
|
|||
|
Returns:
|
|||
|
Dict[str, Any]: 任务完成结果
|
|||
|
"""
|
|||
|
from services.enhanced_scheduler import scheduler
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from data.session import get_async_session
|
|||
|
|
|||
|
try:
|
|||
|
total_waited = 0
|
|||
|
|
|||
|
# 等待任务完成
|
|||
|
while total_waited < max_wait_time:
|
|||
|
# 检查任务状态
|
|||
|
status_result = await scheduler.get_task_status(task_record_id)
|
|||
|
|
|||
|
if not status_result.get("success", False):
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"获取子任务状态失败: {status_result.get('message', '未知错误')}",
|
|||
|
"taskRecordId": task_record_id
|
|||
|
}
|
|||
|
|
|||
|
task_status = status_result.get("data", {}).get("status")
|
|||
|
|
|||
|
# 如果任务已完成或失败或取消,则退出等待
|
|||
|
if task_status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELED]: # 完成、失败、取消
|
|||
|
break
|
|||
|
|
|||
|
# 等待一段时间
|
|||
|
await asyncio.sleep(wait_interval)
|
|||
|
total_waited += wait_interval
|
|||
|
|
|||
|
# 获取最终任务状态
|
|||
|
final_status = await scheduler.get_task_status(task_record_id)
|
|||
|
task_data = final_status.get("data", {})
|
|||
|
task_status = task_data.get("status")
|
|||
|
|
|||
|
if task_status in [TaskStatus.COMPLETED, TaskStatus.CANCELED]: # 完成或取消
|
|||
|
success = True
|
|||
|
message = "子任务执行成功"
|
|||
|
else:
|
|||
|
success = False
|
|||
|
message = f"子任务执行失败: {task_data.get('endedReason', '未知原因')}"
|
|||
|
|
|||
|
# # 从数据库获取任务执行结果
|
|||
|
# async with get_async_session() as session:
|
|||
|
# from sqlalchemy import select
|
|||
|
# result = await session.execute(
|
|||
|
# select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
# )
|
|||
|
# record = result.scalars().first()
|
|||
|
|
|||
|
# if record and record.result:
|
|||
|
# try:
|
|||
|
# output = json.loads(record.result)
|
|||
|
# except Exception:
|
|||
|
# output = {}
|
|||
|
# else:
|
|||
|
# output = {}
|
|||
|
output = {"success": True, "message": message, "taskRecordId": task_record_id, "output": {}}
|
|||
|
return {
|
|||
|
"success": success,
|
|||
|
"message": message,
|
|||
|
"taskStatus": task_status,
|
|||
|
"taskRecordId": task_record_id,
|
|||
|
"output": output
|
|||
|
}
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"等待任务完成异常: {str(e)}")
|
|||
|
return {
|
|||
|
"success": False,
|
|||
|
"message": f"等待任务完成异常: {str(e)}",
|
|||
|
"taskRecordId": task_record_id
|
|||
|
}
|
|||
|
|
|||
|
@register_handler(SubTaskBlockName.SUB_TASK)
|
|||
|
class SubTaskBlockHandler(BlockHandler):
|
|||
|
"""子任务块处理器"""
|
|||
|
|
|||
|
async def execute(
|
|||
|
self,
|
|||
|
block: Dict[str, Any],
|
|||
|
input_params: Dict[str, Any],
|
|||
|
context: TaskContext
|
|||
|
) -> Dict[str, Any]:
|
|||
|
"""执行子任务块"""
|
|||
|
try:
|
|||
|
# 获取子任务ID
|
|||
|
subtask_id = block.get("refTaskDefId")
|
|||
|
|
|||
|
if not subtask_id:
|
|||
|
result = {
|
|||
|
"success": False,
|
|||
|
"message": "缺少子任务ID"
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
|
|||
|
# 获取执行参数(确保默认值)
|
|||
|
is_async = input_params.get("ifAsync", False) # 是否异步执行,默认False
|
|||
|
specified_task_record_id = input_params.get("taskRecordId", None) # 指定的任务记录ID,默认None
|
|||
|
|
|||
|
# 从input_params中提取出子任务需要的参数
|
|||
|
# 排除控制参数
|
|||
|
control_params = ["ifAsync", "taskRecordId"]
|
|||
|
subtask_params = {}
|
|||
|
for key, value in input_params.items():
|
|||
|
if key not in control_params:
|
|||
|
subtask_params[key] = value
|
|||
|
|
|||
|
# 将字典转换为列表形式的参数,与task_edit_service保持一致
|
|||
|
params_list = []
|
|||
|
for key, value in subtask_params.items():
|
|||
|
params_list.append({
|
|||
|
"name": key,
|
|||
|
"type": "字符串", # 默认类型,后续可根据实际类型修改
|
|||
|
"label": key,
|
|||
|
"required": False,
|
|||
|
"defaultValue": str(value) if value is not None else ""
|
|||
|
})
|
|||
|
|
|||
|
logger.info(f"开始执行子任务: {subtask_id}, 异步执行: {is_async}, 指定ID: {specified_task_record_id}")
|
|||
|
|
|||
|
# 获取根任务记录ID - 如果当前任务有父任务,则根任务是父任务的根任务,否则是当前任务
|
|||
|
root_task_record_id = context.get_variable("rootTaskRecordId", context.task_record_id)
|
|||
|
|
|||
|
# 获取父任务的来源信息
|
|||
|
source_info = await self._get_parent_source_info(context.task_record_id)
|
|||
|
|
|||
|
if not source_info:
|
|||
|
result = {
|
|||
|
"success": False,
|
|||
|
"message": f"获取父任务来源信息失败: {context.task_record_id}"
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
|
|||
|
# 提交子任务到调度器
|
|||
|
submit_result = await submit_subtask(
|
|||
|
subtask_id=subtask_id,
|
|||
|
params_list=params_list,
|
|||
|
parent_task_id=context.task_record_id,
|
|||
|
root_task_id=root_task_record_id,
|
|||
|
source_info=source_info,
|
|||
|
specified_task_record_id=specified_task_record_id,
|
|||
|
is_async=is_async
|
|||
|
)
|
|||
|
|
|||
|
if not submit_result.get("success", False):
|
|||
|
result = {
|
|||
|
"success": False,
|
|||
|
"message": submit_result.get("message", "提交子任务失败")
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
|
|||
|
task_record_id = submit_result.get("taskRecordId")
|
|||
|
|
|||
|
# 根据是否异步执行采取不同的处理方式
|
|||
|
if is_async:
|
|||
|
# 异步执行:不等待任务完成
|
|||
|
result = {
|
|||
|
"success": True,
|
|||
|
"message": f"子任务已异步提交到调度器: {task_record_id}",
|
|||
|
"output": {
|
|||
|
"subtaskId": subtask_id,
|
|||
|
"taskRecordId": task_record_id,
|
|||
|
"async": True,
|
|||
|
"queueStatus": submit_result.get("submitResult", {})
|
|||
|
}
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
else:
|
|||
|
# 同步执行:等待任务执行完成
|
|||
|
completion_result = await wait_for_task_completion(task_record_id)
|
|||
|
|
|||
|
success = completion_result.get("success", False)
|
|||
|
message = completion_result.get("message", "子任务执行完成")
|
|||
|
output = completion_result.get("output", {})
|
|||
|
task_status = completion_result.get("taskStatus")
|
|||
|
|
|||
|
# 将子任务输出合并到当前上下文变量
|
|||
|
if isinstance(output, dict):
|
|||
|
for key, value in output.items():
|
|||
|
variable_name = f"subtask_{key}"
|
|||
|
context.set_variable(variable_name, value)
|
|||
|
|
|||
|
# 记录子任务结果到上下文变量
|
|||
|
context.set_variable("subtaskResult", {
|
|||
|
"success": success,
|
|||
|
"message": message,
|
|||
|
"output": output,
|
|||
|
"taskStatus": task_status
|
|||
|
})
|
|||
|
|
|||
|
# 执行完成后记录日志
|
|||
|
result = {
|
|||
|
"success": success,
|
|||
|
"message": message,
|
|||
|
"output": {
|
|||
|
"subtaskId": subtask_id,
|
|||
|
"taskRecordId": task_record_id,
|
|||
|
"async": False,
|
|||
|
"output": output,
|
|||
|
"taskStatus": task_status
|
|||
|
}
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
result = {
|
|||
|
"success": False,
|
|||
|
"message": f"子任务执行异常: {str(e)}"
|
|||
|
}
|
|||
|
await self._record_task_log(block, result, context)
|
|||
|
return result
|
|||
|
|
|||
|
async def _get_parent_source_info(self, task_record_id: str) -> Optional[Dict[str, Any]]:
|
|||
|
"""
|
|||
|
获取父任务的来源信息
|
|||
|
|
|||
|
Args:
|
|||
|
task_record_id: 父任务ID
|
|||
|
|
|||
|
Returns:
|
|||
|
Optional[Dict[str, Any]]: 来源信息
|
|||
|
"""
|
|||
|
from sqlalchemy import select
|
|||
|
from data.models.taskrecord import VWEDTaskRecord
|
|||
|
from data.session import get_async_session
|
|||
|
|
|||
|
try:
|
|||
|
async with get_async_session() as session:
|
|||
|
result = await session.execute(
|
|||
|
select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id)
|
|||
|
)
|
|||
|
parent_record = result.scalars().first()
|
|||
|
|
|||
|
if not parent_record:
|
|||
|
logger.error(f"找不到父任务记录: {task_record_id}")
|
|||
|
return None
|
|||
|
|
|||
|
# 获取父任务的来源信息
|
|||
|
return {
|
|||
|
"source_type": parent_record.source_type,
|
|||
|
"source_system": parent_record.source_system,
|
|||
|
"source_device": parent_record.source_device,
|
|||
|
"source_ip": parent_record.source_ip,
|
|||
|
"source_client_info": parent_record.source_client_info
|
|||
|
}
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"获取父任务来源信息异常: {str(e)}")
|
|||
|
return None
|
|||
|
|