2025-05-12 15:43:21 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
任务运行记录服务模块
|
|
|
|
|
提供任务运行记录相关的服务方法
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
2025-09-30 13:52:36 +08:00
|
|
|
|
from typing import Dict, List, Any
|
|
|
|
|
from sqlalchemy import select
|
2025-05-12 15:43:21 +08:00
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
|
|
from data.models.blockrecord import VWEDBlockRecord
|
|
|
|
|
from data.models.taskrecord import VWEDTaskRecord
|
2025-10-13 14:52:58 +08:00
|
|
|
|
from data.models.taskdef import VWEDTaskDef
|
2025-05-12 15:43:21 +08:00
|
|
|
|
from data.models.tasklog import VWEDTaskLog
|
|
|
|
|
from data.session import get_async_session
|
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
from data.enum.task_block_record_enum import TaskBlockRecordStatus
|
2025-09-09 10:41:27 +08:00
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
# 设置日志
|
|
|
|
|
logger = get_logger("service.task_record_service")
|
|
|
|
|
|
|
|
|
|
class TaskRecordService:
|
|
|
|
|
"""
|
|
|
|
|
任务运行记录服务类
|
|
|
|
|
提供与任务运行记录相关的方法
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def get_task_blocks(task_record_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
获取指定任务记录下的所有块运行情况
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含块运行情况的字典
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 构建查询语句
|
|
|
|
|
query = select(VWEDBlockRecord).where(
|
|
|
|
|
VWEDBlockRecord.task_record_id == task_record_id
|
|
|
|
|
).order_by(VWEDBlockRecord.started_on)
|
|
|
|
|
|
|
|
|
|
# 执行查询
|
|
|
|
|
result = await session.execute(query)
|
|
|
|
|
blocks = result.scalars().all()
|
|
|
|
|
|
|
|
|
|
if not blocks:
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": f"未找到任务记录 {task_record_id} 的块运行情况",
|
|
|
|
|
"data": []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 转换为字典列表
|
|
|
|
|
block_list = []
|
|
|
|
|
for block in blocks:
|
|
|
|
|
block_dict = {
|
|
|
|
|
"id": block.id,
|
|
|
|
|
"block_name": block.block_name,
|
|
|
|
|
"block_id": block.block_id,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"started_on": block.started_on.isoformat() if block.started_on else None,
|
|
|
|
|
"ended_on": block.ended_on.isoformat() if block.ended_on else None,
|
|
|
|
|
"ended_reason": block.ended_reason,
|
|
|
|
|
"block_execute_name": block.block_execute_name,
|
|
|
|
|
"block_input_params_value": json.loads(block.block_input_params_value) if block.block_input_params_value else None,
|
|
|
|
|
"block_out_params_value": json.loads(block.block_out_params_value) if block.block_out_params_value else None,
|
|
|
|
|
"remark": block.remark
|
|
|
|
|
}
|
|
|
|
|
block_list.append(block_dict)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": f"成功获取任务记录 {task_record_id} 的块运行情况",
|
|
|
|
|
"data": block_list
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取任务块运行情况失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"获取任务块运行情况失败: {str(e)}",
|
|
|
|
|
"data": []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def get_block_detail(block_record_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
获取指定块记录的详细信息
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
block_record_id: 块记录ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含块记录详细信息的字典
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 构建查询语句
|
|
|
|
|
query = select(VWEDBlockRecord).where(
|
|
|
|
|
VWEDBlockRecord.id == block_record_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 执行查询
|
|
|
|
|
result = await session.execute(query)
|
|
|
|
|
block = result.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not block:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"未找到ID为 {block_record_id} 的块记录",
|
|
|
|
|
"data": None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 转换为字典
|
|
|
|
|
block_dict = {
|
|
|
|
|
"id": block.id,
|
|
|
|
|
"block_name": block.block_name,
|
|
|
|
|
"block_id": block.block_id,
|
|
|
|
|
"block_config_id": block.block_config_id,
|
|
|
|
|
"block_input_params": json.loads(block.block_input_params) if block.block_input_params else None,
|
|
|
|
|
"block_input_params_value": json.loads(block.block_input_params_value) if block.block_input_params_value else None,
|
|
|
|
|
"block_out_params_value": json.loads(block.block_out_params_value) if block.block_out_params_value else None,
|
|
|
|
|
"block_internal_variables": json.loads(block.block_internal_variables) if block.block_internal_variables else None,
|
|
|
|
|
"block_execute_name": block.block_execute_name,
|
|
|
|
|
"task_id": block.task_id,
|
|
|
|
|
"task_record_id": block.task_record_id,
|
|
|
|
|
"started_on": block.started_on.isoformat() if block.started_on else None,
|
|
|
|
|
"ended_on": block.ended_on.isoformat() if block.ended_on else None,
|
|
|
|
|
"ended_reason": block.ended_reason,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"ctrl_status": block.ctrl_status,
|
|
|
|
|
"input_params": json.loads(block.input_params) if block.input_params else None,
|
|
|
|
|
"internal_variables": json.loads(block.internal_variables) if block.internal_variables else None,
|
|
|
|
|
"output_params": json.loads(block.output_params) if block.output_params else None,
|
|
|
|
|
"version": block.version,
|
|
|
|
|
"remark": block.remark
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "成功获取块记录详情",
|
|
|
|
|
"data": block_dict
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取块记录详情失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"获取块记录详情失败: {str(e)}",
|
|
|
|
|
"data": None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def stop_task_record(task_record_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
停止指定任务记录下的所有运行任务实例,同时禁用定时任务
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含停止结果的响应
|
|
|
|
|
"""
|
|
|
|
|
# 导入增强版调度器
|
|
|
|
|
from services.enhanced_scheduler import scheduler
|
|
|
|
|
from data.enum.task_record_enum import TaskStatus
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 查找所有正在运行的任务记录
|
|
|
|
|
running_tasks_query = await session.execute(
|
|
|
|
|
select(VWEDTaskRecord)
|
|
|
|
|
.where(
|
|
|
|
|
VWEDTaskRecord.id == task_record_id,
|
|
|
|
|
VWEDTaskRecord.status == TaskStatus.RUNNING # 执行中状态码
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
running_tasks = running_tasks_query.scalars().first()
|
|
|
|
|
if not running_tasks:
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "任务记录中没有运行中的任务"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 取消所有运行中的任务
|
|
|
|
|
cancel_result = await scheduler.cancel_task(task_record_id)
|
|
|
|
|
if cancel_result.get("success", False):
|
|
|
|
|
running_tasks.status = TaskStatus.CANCELED
|
|
|
|
|
running_tasks.ended_on = datetime.now()
|
|
|
|
|
running_tasks.ended_reason = "任务终止"
|
|
|
|
|
await session.commit()
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "任务终止成功",
|
|
|
|
|
"data": {
|
|
|
|
|
"task_record_id": task_record_id,
|
|
|
|
|
"status": running_tasks.status,
|
|
|
|
|
"ended_on": running_tasks.ended_on,
|
|
|
|
|
"ended_reason": running_tasks.ended_reason
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": "任务终止失败",
|
|
|
|
|
"data": cancel_result
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"任务记录终止失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"任务记录终止失败: {str(e)}"
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-14 10:29:37 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
async def set_task_error(task_record_id: str, error_reason: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
将指定任务记录及其相关任务块状态设置为错误状态
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
error_reason: 错误原因
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含设置结果的响应
|
|
|
|
|
"""
|
|
|
|
|
from services.enhanced_scheduler import scheduler
|
|
|
|
|
from data.enum.task_record_enum import TaskStatus
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 查找任务记录
|
|
|
|
|
task_query = await session.execute(
|
|
|
|
|
select(VWEDTaskRecord)
|
|
|
|
|
.where(VWEDTaskRecord.id == task_record_id)
|
|
|
|
|
)
|
|
|
|
|
task_record = task_query.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not task_record:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"未找到任务记录 {task_record_id}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 如果任务正在运行,先取消它
|
|
|
|
|
if task_record.status == TaskStatus.RUNNING:
|
2025-08-13 15:27:04 +08:00
|
|
|
|
cancel_result = await scheduler.set_task_error(task_record_id, error_reason)
|
2025-07-14 10:29:37 +08:00
|
|
|
|
if not cancel_result.get("success", False):
|
|
|
|
|
logger.warning(f"取消任务 {task_record_id} 失败: {cancel_result}")
|
2025-08-13 15:27:04 +08:00
|
|
|
|
|
2025-07-14 10:29:37 +08:00
|
|
|
|
# 设置任务状态为失败
|
|
|
|
|
task_record.status = TaskStatus.FAILED
|
|
|
|
|
task_record.ended_on = datetime.now()
|
|
|
|
|
task_record.ended_reason = error_reason
|
|
|
|
|
|
|
|
|
|
# 查找所有相关的任务块记录
|
|
|
|
|
blocks_query = await session.execute(
|
|
|
|
|
select(VWEDBlockRecord)
|
|
|
|
|
.where(VWEDBlockRecord.task_record_id == task_record_id)
|
|
|
|
|
)
|
|
|
|
|
block_records = blocks_query.scalars().all()
|
|
|
|
|
|
|
|
|
|
# 设置所有任务块状态为失败
|
|
|
|
|
updated_blocks = []
|
|
|
|
|
for block in block_records:
|
|
|
|
|
# 只更新未完成的任务块
|
|
|
|
|
if block.status not in [TaskBlockRecordStatus.SUCCESS, TaskBlockRecordStatus.FAILED]:
|
|
|
|
|
block.status = TaskBlockRecordStatus.FAILED
|
|
|
|
|
block.ended_on = datetime.now()
|
|
|
|
|
block.ended_reason = error_reason
|
|
|
|
|
updated_blocks.append({
|
|
|
|
|
"block_id": block.id,
|
|
|
|
|
"block_name": block.block_name,
|
|
|
|
|
"status": block.status
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
# 提交所有更改
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "任务状态设置为错误成功",
|
|
|
|
|
"data": {
|
|
|
|
|
"task_record_id": task_record_id,
|
|
|
|
|
"task_status": task_record.status,
|
|
|
|
|
"error_reason": error_reason,
|
|
|
|
|
"ended_on": task_record.ended_on,
|
|
|
|
|
"updated_blocks_count": len(updated_blocks),
|
|
|
|
|
"updated_blocks": updated_blocks
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"设置任务错误状态失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"设置任务错误状态失败: {str(e)}"
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
@staticmethod
|
2025-10-13 14:52:58 +08:00
|
|
|
|
async def get_block_results(task_record_id: str, use_new_version: bool = True) -> Dict[str, Any]:
|
2025-05-12 15:43:21 +08:00
|
|
|
|
"""
|
2025-09-30 13:52:36 +08:00
|
|
|
|
获取指定任务记录的执行结果,按照任务定义的嵌套结构组织
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
2025-10-13 14:52:58 +08:00
|
|
|
|
use_new_version: 是否使用新版本(基于层级关系字段),默认True
|
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
Returns:
|
2025-09-30 13:52:36 +08:00
|
|
|
|
Dict: 包含嵌套结构执行结果的响应
|
2025-05-12 15:43:21 +08:00
|
|
|
|
"""
|
2025-10-13 14:52:58 +08:00
|
|
|
|
# 使用新版本(基于层级关系字段)
|
|
|
|
|
if use_new_version:
|
|
|
|
|
from services.task_record_service_new import get_block_results_v2
|
|
|
|
|
return await get_block_results_v2(task_record_id)
|
|
|
|
|
|
|
|
|
|
# 旧版本(基于JSON解析)
|
2025-09-09 10:41:27 +08:00
|
|
|
|
from services.execution.handlers.model.block_name import BLOCK_NAME_STR
|
2025-05-12 15:43:21 +08:00
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
2025-09-30 13:52:36 +08:00
|
|
|
|
# 获取任务记录信息,包含任务定义详情
|
|
|
|
|
task_query = select(VWEDTaskRecord).where(
|
|
|
|
|
VWEDTaskRecord.id == task_record_id
|
|
|
|
|
)
|
|
|
|
|
task_result = await session.execute(task_query)
|
|
|
|
|
task_record = task_result.scalars().first()
|
2025-05-12 15:43:21 +08:00
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
if not task_record:
|
2025-05-12 15:43:21 +08:00
|
|
|
|
return {
|
2025-09-30 13:52:36 +08:00
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"未找到任务记录 {task_record_id}",
|
|
|
|
|
"data": None
|
2025-05-12 15:43:21 +08:00
|
|
|
|
}
|
2025-09-30 13:52:36 +08:00
|
|
|
|
|
|
|
|
|
# 获取所有块执行记录
|
|
|
|
|
blocks_query = select(VWEDBlockRecord).where(
|
|
|
|
|
VWEDBlockRecord.task_record_id == task_record_id
|
|
|
|
|
).order_by(VWEDBlockRecord.started_on)
|
|
|
|
|
blocks_result = await session.execute(blocks_query)
|
|
|
|
|
blocks = blocks_result.scalars().all()
|
|
|
|
|
|
|
|
|
|
# 获取所有任务日志
|
|
|
|
|
logs_query = select(VWEDTaskLog).where(
|
|
|
|
|
VWEDTaskLog.task_record_id == task_record_id
|
|
|
|
|
)
|
|
|
|
|
logs_result = await session.execute(logs_query)
|
|
|
|
|
logs = logs_result.scalars().all()
|
|
|
|
|
|
|
|
|
|
# 构建块记录和日志的映射
|
2025-10-13 14:52:58 +08:00
|
|
|
|
# 使用 block_id (任务定义中的id) 作为key,可以正确处理换机器人后block_name变化的情况
|
|
|
|
|
block_map = {}
|
|
|
|
|
block_record_map = {} # block_record_id -> block
|
|
|
|
|
for block in blocks:
|
|
|
|
|
# 如果同一个block_id有多个记录(换机器人导致),使用最新的
|
|
|
|
|
if block.block_id not in block_map or block.started_on > block_map[block.block_id].started_on:
|
|
|
|
|
block_map[block.block_id] = block
|
|
|
|
|
block_record_map[block.id] = block
|
|
|
|
|
|
|
|
|
|
# 使用 block_record_id 关联日志,避免block_name重复导致的日志混乱
|
|
|
|
|
log_map = {} # block_id -> logs
|
2025-09-30 13:52:36 +08:00
|
|
|
|
for log in logs:
|
2025-10-13 14:52:58 +08:00
|
|
|
|
# 通过 block_record_id 找到对应的 block,再通过 block.block_id 建立映射
|
|
|
|
|
if log.block_record_id and log.block_record_id in block_record_map:
|
|
|
|
|
block = block_record_map[log.block_record_id]
|
|
|
|
|
if block.block_id not in log_map:
|
|
|
|
|
log_map[block.block_id] = []
|
|
|
|
|
log_map[block.block_id].append(log)
|
|
|
|
|
else:
|
|
|
|
|
# 兼容旧数据:没有block_record_id的日志,使用task_block_id(即block_name)匹配
|
|
|
|
|
# 这种情况下,尝试通过block_name找到对应的block
|
|
|
|
|
matching_block = None
|
|
|
|
|
for block in blocks:
|
|
|
|
|
if block.block_name == log.task_block_id:
|
|
|
|
|
matching_block = block
|
|
|
|
|
break
|
|
|
|
|
if matching_block:
|
|
|
|
|
if matching_block.block_id not in log_map:
|
|
|
|
|
log_map[matching_block.block_id] = []
|
|
|
|
|
log_map[matching_block.block_id].append(log)
|
2025-09-30 13:52:36 +08:00
|
|
|
|
|
|
|
|
|
# 解析任务定义详情
|
|
|
|
|
task_def_detail = None
|
|
|
|
|
if task_record.task_def_detail:
|
|
|
|
|
try:
|
|
|
|
|
task_def_detail = json.loads(task_record.task_def_detail)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
logger.warning(f"任务记录 {task_record_id} 的task_def_detail解析失败")
|
|
|
|
|
|
|
|
|
|
if not task_def_detail or 'rootBlock' not in task_def_detail:
|
|
|
|
|
# 如果没有任务定义详情,回退到原始的平铺模式
|
|
|
|
|
return await TaskRecordService._get_block_results_fallback(
|
|
|
|
|
task_record_id, blocks, logs, BLOCK_NAME_STR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 构建嵌套结构
|
|
|
|
|
nested_results = await TaskRecordService._build_nested_block_results(
|
|
|
|
|
task_def_detail['rootBlock'],
|
|
|
|
|
block_map,
|
|
|
|
|
log_map,
|
|
|
|
|
BLOCK_NAME_STR,
|
|
|
|
|
level=0
|
|
|
|
|
)
|
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
2025-09-30 13:52:36 +08:00
|
|
|
|
"message": "成功获取任务记录嵌套执行结果",
|
|
|
|
|
"data": nested_results
|
2025-05-12 15:43:21 +08:00
|
|
|
|
}
|
2025-09-30 13:52:36 +08:00
|
|
|
|
|
2025-05-12 15:43:21 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取任务记录执行结果失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"获取任务记录执行结果失败: {str(e)}"
|
|
|
|
|
}
|
2025-09-30 13:52:36 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def _build_nested_block_results(
|
|
|
|
|
block_def: Dict[str, Any],
|
|
|
|
|
block_map: Dict[str, VWEDBlockRecord],
|
|
|
|
|
log_map: Dict[str, List[VWEDTaskLog]],
|
|
|
|
|
block_name_mapping: Dict[str, str],
|
|
|
|
|
level: int = 0
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
递归构建嵌套的块执行结果
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
Args:
|
|
|
|
|
block_def: 任务块定义
|
|
|
|
|
block_map: 块名称到执行记录的映射
|
|
|
|
|
log_map: 块名称到日志记录的映射
|
|
|
|
|
block_name_mapping: 块类型名称映射
|
|
|
|
|
level: 嵌套层级(用于调试)
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
Returns:
|
|
|
|
|
Dict: 嵌套的块执行结果
|
|
|
|
|
"""
|
2025-10-13 14:52:58 +08:00
|
|
|
|
from services.execution.handlers.model.block_name import ProgressBlockName
|
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
block_id = str(block_def.get('id', ''))
|
|
|
|
|
block_name = block_def.get('name', '')
|
|
|
|
|
block_type = block_def.get('blockType', '')
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
|
|
|
|
# 获取执行记录(使用block_id作为key)
|
|
|
|
|
execution_record = block_map.get(block_id)
|
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
# 构建基础结果结构
|
|
|
|
|
result = {
|
|
|
|
|
"blockId": block_id,
|
|
|
|
|
"blockName": block_name,
|
|
|
|
|
"blockType": block_type,
|
|
|
|
|
"blockTypeName": block_name_mapping.get(block_type, block_type),
|
|
|
|
|
"level": level,
|
|
|
|
|
"executionStatus": None,
|
|
|
|
|
"executionRecord": None,
|
|
|
|
|
"logs": [],
|
|
|
|
|
"children": []
|
|
|
|
|
}
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
# 如果有执行记录,添加执行信息
|
|
|
|
|
if execution_record:
|
|
|
|
|
execution_info = {
|
|
|
|
|
"recordId": execution_record.id,
|
|
|
|
|
"status": execution_record.status,
|
|
|
|
|
"startedOn": execution_record.started_on.isoformat() if execution_record.started_on else None,
|
|
|
|
|
"endedOn": execution_record.ended_on.isoformat() if execution_record.ended_on else None,
|
|
|
|
|
"endedReason": execution_record.ended_reason,
|
|
|
|
|
"inputParamsValue": json.loads(execution_record.block_input_params_value) if execution_record.block_input_params_value else None,
|
|
|
|
|
"outputParamsValue": json.loads(execution_record.block_out_params_value) if execution_record.block_out_params_value else None,
|
|
|
|
|
"remark": execution_record.remark
|
|
|
|
|
}
|
|
|
|
|
result["executionRecord"] = execution_info
|
|
|
|
|
result["executionStatus"] = execution_record.status
|
|
|
|
|
else:
|
|
|
|
|
# 如果没有执行记录,标记为未执行状态
|
|
|
|
|
from data.enum.task_block_record_enum import TaskBlockRecordStatus
|
|
|
|
|
result["executionStatus"] = TaskBlockRecordStatus.NOT_EXECUTED
|
|
|
|
|
result["executionRecord"] = {
|
|
|
|
|
"recordId": None,
|
|
|
|
|
"status": TaskBlockRecordStatus.NOT_EXECUTED,
|
|
|
|
|
"startedOn": None,
|
|
|
|
|
"endedOn": None,
|
|
|
|
|
"endedReason": "任务块尚未执行",
|
|
|
|
|
"inputParamsValue": None,
|
|
|
|
|
"outputParamsValue": None,
|
|
|
|
|
"remark": "任务块尚未执行"
|
|
|
|
|
}
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
|
|
|
|
# 如果有日志记录,添加日志信息(使用block_id作为key)
|
|
|
|
|
if block_id in log_map:
|
|
|
|
|
logs = log_map[block_id]
|
2025-09-30 13:52:36 +08:00
|
|
|
|
for log in logs:
|
|
|
|
|
try:
|
|
|
|
|
log_messages = json.loads(log.message) if log.message else {}
|
|
|
|
|
log_info = {
|
|
|
|
|
"logId": log.id,
|
|
|
|
|
"level": log.level,
|
|
|
|
|
"createdAt": log.created_at.isoformat() if log.created_at else None,
|
|
|
|
|
"message": log_messages.get("message", ""),
|
|
|
|
|
"output": log_messages.get("output", "")
|
|
|
|
|
}
|
|
|
|
|
result["logs"].append(log_info)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
# 如果日志不是JSON格式,直接使用原始消息
|
|
|
|
|
log_info = {
|
|
|
|
|
"logId": log.id,
|
|
|
|
|
"level": log.level,
|
|
|
|
|
"createdAt": log.created_at.isoformat() if log.created_at else None,
|
|
|
|
|
"message": log.message or "",
|
|
|
|
|
"output": ""
|
|
|
|
|
}
|
|
|
|
|
result["logs"].append(log_info)
|
2025-10-13 14:52:58 +08:00
|
|
|
|
|
|
|
|
|
# 检查是否是迭代类型的块(需要特殊处理子块结果)
|
|
|
|
|
is_iteration_block = block_type in [
|
|
|
|
|
ProgressBlockName.ITERATE_LIST,
|
|
|
|
|
ProgressBlockName.WHILE,
|
|
|
|
|
ProgressBlockName.REPEAT_NUM
|
|
|
|
|
]
|
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
# 递归处理子块
|
|
|
|
|
children_def = block_def.get('children', {})
|
2025-10-13 14:52:58 +08:00
|
|
|
|
if children_def:
|
|
|
|
|
if is_iteration_block:
|
|
|
|
|
# 对于迭代类型的块,从日志中提取迭代结果并重新组织
|
|
|
|
|
iteration_children = await TaskRecordService._extract_iteration_results(
|
|
|
|
|
block_def, block_map, log_map, block_name_mapping, level
|
|
|
|
|
)
|
|
|
|
|
result["children"] = iteration_children
|
|
|
|
|
else:
|
|
|
|
|
# 普通块:直接递归处理子块
|
|
|
|
|
for children_blocks in children_def.values():
|
|
|
|
|
if isinstance(children_blocks, list):
|
|
|
|
|
for child_block in children_blocks:
|
|
|
|
|
child_result = await TaskRecordService._build_nested_block_results(
|
|
|
|
|
child_block, block_map, log_map, block_name_mapping, level + 1
|
|
|
|
|
)
|
|
|
|
|
result["children"].append(child_result)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def _extract_iteration_results(
|
|
|
|
|
block_def: Dict[str, Any],
|
|
|
|
|
block_map: Dict[str, VWEDBlockRecord],
|
|
|
|
|
log_map: Dict[str, List[VWEDTaskLog]],
|
|
|
|
|
block_name_mapping: Dict[str, str],
|
|
|
|
|
level: int
|
|
|
|
|
) -> List[Dict[str, Any]]:
|
|
|
|
|
"""
|
|
|
|
|
从迭代块的日志中提取并重组迭代执行结果
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
block_def: 迭代块定义
|
|
|
|
|
block_map: 块名称到执行记录的映射
|
|
|
|
|
log_map: 块名称到日志记录的映射
|
|
|
|
|
block_name_mapping: 块类型名称映射
|
|
|
|
|
level: 当前层级
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List[Dict]: 按迭代次数组织的子块执行结果
|
|
|
|
|
"""
|
|
|
|
|
block_id = str(block_def.get('id', ''))
|
|
|
|
|
block_name = block_def.get('name', '')
|
|
|
|
|
block_type = block_def.get('blockType', '')
|
|
|
|
|
|
|
|
|
|
# 获取该块的日志,从中提取迭代信息(使用block_id作为key)
|
|
|
|
|
logs = log_map.get(block_id, [])
|
|
|
|
|
|
|
|
|
|
# 查找包含迭代结果的日志
|
|
|
|
|
iteration_results = []
|
|
|
|
|
iteration_data = None
|
|
|
|
|
|
|
|
|
|
for log in logs:
|
|
|
|
|
try:
|
|
|
|
|
log_messages = json.loads(log.message) if log.message else {}
|
|
|
|
|
print(log_messages, "==================")
|
|
|
|
|
output = log_messages.get("output", {})
|
|
|
|
|
|
|
|
|
|
# 检查是否包含迭代结果
|
|
|
|
|
if isinstance(output, dict) and "iterationResults" in output:
|
|
|
|
|
iteration_data = output["iterationResults"]
|
|
|
|
|
break # 找到迭代结果后退出循环
|
|
|
|
|
|
|
|
|
|
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
|
|
|
logger.debug(f"解析日志失败: {str(e)}")
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 如果在当前块的日志中没有找到迭代结果,尝试从所有日志中查找包含该块子块执行结果的日志
|
|
|
|
|
if not iteration_data:
|
|
|
|
|
logger.debug(f"在块 {block_name} 的日志中未找到迭代结果,尝试从所有日志中查找")
|
|
|
|
|
for log_block_name, log_list in log_map.items():
|
|
|
|
|
for log in log_list:
|
|
|
|
|
try:
|
|
|
|
|
log_messages = json.loads(log.message) if log.message else {}
|
|
|
|
|
output = log_messages.get("output", {})
|
|
|
|
|
|
|
|
|
|
# 检查results数组中是否包含该块的执行结果
|
|
|
|
|
if isinstance(output, dict) and "results" in output:
|
|
|
|
|
results = output.get("results", [])
|
|
|
|
|
for result_item in results:
|
|
|
|
|
# 检查child_id是否匹配当前块
|
|
|
|
|
if result_item.get("child_id") == block_def.get("id"):
|
|
|
|
|
# 检查是否包含iterationResults
|
|
|
|
|
child_output = result_item.get("output", {})
|
|
|
|
|
if isinstance(child_output, dict) and "iterationResults" in child_output:
|
|
|
|
|
iteration_data = child_output["iterationResults"]
|
|
|
|
|
logger.debug(f"找到块 {block_name} 的迭代结果")
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if iteration_data:
|
|
|
|
|
break
|
|
|
|
|
except (json.JSONDecodeError, KeyError, TypeError):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if iteration_data:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 处理迭代数据
|
|
|
|
|
if iteration_data:
|
|
|
|
|
# 遍历每次迭代
|
|
|
|
|
for iteration in iteration_data:
|
|
|
|
|
iteration_index = iteration.get("index", 0)
|
|
|
|
|
iteration_result = iteration.get("result", {})
|
|
|
|
|
|
|
|
|
|
# 提取该次迭代的子块结果
|
|
|
|
|
iteration_output = iteration_result.get("output", {})
|
|
|
|
|
iteration_block_results = iteration_output.get("results", [])
|
|
|
|
|
|
|
|
|
|
# 构建该次迭代的结果结构
|
|
|
|
|
iteration_item = {
|
|
|
|
|
"iterationIndex": iteration_index,
|
|
|
|
|
"success": iteration.get("success", False),
|
|
|
|
|
"children": []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 处理该次迭代中的每个子块
|
|
|
|
|
children_def = block_def.get('children', {})
|
|
|
|
|
if children_def:
|
|
|
|
|
for branch_name, children_blocks in children_def.items():
|
|
|
|
|
if isinstance(children_blocks, list):
|
|
|
|
|
for child_block in children_blocks:
|
|
|
|
|
# 为每个子块创建结果对象
|
|
|
|
|
child_result = await TaskRecordService._build_iteration_child_result(
|
|
|
|
|
child_block,
|
|
|
|
|
iteration_block_results,
|
|
|
|
|
iteration_index,
|
|
|
|
|
block_map,
|
|
|
|
|
log_map,
|
|
|
|
|
block_name_mapping,
|
|
|
|
|
level + 1
|
|
|
|
|
)
|
|
|
|
|
iteration_item["children"].append(child_result)
|
|
|
|
|
|
|
|
|
|
iteration_results.append(iteration_item)
|
|
|
|
|
|
|
|
|
|
# 如果没有找到迭代结果,回退到普通处理方式
|
|
|
|
|
if not iteration_results:
|
|
|
|
|
logger.warning(f"迭代块 {block_name} (ID: {block_def.get('id')}) 没有找到迭代结果,使用普通方式处理")
|
|
|
|
|
children_def = block_def.get('children', {})
|
|
|
|
|
normal_children = []
|
|
|
|
|
if children_def:
|
|
|
|
|
for children_blocks in children_def.values():
|
|
|
|
|
if isinstance(children_blocks, list):
|
|
|
|
|
for child_block in children_blocks:
|
|
|
|
|
child_result = await TaskRecordService._build_nested_block_results(
|
|
|
|
|
child_block, block_map, log_map, block_name_mapping, level + 1
|
|
|
|
|
)
|
|
|
|
|
normal_children.append(child_result)
|
|
|
|
|
return normal_children
|
|
|
|
|
|
|
|
|
|
return iteration_results
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def _build_iteration_child_result(
|
|
|
|
|
child_block_def: Dict[str, Any],
|
|
|
|
|
iteration_results: List[Dict[str, Any]],
|
|
|
|
|
iteration_index: int,
|
|
|
|
|
block_map: Dict[str, VWEDBlockRecord],
|
|
|
|
|
log_map: Dict[str, List[VWEDTaskLog]],
|
|
|
|
|
block_name_mapping: Dict[str, str],
|
|
|
|
|
level: int
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
构建迭代块子块的执行结果
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
child_block_def: 子块定义
|
|
|
|
|
iteration_results: 当前迭代的执行结果列表
|
|
|
|
|
iteration_index: 迭代索引
|
|
|
|
|
block_map: 块名称到执行记录的映射
|
|
|
|
|
log_map: 块名称到日志记录的映射
|
|
|
|
|
block_name_mapping: 块类型名称映射
|
|
|
|
|
level: 嵌套层级
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 子块执行结果
|
|
|
|
|
"""
|
|
|
|
|
block_id = str(child_block_def.get('id', ''))
|
|
|
|
|
block_name = child_block_def.get('name', '')
|
|
|
|
|
block_type = child_block_def.get('blockType', '')
|
|
|
|
|
|
|
|
|
|
# 从迭代结果中查找该子块的执行输出
|
|
|
|
|
child_output = None
|
|
|
|
|
for result_item in iteration_results:
|
|
|
|
|
if result_item.get("child_id") == child_block_def.get("id"):
|
|
|
|
|
child_output = result_item.get("output", {})
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
# 获取执行记录(注意:迭代块的子块可能被执行多次,这里获取的是最后一次)
|
|
|
|
|
# 使用block_id作为key
|
|
|
|
|
execution_record = block_map.get(block_id)
|
|
|
|
|
|
|
|
|
|
# 构建结果结构
|
|
|
|
|
result = {
|
|
|
|
|
"blockId": block_id,
|
|
|
|
|
"blockName": block_name,
|
|
|
|
|
"blockType": block_type,
|
|
|
|
|
"blockTypeName": block_name_mapping.get(block_type, block_type),
|
|
|
|
|
"level": level,
|
|
|
|
|
"iterationIndex": iteration_index,
|
|
|
|
|
"executionStatus": None,
|
|
|
|
|
"executionRecord": None,
|
|
|
|
|
"executionOutput": child_output, # 该次迭代的执行输出
|
|
|
|
|
"logs": [],
|
|
|
|
|
"children": []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 如果有执行记录,添加执行信息(注意:这是所有迭代的汇总信息)
|
|
|
|
|
if execution_record:
|
|
|
|
|
execution_info = {
|
|
|
|
|
"recordId": execution_record.id,
|
|
|
|
|
"status": execution_record.status,
|
|
|
|
|
"startedOn": execution_record.started_on.isoformat() if execution_record.started_on else None,
|
|
|
|
|
"endedOn": execution_record.ended_on.isoformat() if execution_record.ended_on else None,
|
|
|
|
|
"endedReason": execution_record.ended_reason,
|
|
|
|
|
"inputParamsValue": json.loads(execution_record.block_input_params_value) if execution_record.block_input_params_value else None,
|
|
|
|
|
"outputParamsValue": json.loads(execution_record.block_out_params_value) if execution_record.block_out_params_value else None,
|
|
|
|
|
"remark": execution_record.remark
|
|
|
|
|
}
|
|
|
|
|
result["executionRecord"] = execution_info
|
|
|
|
|
result["executionStatus"] = execution_record.status
|
|
|
|
|
|
|
|
|
|
# 添加该次迭代的日志
|
|
|
|
|
# 从child_output中提取该次迭代的日志信息(如果有的话)
|
|
|
|
|
if child_output and isinstance(child_output, dict):
|
|
|
|
|
# 将child_output作为该次迭代的执行日志
|
|
|
|
|
log_info = {
|
|
|
|
|
"logId": f"iteration_{iteration_index}_{block_name}",
|
|
|
|
|
"level": 1,
|
|
|
|
|
"createdAt": None,
|
|
|
|
|
"message": f"第{iteration_index + 1}次迭代执行结果",
|
|
|
|
|
"output": child_output
|
|
|
|
|
}
|
|
|
|
|
result["logs"].append(log_info)
|
|
|
|
|
|
|
|
|
|
# 递归处理嵌套的子块(如果有)
|
|
|
|
|
children_def = child_block_def.get('children', {})
|
2025-09-30 13:52:36 +08:00
|
|
|
|
if children_def:
|
|
|
|
|
for children_blocks in children_def.values():
|
|
|
|
|
if isinstance(children_blocks, list):
|
2025-10-13 14:52:58 +08:00
|
|
|
|
for nested_child in children_blocks:
|
|
|
|
|
nested_result = await TaskRecordService._build_nested_block_results(
|
|
|
|
|
nested_child, block_map, log_map, block_name_mapping, level + 1
|
2025-09-30 13:52:36 +08:00
|
|
|
|
)
|
2025-10-13 14:52:58 +08:00
|
|
|
|
result["children"].append(nested_result)
|
|
|
|
|
|
2025-09-30 13:52:36 +08:00
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def _get_block_results_fallback(
|
|
|
|
|
task_record_id: str,
|
|
|
|
|
blocks: List[VWEDBlockRecord],
|
|
|
|
|
logs: List[VWEDTaskLog],
|
|
|
|
|
block_name_mapping: Dict[str, str]
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
回退方案:当无法解析任务定义时,使用原始的平铺模式
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
blocks: 所有块执行记录
|
|
|
|
|
logs: 所有日志记录
|
|
|
|
|
block_name_mapping: 块类型名称映射
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 平铺格式的执行结果
|
|
|
|
|
"""
|
|
|
|
|
block_results = []
|
|
|
|
|
processed_block_names = set()
|
|
|
|
|
log_map = {}
|
|
|
|
|
|
|
|
|
|
# 构建日志映射
|
|
|
|
|
for log in logs:
|
|
|
|
|
if log.task_block_id not in log_map:
|
|
|
|
|
log_map[log.task_block_id] = []
|
|
|
|
|
log_map[log.task_block_id].append(log)
|
|
|
|
|
|
|
|
|
|
for block in sorted(blocks, key=lambda x: x.ended_on or x.created_at, reverse=True):
|
|
|
|
|
if block.block_name in processed_block_names:
|
|
|
|
|
continue
|
|
|
|
|
processed_block_names.add(block.block_name)
|
|
|
|
|
|
|
|
|
|
if block.status != TaskBlockRecordStatus.SUCCESS:
|
|
|
|
|
if block.block_name == "-1":
|
|
|
|
|
context = f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}] {block.task_record_id}"
|
|
|
|
|
else:
|
|
|
|
|
context = f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}] {block.ended_reason}"
|
|
|
|
|
block_results.append({
|
|
|
|
|
"created_at": block.created_at,
|
|
|
|
|
"context": context,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"blockName": block.block_name,
|
|
|
|
|
"blockType": block.block_execute_name
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
# 处理成功的块
|
|
|
|
|
if block.block_name == "-1":
|
|
|
|
|
context = f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}] {block.task_record_id}"
|
|
|
|
|
block_results.append({
|
|
|
|
|
"created_at": block.created_at,
|
|
|
|
|
"context": context,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"blockName": block.block_name,
|
|
|
|
|
"blockType": block.block_execute_name
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
# 查找对应的日志
|
|
|
|
|
block_logs = log_map.get(block.block_name, [])
|
|
|
|
|
if block_logs:
|
|
|
|
|
for task_log in block_logs:
|
|
|
|
|
try:
|
|
|
|
|
messages = json.loads(task_log.message) if task_log.message else {}
|
|
|
|
|
message = messages.get("message", "")
|
|
|
|
|
output = messages.get("output", "")
|
|
|
|
|
|
|
|
|
|
context_parts = [f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}]"]
|
|
|
|
|
if message:
|
|
|
|
|
context_parts.append(message)
|
|
|
|
|
if output:
|
|
|
|
|
if isinstance(output, dict) and output.get("message"):
|
|
|
|
|
context_parts.append(f"@{output.get('message')}")
|
|
|
|
|
else:
|
|
|
|
|
context_parts.append(f"@{str(output)}")
|
|
|
|
|
|
|
|
|
|
block_results.append({
|
|
|
|
|
"created_at": task_log.created_at,
|
|
|
|
|
"context": " ".join(context_parts),
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"blockName": block.block_name,
|
|
|
|
|
"blockType": block.block_execute_name
|
|
|
|
|
})
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
context = f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}] {task_log.message}"
|
|
|
|
|
block_results.append({
|
|
|
|
|
"created_at": task_log.created_at,
|
|
|
|
|
"context": context,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"blockName": block.block_name,
|
|
|
|
|
"blockType": block.block_execute_name
|
|
|
|
|
})
|
|
|
|
|
else:
|
|
|
|
|
# 没有日志时使用块的结束原因
|
|
|
|
|
context = f"[{block.block_execute_name}@{block_name_mapping.get(block.block_execute_name, '其他')}] {block.ended_reason or '执行完成'}"
|
|
|
|
|
block_results.append({
|
|
|
|
|
"created_at": block.created_at,
|
|
|
|
|
"context": context,
|
|
|
|
|
"status": block.status,
|
|
|
|
|
"blockName": block.block_name,
|
|
|
|
|
"blockType": block.block_execute_name
|
|
|
|
|
})
|
|
|
|
|
logger.warning(f"任务记录 {task_record_id} 的块 {block.block_name} 没有日志")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "成功获取任务记录执行结果(平铺模式)",
|
|
|
|
|
"data": sorted(block_results, key=lambda x: x["created_at"], reverse=True)
|
|
|
|
|
}
|
2025-05-12 15:43:21 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
async def get_task_record_detail(task_record_id: str) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
获取指定任务记录的详细信息
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含任务记录详细信息的字典
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 构建查询语句
|
|
|
|
|
query = select(VWEDTaskRecord).where(
|
|
|
|
|
VWEDTaskRecord.id == task_record_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 执行查询
|
|
|
|
|
result = await session.execute(query)
|
|
|
|
|
task_record = result.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not task_record:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"未找到ID为 {task_record_id} 的任务记录",
|
|
|
|
|
"data": None
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 计算执行时长(如果任务已结束)
|
|
|
|
|
execution_time = None
|
|
|
|
|
if task_record.ended_on and task_record.first_executor_time:
|
|
|
|
|
time_diff = task_record.ended_on - task_record.first_executor_time
|
|
|
|
|
execution_time = int(time_diff.total_seconds() * 1000) # 转换为毫秒
|
|
|
|
|
elif task_record.executor_time:
|
|
|
|
|
execution_time = task_record.executor_time
|
|
|
|
|
|
|
|
|
|
# 转换为字典
|
|
|
|
|
task_dict = {
|
|
|
|
|
"id": task_record.id,
|
|
|
|
|
"task_id": task_record.def_id,
|
|
|
|
|
"task_name": task_record.def_label,
|
|
|
|
|
"task_version": task_record.def_version,
|
|
|
|
|
"status": task_record.status,
|
|
|
|
|
"input_params": json.loads(task_record.input_params) if task_record.input_params else None,
|
|
|
|
|
"started_on": task_record.first_executor_time.isoformat() if task_record.first_executor_time else None,
|
|
|
|
|
"ended_on": task_record.ended_on.isoformat() if task_record.ended_on else None,
|
|
|
|
|
"ended_reason": task_record.ended_reason,
|
|
|
|
|
"execution_time": execution_time,
|
|
|
|
|
"created_at": task_record.created_at.isoformat() if task_record.created_at else None,
|
|
|
|
|
"updated_at": task_record.updated_at.isoformat() if task_record.updated_at else None,
|
|
|
|
|
"agv_id": task_record.agv_id,
|
|
|
|
|
"parent_task_record_id": task_record.parent_task_record_id,
|
|
|
|
|
"root_task_record_id": task_record.root_task_record_id,
|
|
|
|
|
"state_description": task_record.state_description,
|
|
|
|
|
"if_have_child_task": bool(task_record.if_have_child_task) if task_record.if_have_child_task is not None else None,
|
|
|
|
|
"periodic_task": task_record.periodic_task,
|
|
|
|
|
"priority": task_record.priority,
|
|
|
|
|
"work_stations": task_record.work_stations,
|
|
|
|
|
"work_types": task_record.work_types,
|
|
|
|
|
"variables": json.loads(task_record.variables) if task_record.variables else None,
|
|
|
|
|
"source_type": task_record.source_type,
|
|
|
|
|
"source_system": task_record.source_system,
|
|
|
|
|
"source_user": task_record.source_user,
|
|
|
|
|
"source_device": task_record.source_device,
|
|
|
|
|
"source_ip": task_record.source_ip,
|
|
|
|
|
"source_time": task_record.source_time.isoformat() if task_record.source_time else None,
|
|
|
|
|
"source_client_info": task_record.source_client_info,
|
|
|
|
|
"source_remarks": task_record.source_remarks
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "成功获取任务记录详情",
|
|
|
|
|
"data": task_dict
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"获取任务记录详情失败: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"获取任务记录详情失败: {str(e)}",
|
|
|
|
|
"data": None
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-13 14:52:58 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
async def switch_robot(task_record_id: str, switch_request: Dict[str, Any], client_ip: str = None, client_info: str = None, tf_api_token: str = None) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
换机器人功能
|
|
|
|
|
|
|
|
|
|
两种场景:
|
|
|
|
|
1. 未载货: 提取原始任务块,更新选择机器人参数,追加到任务定义
|
|
|
|
|
2. 已载货: 先添加放货块,再提取并修改原始任务块(替换GetIdleCrowdedSiteBp/更新siteId),追加到任务定义
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 任务记录ID
|
|
|
|
|
switch_request: 换机器人请求参数
|
|
|
|
|
client_ip: 客户端IP地址
|
|
|
|
|
client_info: 客户端信息
|
|
|
|
|
tf_api_token: 系统任务令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含换机器人结果的响应
|
|
|
|
|
"""
|
|
|
|
|
from data.enum.task_record_enum import TaskStatus
|
|
|
|
|
from services.enhanced_scheduler import scheduler
|
|
|
|
|
from services.task_record_switch_robot_helpers import (
|
|
|
|
|
cancel_and_cleanup_task,
|
|
|
|
|
extract_original_task_blocks,
|
|
|
|
|
recursively_update_select_agv_blocks,
|
|
|
|
|
validate_site_id_type,
|
|
|
|
|
find_pick_storage_location_block,
|
|
|
|
|
replace_get_crowded_site_to_get_site,
|
|
|
|
|
build_put_cargo_blocks_for_storage_location,
|
|
|
|
|
build_put_cargo_blocks_for_station,
|
|
|
|
|
check_select_robot_block_success,
|
|
|
|
|
validate_and_convert_site_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 1. 查询任务记录
|
|
|
|
|
task_query = await session.execute(
|
|
|
|
|
select(VWEDTaskRecord)
|
|
|
|
|
.where(VWEDTaskRecord.id == task_record_id)
|
|
|
|
|
)
|
|
|
|
|
task_record = task_query.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not task_record:
|
|
|
|
|
return {"success": False, "message": f"未找到任务记录 {task_record_id}", "code": 404}
|
|
|
|
|
|
|
|
|
|
# 2. 查询任务定义以获取 map_id
|
|
|
|
|
task_def_query = await session.execute(
|
|
|
|
|
select(VWEDTaskDef)
|
|
|
|
|
.where(VWEDTaskDef.id == task_record.def_id)
|
|
|
|
|
)
|
|
|
|
|
task_def = task_def_query.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not task_def:
|
|
|
|
|
return {"success": False, "message": f"未找到任务定义 {task_record.def_id}", "code": 404}
|
|
|
|
|
|
|
|
|
|
map_id = task_def.map_id
|
|
|
|
|
|
|
|
|
|
# 2. 检查任务状态
|
|
|
|
|
if task_record.status != TaskStatus.RUNNING:
|
|
|
|
|
return {"success": False, "message": f"任务状态为 {task_record.status},只能对运行中的任务换机器人", "code": 400}
|
|
|
|
|
|
|
|
|
|
# 3. 解析任务定义详情
|
|
|
|
|
task_def_detail = None
|
|
|
|
|
if task_record.task_def_detail:
|
|
|
|
|
try:
|
|
|
|
|
task_def_detail = json.loads(task_record.task_def_detail)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
return {"success": False, "message": "任务定义解析失败", "code": 400}
|
|
|
|
|
|
|
|
|
|
if not task_def_detail or 'rootBlock' not in task_def_detail:
|
|
|
|
|
return {"success": False, "message": "任务定义缺少rootBlock", "code": 400}
|
|
|
|
|
|
|
|
|
|
# 3.1 检查原任务中选择机器人块是否执行成功
|
|
|
|
|
robot_success, robot_error = await check_select_robot_block_success(session, task_record_id)
|
|
|
|
|
if not robot_success:
|
|
|
|
|
return {"success": False, "message": robot_error, "code": 400}
|
|
|
|
|
|
|
|
|
|
# 4. 获取换机器人参数
|
|
|
|
|
has_cargo = switch_request.get('has_cargo', False)
|
|
|
|
|
robot_name = switch_request.get('robot_name')
|
|
|
|
|
robot_group = switch_request.get('robot_group')
|
|
|
|
|
robot_label_group = switch_request.get('robot_label_group')
|
|
|
|
|
switch_reason = switch_request.get('switch_reason', '换机器人')
|
|
|
|
|
|
|
|
|
|
# 5. 取消正在执行的任务并清理资源
|
|
|
|
|
# 注意: cancel_task 方法会自动生成所有块的执行记录
|
|
|
|
|
await cancel_and_cleanup_task(scheduler, task_record_id, switch_reason)
|
|
|
|
|
logger.info("已取消任务并清理资源(包括生成块记录)")
|
|
|
|
|
|
|
|
|
|
# 6. 提取原始任务块列表(rootBlock的children)
|
|
|
|
|
original_blocks = extract_original_task_blocks(task_def_detail)
|
|
|
|
|
logger.info(f"提取了 {len(original_blocks)} 个原始任务块")
|
|
|
|
|
|
|
|
|
|
# 7. 根据是否载货执行不同逻辑
|
|
|
|
|
new_combined_blocks = []
|
|
|
|
|
|
|
|
|
|
if not has_cargo:
|
|
|
|
|
# 未载货场景: 更新选择机器人参数,并转换站点到库位
|
|
|
|
|
logger.info(f"未载货场景: 更新原始任务块的选择机器人参数")
|
|
|
|
|
|
|
|
|
|
# 7.1 从原始任务块中提取站点参数并转换为库位
|
|
|
|
|
# 查找取货的获取库位块,检查是否需要站点转换
|
|
|
|
|
from services.task_record_switch_robot_helpers import find_and_convert_station_to_storage
|
|
|
|
|
converted_blocks = await find_and_convert_station_to_storage(
|
|
|
|
|
session, original_blocks, map_id
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 7.2 更新选择机器人参数
|
|
|
|
|
updated_blocks = recursively_update_select_agv_blocks(
|
|
|
|
|
converted_blocks, robot_name, robot_group, robot_label_group
|
|
|
|
|
)
|
|
|
|
|
new_combined_blocks = updated_blocks
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# 已载货场景
|
|
|
|
|
logger.info(f"已载货场景: 先添加放货块,再修改原始任务块")
|
|
|
|
|
|
|
|
|
|
# 获取传入的放货参数
|
|
|
|
|
site_id = switch_request.get('site_id')
|
|
|
|
|
action = switch_request.get('action')
|
|
|
|
|
|
|
|
|
|
if not site_id or not action:
|
|
|
|
|
return {"success": False, "message": "已载货场景需要提供site_id和action参数", "code": 400}
|
|
|
|
|
|
|
|
|
|
# 8. 校验并转换site_id(站点转库位)
|
|
|
|
|
validate_success, validated_site_id, validate_error = await validate_and_convert_site_id(
|
|
|
|
|
session, site_id, map_id, task_def_detail, has_cargo
|
|
|
|
|
)
|
|
|
|
|
if not validate_success:
|
|
|
|
|
return {"success": False, "message": validate_error, "code": 400}
|
|
|
|
|
|
|
|
|
|
logger.info(f"site_id '{site_id}' 校验并转换为: {validated_site_id}")
|
|
|
|
|
|
|
|
|
|
# 9. 构建放货块(使用转换后的site_id,统一使用库位场景)
|
|
|
|
|
put_cargo_blocks = build_put_cargo_blocks_for_storage_location(
|
|
|
|
|
validated_site_id, action, robot_name, robot_group, robot_label_group
|
|
|
|
|
)
|
|
|
|
|
logger.info(f"构建了 {len(put_cargo_blocks)} 个放货任务块")
|
|
|
|
|
|
|
|
|
|
# 10. 修改原始任务块
|
|
|
|
|
# 10.1 查找取货的获取库位块
|
|
|
|
|
pick_block_result = find_pick_storage_location_block(original_blocks)
|
|
|
|
|
if pick_block_result:
|
|
|
|
|
pick_block, pick_idx, pick_list = pick_block_result
|
|
|
|
|
logger.info(f"找到取货的获取库位块: {pick_block.get('blockType')}")
|
|
|
|
|
|
|
|
|
|
# 10.2 如果是GetIdleCrowdedSiteBp,替换为GetIdleSiteBp,并使用转换后的site_id
|
|
|
|
|
replace_get_crowded_site_to_get_site(pick_block, validated_site_id)
|
|
|
|
|
|
|
|
|
|
# 10.3 更新所有选择机器人块的参数
|
|
|
|
|
updated_original_blocks = recursively_update_select_agv_blocks(
|
|
|
|
|
original_blocks, robot_name, robot_group, robot_label_group
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 11. 组合新任务块: 放货块 + 修改后的原始任务块
|
|
|
|
|
new_combined_blocks = put_cargo_blocks + updated_original_blocks
|
|
|
|
|
|
|
|
|
|
# 12. 更新任务定义,将新组合的块追加到rootBlock的children
|
|
|
|
|
task_def_detail['rootBlock']['children']['default'].extend(new_combined_blocks)
|
|
|
|
|
logger.info(f"已将 {len(new_combined_blocks)} 个任务块追加到任务定义")
|
|
|
|
|
|
|
|
|
|
# 13. 更新任务记录的task_def_detail和备注
|
|
|
|
|
task_record.task_def_detail = json.dumps(task_def_detail, ensure_ascii=False)
|
|
|
|
|
scenario_desc = "未载货" if not has_cargo else "已载货"
|
|
|
|
|
task_record.ended_reason = f"换机器人({scenario_desc}): {switch_reason}"
|
|
|
|
|
|
|
|
|
|
# 14. 将任务状态重置为排队中,准备重新提交
|
|
|
|
|
task_record.status = TaskStatus.QUEUED
|
|
|
|
|
logger.info(f"任务 {task_record_id} 状态重置为排队中,准备重新提交")
|
|
|
|
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
# 15. 重新提交任务到调度器
|
|
|
|
|
logger.info(f"任务 {task_record_id} 重新提交到调度器")
|
|
|
|
|
submit_result = await scheduler.submit_task(task_record_id=task_record_id)
|
|
|
|
|
|
|
|
|
|
if not submit_result.get("success", False):
|
|
|
|
|
logger.warning(f"重新提交任务失败: {submit_result}")
|
|
|
|
|
# 如果提交失败,任务已经更新但未执行,需要回滚状态
|
|
|
|
|
task_record.status = TaskStatus.FAILED
|
|
|
|
|
task_record.ended_reason = f"换机器人失败: {submit_result.get('message', '未知错误')}"
|
|
|
|
|
task_record.ended_on = datetime.datetime.now()
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"换机器人操作完成但重新提交任务失败: {submit_result.get('message', '未知错误')}",
|
|
|
|
|
"code": 400,
|
|
|
|
|
"data": {
|
|
|
|
|
"task_record_id": task_record_id,
|
|
|
|
|
"task_updated": True,
|
|
|
|
|
"task_resubmitted": False
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.info(f"任务 {task_record_id} 重新提交成功")
|
|
|
|
|
|
|
|
|
|
# 16. 返回成功结果
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": f"换机器人成功({scenario_desc}场景),任务已重新提交",
|
|
|
|
|
"data": {
|
|
|
|
|
"task_record_id": task_record_id,
|
|
|
|
|
"switch_reason": switch_reason,
|
|
|
|
|
"has_cargo": has_cargo,
|
|
|
|
|
"robot_name": robot_name,
|
|
|
|
|
"robot_group": robot_group,
|
|
|
|
|
"robot_label_group": robot_label_group,
|
|
|
|
|
"site_id": switch_request.get('site_id') if has_cargo else None,
|
|
|
|
|
"action": switch_request.get('action') if has_cargo else None,
|
|
|
|
|
"added_blocks": len(new_combined_blocks),
|
|
|
|
|
"resubmitted": True
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"换机器人失败: {str(e)}")
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"换机器人失败: {str(e)}",
|
|
|
|
|
"code": 500
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-20 16:50:45 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
async def retry_failed_task(task_record_id: str, client_ip: str = None, client_info: str = None, tf_api_token: str = None) -> Dict[str, Any]:
|
|
|
|
|
"""
|
|
|
|
|
重新执行失败的任务
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
task_record_id: 失败的任务记录ID
|
|
|
|
|
client_ip: 客户端IP地址
|
|
|
|
|
client_info: 客户端信息
|
|
|
|
|
tf_api_token: 系统任务令牌
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
Dict: 包含重发结果的响应
|
|
|
|
|
"""
|
|
|
|
|
from data.enum.task_record_enum import TaskStatus
|
|
|
|
|
try:
|
|
|
|
|
async with get_async_session() as session:
|
|
|
|
|
# 查找失败的任务记录
|
|
|
|
|
task_query = await session.execute(
|
|
|
|
|
select(VWEDTaskRecord)
|
|
|
|
|
.where(VWEDTaskRecord.id == task_record_id)
|
|
|
|
|
)
|
|
|
|
|
failed_task = task_query.scalars().first()
|
|
|
|
|
|
|
|
|
|
if not failed_task:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"未找到任务记录 {task_record_id}",
|
|
|
|
|
"code": 404
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 检查任务状态是否为失败状态
|
|
|
|
|
if failed_task.status not in [TaskStatus.FAILED, TaskStatus.CANCELED]:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"任务状态为 {failed_task.status},只能重发失败/终止的任务",
|
|
|
|
|
"code": 400
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 检查是否有任务定义
|
|
|
|
|
if not failed_task.def_id:
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": "任务记录缺少任务定义ID,无法重发",
|
|
|
|
|
"code": 400
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 构建重发请求参数
|
2025-09-30 13:52:36 +08:00
|
|
|
|
from routes.model.task_edit_model import TaskEditRunRequest
|
2025-09-20 16:50:45 +08:00
|
|
|
|
|
|
|
|
|
# 解析原任务的输入参数
|
2025-09-25 10:52:52 +08:00
|
|
|
|
if failed_task.input_params:
|
|
|
|
|
input_params = json.loads(failed_task.input_params)
|
|
|
|
|
else:
|
|
|
|
|
input_params = None
|
2025-09-20 16:50:45 +08:00
|
|
|
|
# 构建运行请求
|
|
|
|
|
run_request = TaskEditRunRequest(
|
|
|
|
|
taskId=failed_task.def_id,
|
2025-09-25 10:52:52 +08:00
|
|
|
|
params=input_params if input_params else [],
|
2025-09-20 16:50:45 +08:00
|
|
|
|
source_type=failed_task.source_type,
|
|
|
|
|
source_system=failed_task.source_system or "VWED-RETRY",
|
|
|
|
|
source_device=failed_task.source_device or "retry-system",
|
|
|
|
|
source_user=failed_task.source_user,
|
|
|
|
|
source_remarks=f"重发失败任务: {task_record_id}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 调用任务编辑服务的运行方法
|
|
|
|
|
from services.task_edit_service import TaskEditService
|
|
|
|
|
|
|
|
|
|
result = await TaskEditService.run_task(
|
|
|
|
|
run_request,
|
|
|
|
|
client_ip=client_ip,
|
|
|
|
|
client_info=client_info,
|
|
|
|
|
tf_api_token=tf_api_token
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not result or not result.get("success", False):
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"重发任务失败: {result.get('message', '未知错误') if result else '任务启动失败'}",
|
|
|
|
|
"code": 500
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 记录重发关系
|
|
|
|
|
logger.info(f"任务 {task_record_id} 重发成功,新任务记录ID: {result.get('data', {}).get('taskRecordId')}")
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"success": True,
|
|
|
|
|
"message": "任务重发成功",
|
|
|
|
|
"data": {
|
|
|
|
|
"original_task_record_id": task_record_id,
|
|
|
|
|
"new_task_record_id": result.get("data", {}).get("taskRecordId"),
|
|
|
|
|
"new_task_id": result.get("data", {}).get("taskId"),
|
|
|
|
|
"retry_time": datetime.datetime.now().isoformat(),
|
|
|
|
|
"original_task_name": failed_task.def_label,
|
|
|
|
|
"original_input_params": json.loads(failed_task.input_params) if failed_task.input_params else {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"重发失败任务异常: {str(e)}")
|
|
|
|
|
return {
|
|
|
|
|
"success": False,
|
|
|
|
|
"message": f"重发任务失败: {str(e)}",
|
|
|
|
|
"code": 500
|
|
|
|
|
}
|
|
|
|
|
|