/*
 * Copyright (c) 京北方信息技术股份有限公司 Corporation 2022 . All rights reserved.
 *
 */
package net.northking.cctp.executePlan.api.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.page.PageMethod;
import net.northking.cctp.common.db.*;
import net.northking.cctp.common.db.annotation.DatabaseWriteOnly;
import net.northking.cctp.common.db.excel.AbstractExcelService;
import net.northking.cctp.common.db.excel.ExcelHeader;
import net.northking.cctp.common.dto.AssociatedFilesDto;
import net.northking.cctp.common.exception.PlatformRuntimeException;
import net.northking.cctp.common.http.QueryByPage;
import net.northking.cctp.common.http.ResultWrapper;
import net.northking.cctp.common.s3.FileDownloadException;
import net.northking.cctp.common.s3.SimpleStorageService;
import net.northking.cctp.common.security.authentication.NKSecurityContext;
import net.northking.cctp.common.util.UUIDUtil;
import net.northking.cctp.executePlan.constants.MsgConstant;
import net.northking.cctp.executePlan.constants.PlanConstant;
import net.northking.cctp.executePlan.constants.RabbitConstant;
import net.northking.cctp.executePlan.constants.RedisConstant;
import net.northking.cctp.executePlan.db.entity.*;
import net.northking.cctp.executePlan.db.service.AtuPlanAppLinkService;
import net.northking.cctp.executePlan.db.service.AtuPlanBatchDeviceLinkService;
import net.northking.cctp.executePlan.db.service.AtuPlanSceneCaseTaskService;
import net.northking.cctp.executePlan.db.service.AtuPlanTaskService;
import net.northking.cctp.executePlan.dto.planBatch.AtuBatchCaseInfoDto;
import net.northking.cctp.executePlan.dto.planBatch.AtuExceptionCase;
import net.northking.cctp.executePlan.dto.planBatch.BatchRetryDto;
import net.northking.cctp.executePlan.dto.planBatch.SceneCaseStopDto;
import net.northking.cctp.executePlan.dto.planEntrust.DeviceListDto;
import net.northking.cctp.executePlan.dto.planEntrust.EngineInfoListDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeExecDto;
import net.northking.cctp.executePlan.dto.planSceneCase.AtuSceneNodeInfoDto;
import net.northking.cctp.executePlan.dto.planSceneCase.CdEngineInfoDetailDto;
import net.northking.cctp.executePlan.dto.planSceneCase.SceneFirstNodeDto;
import net.northking.cctp.executePlan.dto.planTask.*;
import net.northking.cctp.executePlan.exception.ExecPlanError;
import net.northking.cctp.executePlan.feign.AttachmentFeignClient;
import net.northking.cctp.executePlan.feign.DeviceFeignClient;
import net.northking.cctp.executePlan.feign.PublicFeignClient;
import net.northking.cctp.executePlan.feign.ScriptCaseFeignClient;
import net.northking.cctp.executePlan.pub.dto.ScriptFirstExecutionDTO;
import net.northking.cctp.executePlan.utils.EnvNameUtils;
import net.northking.cctp.executePlan.utils.MinioPathUtils;
import net.northking.cctp.executePlan.utils.WordUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.io.*;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 计划执行任务表服务实现类
 *
 * @author maven-cctp-plugin
 * @since 1.0
 */

@Service
@ConditionalOnMissingBean(name = "AtuPlanTaskApiServiceImpl")
@RefreshScope
public class AtuPlanTaskApiServiceImpl extends AbstractExcelService<AtuPlanTask> implements AtuPlanTaskApiService {
    /**
     * 日志
     */
    private final static Logger logger = LoggerFactory.getLogger(AtuPlanTaskApiServiceImpl.class);

    @Autowired
    private AtuPlanTaskService atuPlanTaskService;

    @Autowired
    private AtuPlanAppLinkService atuPlanAppLinkService;

    @Autowired
    private AtuPlanSceneCaseTaskService atuPlanSceneCaseTaskService;

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private PublicFeignClient publicFeignClient;

    @Autowired
    private AtuPlanBatchDeviceLinkService atuPlanBatchDeviceLinkService;

    @Autowired
    private ScriptCaseFeignClient scriptCaseFeignClient;

    @Autowired
    private DeviceFeignClient deviceFeignClient;

    @Autowired
    private SimpleStorageService simpleStorageService;

    @Autowired
    private AtuPlanBatchApiService planBatchApiService;

    @Autowired
    private AtuPlanInfoApiService atuPlanInfoApiService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Autowired
    private EnvNameUtils envNameUtils;
    @Autowired
    private AttachmentFeignClient attachmentFeignClient;

    @Value("${zmn.hostStr}")
    private String hostStr;

    private static final DefaultRedisScript<Boolean> TASK_RETRY_INIT_SCRIPT;

    static {
        TASK_RETRY_INIT_SCRIPT = new DefaultRedisScript<>();
        TASK_RETRY_INIT_SCRIPT.setLocation(new ClassPathResource("/lua/taskRetryInitCache.lua"));
        TASK_RETRY_INIT_SCRIPT.setResultType(Boolean.class);
    }


    //= Excel 导入导出相关代码 start ==================//
    @Override
    public BasicService<AtuPlanTask> getService() {
        return this.atuPlanTaskService;
    }

    @Override
    public ExcelHeader[] excelHeaders() {
        ExcelHeader id = new ExcelHeader("主键", "id", String.class);
        ExcelHeader batchId = new ExcelHeader("调度主键", "batchId", String.class);
        ExcelHeader scriptId = new ExcelHeader("脚本主键", "scriptId", String.class);
        ExcelHeader scriptName = new ExcelHeader("脚本名称", "scriptName", String.class);
        ExcelHeader caseId = new ExcelHeader("用例主键", "caseId", String.class);
        ExcelHeader caseName = new ExcelHeader("用例名称", "caseName", String.class);
        ExcelHeader caseType = new ExcelHeader("用例类型;1-接口，2-PC/BS，3-PC/CS，4-安卓，5-ios，6-场景用例", "caseType", String.class);
        ExcelHeader engineId = new ExcelHeader("引擎主键", "engineId", String.class);
        ExcelHeader deviceId = new ExcelHeader("设备主键", "deviceId", String.class);
        ExcelHeader appId = new ExcelHeader("应用主键", "appId", String.class);
        ExcelHeader status = new ExcelHeader("状态;0-等待中，1-执行中，2-执行成功，3-执行失败，4-断言失败，5-取消，6-超时", "status", String.class);
        ExcelHeader startTime = new ExcelHeader("开始时间", "startTime", Date.class);
        ExcelHeader endTime = new ExcelHeader("结束时间", "endTime", Date.class);
        ExcelHeader errorMsg = new ExcelHeader("错误信息", "errorMsg", String.class);
        ExcelHeader videoUrl = new ExcelHeader("视频地址", "videoUrl", String.class);
        ExcelHeader execResultFile = new ExcelHeader("执行结果文件", "execResultFile", String.class);
        ExcelHeader createdTime = new ExcelHeader("创建时间", "createdTime", Date.class);

        return new ExcelHeader[]{};
    }

    /**
     * Excel导入，入库之前的处理
     *
     * @param records 从Excel读取到的记录
     * @return 待入库的记录
     */
    @Override
    protected List<AtuPlanTask> beforeInsertBatch(List<AtuPlanTask> records) {
        for (AtuPlanTask record : records) {
            record.setId(UUIDUtil.create32UUID());
        }
        return records;
    }

    /**
     * 写入Excel之前的处理<br>
     * 数据的特殊处理在此处加工处理
     *
     * @param record 来自数据库记录
     * @return 待写入Excel的记录
     */
    @Override
    protected AtuPlanTask beforeWriteExcel(AtuPlanTask record) {
        return record;
    }

    //= Excel 导入导出相关代码 end =======================//

    //= 增删改查 相关代码 start ==================//
    @Override
    public Pagination<AtuPlanTaskPageDto> pagingQuery(QueryByPage<AtuPlanTaskQueryDto> queryByPage) {
        QueryByPage<AtuPlanTaskQueryDto> qty = new QueryByPage<>();
        qty.setQuery(new AtuPlanTaskQueryDto());
        qty.getQuery().setBatchId(queryByPage.getQuery().getBatchId());
        String keyWord = queryByPage.getQuery().getKeyword();
        if (org.springframework.util.StringUtils.hasText(keyWord))
            keyWord = keyWord.replace("%", "\\%").replace("_", "\\_");
        qty.getQuery().setKeyword(keyWord);
        if (org.springframework.util.StringUtils.hasText(queryByPage.getQuery().getCaseName())) {
            qty.getQuery().setCaseName(queryByPage.getQuery().getCaseName());
        }
        if (org.springframework.util.StringUtils.hasText(queryByPage.getQuery().getScriptName())) {
            qty.getQuery().setScriptName(queryByPage.getQuery().getScriptName());
        }
        if (org.springframework.util.StringUtils.hasText(queryByPage.getQuery().getStatus())) {
            qty.getQuery().setStatus(queryByPage.getQuery().getStatus());
        }
        DefaultPagination<AtuPlanTaskPageDto> pagination = new DefaultPagination();
        qty.setPageNo(queryByPage.getPageNo());
        qty.setPageSize(queryByPage.getPageSize());
        pagination.setPageSize(queryByPage.getPageSize());
        pagination.setPageNo(queryByPage.getPageNo());
        handlePageParam(qty);
        Page<AtuPlanTaskPageDto> page = PageMethod.startPage(qty.getPageNo(), qty.getPageSize());
        List<AtuPlanTaskPageDto> result = atuPlanTaskService.queryList(qty);
        pagination.setRecords(result);
        pagination.setRecordCount(page.getTotal());
        for (AtuPlanTaskPageDto dto : pagination.getRecords()) {
            //根据脚本id获取脚本编码
            if (StringUtils.isNotBlank(dto.getCaseNo())) {
                dto.setScriptCode(dto.getCaseNo());
            } else {
                Object resultCode = redisTemplate.opsForValue().get(MsgConstant.UITEST_SCRIPTCODE + dto.getScriptId());
                if (resultCode != null && !resultCode.equals("")) {
                    dto.setScriptCode(resultCode.toString());
                } else {
                    Map<String, String> scriptMap = new HashMap<>();
                    scriptMap.put(MsgConstant.ID, dto.getScriptId());
                    scriptMap.put(MsgConstant.TYPE, dto.getCaseType());
                    String scriptCode = scriptCaseFeignClient.queryScriptCode(scriptMap);
                    if (!StringUtils.isBlank(scriptCode)) {
                        dto.setScriptCode(scriptCode);
                    }
                }
            }
            //场景用例返回对应的脚本执行视频
            if (dto.getCaseType().equals(PlanConstant.SCRIPT_TYPE_SCENE)) {
                List<SceneNodeDto> sceneNodes = new ArrayList<>();
                //获取每个节点最后一次执行的内容
                List<AtuPlanSceneCaseTask> query = this.atuPlanSceneCaseTaskService.queryNodeInfo(dto.getId());
                for (AtuPlanSceneCaseTask caseTask : query) {
                    if (caseTask.getNodeType().equals(PlanConstant.SCRIPT_TYPE_API)) {
                        dto.setApiCase(true);
                    }
                    if (StrUtil.isNotBlank(caseTask.getVideoUrl())) {
                        SceneNodeDto node = new SceneNodeDto();
                        BeanUtils.copyProperties(caseTask, node);
                        //视频播放的设备类型(1、移动 2、PC)
                        switch (caseTask.getNodeType()) {
                            case PlanConstant.SCRIPT_TYPE_API:
                            case PlanConstant.SCRIPT_TYPE_BS:
                            case PlanConstant.SCRIPT_TYPE_CS:
                                node.setDeviceType(PlanConstant.DEVICE_TYPE_PC);
                                break;
                            case PlanConstant.SCRIPT_TYPE_ANDROID:
                            case PlanConstant.SCRIPT_TYPE_IOS:
                                node.setDeviceType(PlanConstant.DEVICE_TYPE_MOBILE);
                                break;
                            default:
                                break;
                        }
                        sceneNodes.add(node);
                    }
                }
                dto.setSceneData(sceneNodes);
            } else if (StrUtil.isNotBlank(dto.getVideoUrl())) {
                List<SceneNodeDto> sceneNodes = new ArrayList<>();
                SceneNodeDto node = new SceneNodeDto();
                BeanUtils.copyProperties(dto, node);
                switch (dto.getCaseType()) {
                    case PlanConstant.SCRIPT_TYPE_API:
                    case PlanConstant.SCRIPT_TYPE_BS:
                    case PlanConstant.SCRIPT_TYPE_CS:
                        node.setDeviceType(PlanConstant.DEVICE_TYPE_PC);
                        break;
                    case PlanConstant.SCRIPT_TYPE_ANDROID:
                    case PlanConstant.SCRIPT_TYPE_IOS:
                        node.setDeviceType(PlanConstant.DEVICE_TYPE_MOBILE);
                        break;
                }
                sceneNodes.add(node);
                dto.setSceneData(sceneNodes);
            }
            if (dto.getStatus().equals(PlanConstant.TASK_START_EXECUTE_STATUS)
                    && dto.getCaseType().equals(PlanConstant.SCRIPT_TYPE_API)) {
                dto.setApiCase(true);
            }
            if (PlanConstant.TASK_WAIT_EXECUTE_STATUS.equals(dto.getStatus()) ||
                    PlanConstant.TASK_START_EXECUTE_STATUS.equals(dto.getStatus())) {
                // 任务等待中或正在执行设置已有的任务结束时间为null，避免计算耗时
                dto.setEndTime(null);
            }
            // 设置环境名称
            if (StrUtil.isNotBlank(dto.getEnvId())) {
                String envName = envNameUtils.getEnvName(dto.getEnvId());
                if (envName != null) {
                    dto.setEnvName(envName.toString());
                }
            }
        }
        return pagination;
    }

    @Override
    public List<AtuPlanTask> query(AtuPlanTask atuPlanTask) {
        return atuPlanTaskService.query(atuPlanTask);
    }

    @Override
    public List<AtuPlanTask> query(AtuPlanTask atuPlanTask, OrderBy orderBy) {
        return atuPlanTaskService.query(atuPlanTask, orderBy);
    }


    @Override
    @DatabaseWriteOnly
    public String insert(AtuPlanTask task) {
        this.atuPlanTaskService.insert(task);
        return task.getId();
    }


    /**
     * 计划批次停止
     *
     * @param batchId 批次ID
     * @return 结果
     */
    @Override
    public Boolean stop(String batchId) {
        logger.debug("删除批次任务队列");
        amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_PC_KEY + batchId);
        amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_MOB_KEY + "android" + "." + batchId);
        amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_MOB_KEY + "ios" + "." + batchId);
        amqpAdmin.deleteQueue(RabbitConstant.TASK_EXEC_QUEUE_API_KEY + batchId);

        logger.debug("查询该批次所有未完成的任务信息");
        List<AtuPlanTask> taskList = atuPlanTaskService.queryUnfinishedTaskByBatchId(batchId);
        if (CollUtil.isEmpty(taskList)) {
            logger.debug("任务已全部执行完成");
            return true;
        }
        ThreadUtil.execAsync(() -> {
            logger.debug("通知引擎停止批次任务");
            noticeEngineRemoveBatch(batchId);
        });
        logger.debug("该批次未完成的任务数量：{}", taskList.size());
        for (AtuPlanTask planTask : taskList) {
            if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, planTask.getCaseType())) {
                logger.debug("通知场景用例停止用例解析");
                SceneCaseStopDto stopDto = new SceneCaseStopDto();
                stopDto.setTaskId(planTask.getId());
                stopDto.setCaseId(planTask.getCaseId());
                ResultWrapper<String> stopResult = publicFeignClient.stopCase(stopDto);
                if (ObjectUtil.isNull(stopResult) || !stopResult.isSuccess()) {
                    logger.error("场景任务[" + planTask.getId() + "]取消失败");
                }
                logger.debug("场景任务[" + planTask.getId() + "]取消结果 => " + JSONUtil.toJsonStr(stopResult));
                // 查询场景节点信息
                AtuPlanSceneCaseTask sceneCaseTask = atuPlanSceneCaseTaskService.queryUnfinishedTaskByTaskId(planTask.getId());
                if (ObjectUtil.isNotNull(sceneCaseTask)) {
                    logger.debug("更新场景节点任务状态");
                    sceneCaseTask.setStatus(PlanConstant.TASK_CANCEL_STATUS);
                    if (ObjectUtil.isNull(sceneCaseTask.getStartTime())) {
                        sceneCaseTask.setStartTime(System.currentTimeMillis());
                    }
                    sceneCaseTask.setEndTime(System.currentTimeMillis());
                    sceneCaseTask.setErrorMsg("任务取消");
                    // 更新场景节点状态
                    atuPlanSceneCaseTaskService.updateByPrimaryKey(sceneCaseTask);
                }
            }
            logger.debug("任务状态为：{}", planTask.getStatus());
            // 判断是否终态
            if (ObjectUtil.equals(PlanConstant.TASK_WAIT_EXECUTE_STATUS, planTask.getStatus())
                    || ObjectUtil.equals(PlanConstant.TASK_START_EXECUTE_STATUS, planTask.getStatus())) {
                logger.debug("修改任务状态为取消");
                this.taskExecFailUpdate(planTask, PlanConstant.TASK_CANCEL_STATUS, "任务取消");
            }
        }
        return true;
    }

    public void taskExecFailUpdate(AtuPlanTask planTask, String status, String errorMsg) {
        logger.debug("修改任务状态为: {}", status);
        if (ObjectUtil.isNull(planTask.getStartTime())) {
            logger.debug("任务还未开始执行，修改开始状态");
            planTask.setStartTime(System.currentTimeMillis());
            planTask.setStatus(PlanConstant.TASK_START_EXECUTE_STATUS);
            // 任务还未开始执行，先设置为开始执行状态
            planBatchApiService.updateCacheBatchSumData(planTask);
        }
        planTask.setStatus(status);
        planTask.setEndTime(System.currentTimeMillis());
        planTask.setErrorMsg(errorMsg);
        atuPlanTaskService.updateByPrimaryKey(planTask);
        logger.debug("更新缓存中的统计信息");
        planBatchApiService.updateCacheBatchSumData(planTask);
    }

    /**
     * 创建任务执行信息
     *
     * @param planTask      任务信息
     * @param batchCaseInfo 用例信息
     */
    @Override
    public void createTaskExecuteInfo(AtuPlanTask planTask, AtuBatchCaseInfoDto batchCaseInfo) {
        AtuTaskExecDto atuTaskExecDto = new AtuTaskExecDto();
        BeanUtil.copyProperties(batchCaseInfo, atuTaskExecDto);
        atuTaskExecDto.setTaskId(planTask.getId());
        atuTaskExecDto.setCaseId(planTask.getCaseId());
        atuTaskExecDto.setCaseParams(batchCaseInfo.getCaseParam());
        String type = planTask.getCaseType();
        atuTaskExecDto.setCaseType(type);
        atuTaskExecDto.setFailRetryNum(batchCaseInfo.getFailRetryCount());
        boolean deviceOffline = false;
        if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, type)) {
            // 场景用例
            // 第一节点执行信息
            AtuSceneNodeExecDto sceneNodeExecDto = batchCaseInfo.getSceneNodeExecDto();
            logger.debug("场景首节点信息：" + JSONUtil.toJsonStr(sceneNodeExecDto));
            if (ObjectUtil.isNull(sceneNodeExecDto)) {
                this.taskExecFailUpdate(planTask,PlanConstant.TASK_EXECUTE_FAIL_STATUS,"场景节点执行信息不存在");
                throw new PlatformRuntimeException(ExecPlanError.SCENE_NODE_EXEC_INFO_IS_NULL);
            }
            AtuSceneNodeInfoDto nextNodeInfo = sceneNodeExecDto.getNodeInfo();
            if (ObjectUtil.isNull(nextNodeInfo)) {
                this.taskExecFailUpdate(planTask,PlanConstant.TASK_EXECUTE_FAIL_STATUS,"场景下一节点信息不存在");
                throw new PlatformRuntimeException(ExecPlanError.SCENE_NEXT_NODE_INFO_IS_NULL);
            }
            // 生成场景节点任务
            AtuPlanSceneCaseTask planSceneCaseTask = new AtuPlanSceneCaseTask();
            planSceneCaseTask.setId(IdUtil.simpleUUID());
            planSceneCaseTask.setTaskId(planTask.getId());
            planSceneCaseTask.setScriptId(nextNodeInfo.getScriptId());
            planSceneCaseTask.setVersionId(nextNodeInfo.getVersionId());
            planSceneCaseTask.setVersionName(nextNodeInfo.getVersionName());
            planSceneCaseTask.setScriptName(nextNodeInfo.getScriptName());
            planSceneCaseTask.setScriptJson(nextNodeInfo.getScriptPath());
            planSceneCaseTask.setNodeId(nextNodeInfo.getNodeId());
            planSceneCaseTask.setNodeType(nextNodeInfo.getNodeType());
            if (CollUtil.isNotEmpty(nextNodeInfo.getCaseParam())) {
                planSceneCaseTask.setNodeParams(JSONUtil.toJsonStr(nextNodeInfo.getCaseParam()));
            }
            planSceneCaseTask.setPrevNodeId(sceneNodeExecDto.getNodeId());
            planSceneCaseTask.setCreatedTime(new Date());
            planSceneCaseTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
            planSceneCaseTask.setScriptJson(nextNodeInfo.getScriptPath());
            atuPlanSceneCaseTaskService.insert(planSceneCaseTask);

            // 移动用例
            atuTaskExecDto.setAppId(queryAppId(batchCaseInfo.getPlanId(), nextNodeInfo.getPlatformType(),
                    nextNodeInfo.getAppPackage(), nextNodeInfo.getNodeType()));
            atuTaskExecDto.setTaskId(planSceneCaseTask.getId());
            atuTaskExecDto.setScriptPath(nextNodeInfo.getScriptPath());
            atuTaskExecDto.setCaseParams(nextNodeInfo.getCaseParam());

            type = nextNodeInfo.getNodeType();

            deviceOffline = checkDeviceOffline(planTask, planSceneCaseTask, batchCaseInfo, true);
        } else {
            atuTaskExecDto.setAppId(queryAppId(batchCaseInfo.getPlanId(), batchCaseInfo.getPlatformType(),
                    batchCaseInfo.getAppPackage(), planTask.getCaseType()));

            deviceOffline = checkDeviceOffline(planTask, new AtuPlanSceneCaseTask(), batchCaseInfo, false);
        }

        if (deviceOffline) {
            logger.debug("该类型的设备全部离线了，任务结束");
            return;
        }
        // 发送任务执行信息至消息队列
        sendToQueue(atuTaskExecDto.getBatchId(), atuTaskExecDto, type);
    }

    /**
     * 校验设备是否离线
     *
     * @param planTask          任务信息
     * @param planSceneCaseTask 场景节点任务信息
     * @param batchCaseInfo     用例信息
     * @param isScene           是否场景任务
     */
    private boolean checkDeviceOffline(AtuPlanTask planTask, AtuPlanSceneCaseTask planSceneCaseTask,
                                       AtuBatchCaseInfoDto batchCaseInfo, boolean isScene) {
        logger.debug("校验设备是否离线");
        String taskType = planTask.getCaseType();
        if (isScene) {
            taskType = planSceneCaseTask.getNodeType();
        }
        AtuPlanBatchDeviceLink batchDeviceLink = new AtuPlanBatchDeviceLink();
        logger.debug("脚本类型为：{}", taskType);
        switch (taskType) {
            case PlanConstant.SCRIPT_TYPE_API:
            case PlanConstant.SCRIPT_TYPE_BS:
            case PlanConstant.SCRIPT_TYPE_CS:
                batchDeviceLink.setDeviceType(PlanConstant.DEVICE_TYPE_PC);
                break;
            case PlanConstant.SCRIPT_TYPE_ANDROID:
            case PlanConstant.SCRIPT_TYPE_IOS:
                batchDeviceLink.setDeviceType(PlanConstant.DEVICE_TYPE_MOBILE);
                break;
        }
        if (StrUtil.isEmpty(batchDeviceLink.getDeviceType())) {
            logger.debug("设备类型为空，结束校验");
            return false;
        }
        batchDeviceLink.setBatchId(planTask.getBatchId());
        batchDeviceLink.setOffline(false);
        List<AtuPlanBatchDeviceLink> onlineDeviceList = atuPlanBatchDeviceLinkService.query(batchDeviceLink);
        if (CollUtil.isNotEmpty(onlineDeviceList)) {
            logger.debug("部分设备离线，任务照常执行, 在线设备数量：{}", onlineDeviceList.size());
            return false;
        }
        if (isScene) {
            logger.debug("场景任务，更新节点任务状态");
            // 查询首节点任务类型
            planSceneCaseTask.setStartTime(System.currentTimeMillis());
            planSceneCaseTask.setStatus(PlanConstant.TASK_EXECUTE_FAIL_STATUS);
            planSceneCaseTask.setEndTime(System.currentTimeMillis());
            planSceneCaseTask.setErrorMsg("该计划绑定的设备已离线");
            logger.debug("更新当前节点信息 => " + JSONUtil.toJsonStr(planSceneCaseTask));
            atuPlanSceneCaseTaskService.updateByPrimaryKey(planSceneCaseTask);

            logger.debug("通知脚本用例服务该场景任务取消执行");
            SceneCaseStopDto sceneCaseStopDto = new SceneCaseStopDto();
            sceneCaseStopDto.setTaskId(batchCaseInfo.getTaskId());
            sceneCaseStopDto.setCaseId(batchCaseInfo.getCaseId());
            try {
                publicFeignClient.stopCase(sceneCaseStopDto);
            } catch (Exception e) {
                logger.error("通知脚本用例服务该场景任务取消执行异常", e);
            }
        }
        logger.debug("更新任务为离线超时状态");
        this.taskExecFailUpdate(planTask, PlanConstant.TASK_EXECUTE_FAIL_STATUS, "该计划绑定的设备已离线");
        return true;
    }

    /**
     * 查询应用ID
     *
     * @param planId       计划ID
     * @param platformType 平台类型
     * @param appPackage   app包名
     * @param caseType     用例类型
     * @return 结果
     */
    @Override
    public String queryAppId(String planId, String platformType, String appPackage, String caseType) {
        if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_ANDROID, caseType)
                || ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_IOS, caseType)) {
            // 根据计划ID，应用包名，应用平台类型查询应用ID
            AtuPlanAppLink planAppLink = new AtuPlanAppLink();
            planAppLink.setPlanId(planId);
            planAppLink.setPlatformType(platformType);
            logger.debug("查询应用ID参数 => " + JSONUtil.toJsonStr(planAppLink));
            String appId = atuPlanAppLinkService.queryAppId(planAppLink);
            if (StrUtil.isBlank(appId)) {
                logger.error("该平台绑定的应用编号信息为空");
                return "";
                //throw new PlatformRuntimeException(ExecPlanError.CASE_APP_ID_IS_NULL);
            }
            return appId;
        }
        return "";
    }

    /**
     * 下载脚本文件
     *
     * @param scriptJson 脚本存放地址
     * @return 脚本对象
     */
    private JSONObject downloadScriptFile(String scriptJson) {
        if (StrUtil.isBlank(scriptJson)) {
            logger.error("脚本存放地址不存在");
            return null;
        }
        String[] resultPath = MinioPathUtils.idToPath(scriptJson);
        InputStream inputStream = null;
        JSONObject scriptData;
        try {
            inputStream = simpleStorageService.downloadAsStream(resultPath[0], "/" + resultPath[1]);
            scriptData = MinioPathUtils.getFileContent(inputStream, JSONObject.class);
        } catch (FileDownloadException e) {
            logger.error("读取原脚本文件失败，失败原因：", e);
            throw new PlatformRuntimeException(ExecPlanError.DOWNLOAD_SCRIPT_FAIL);
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error("关闭文件流异常", e);
                }
            }
        }
        return scriptData;
    }

    @Override
    public AtuPlanTaskDetailDto findTaskDetailById(String taskId) {
        AtuPlanTaskDetailDto taskDetailDto = new AtuPlanTaskDetailDto();
        // 查询任务信息
        AtuPlanTask planTask = atuPlanTaskService.findByPrimaryKey(taskId);
        if (ObjectUtil.isNull(planTask)) {
            throw new PlatformRuntimeException(ExecPlanError.TASK_INFO_IS_NULL);
        }
        BeanUtils.copyProperties(planTask, taskDetailDto);
        taskDetailDto.setTaskId(planTask.getId());
        taskDetailDto.setExecResult(planTask.getStatus());

        //啄木鸟跳转ip赋值
        taskDetailDto.setHostStr(hostStr);
        // 脚本下载解析
        JSONObject scriptJson = downloadScriptFile(planTask.getScriptJson());
        if (scriptJson != null) {
            logger.debug("scriptJson => " + scriptJson);
            // 解析脚本，获取脚本参数信息
            JSONArray variables = new JSONArray();
            try {
                String str = scriptJson.getStr(MsgConstant.JSONARRAY_KEY_VARIABLES);
                if (str.contains(MsgConstant.JSONARRAY_KEY_REF)){//引用模式取数问题
                    variables = JSONUtil.parseArray(scriptJson.getStr(MsgConstant.JSONARRAY_KEY_VARIABLES_FIELD));
                }else {
                    variables = JSONUtil.parseArray(scriptJson.getStr(MsgConstant.JSONARRAY_KEY_VARIABLES));
                }
            } catch (Exception e) {
                logger.info("脚本参数内容 => " + JSONUtil.toJsonStr(scriptJson.getStr(MsgConstant.JSONARRAY_KEY_VARIABLES)));
                logger.error("脚本参数解析失败", e);
            }
            // 返回步骤的脚本列表
            List<TaskCaseStepDetailDto> steps = new ArrayList<>();
            if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, planTask.getCaseType())) {
                // 查询获取场景脚本的所有节点
                JSONArray nodes = scriptJson.getJSONArray(MsgConstant.JSONARRAY_KEY_NODES);

                // 根据执行结果排序节点
                //logger.debug("节点数组排序前 => " + JSONUtil.toJsonStr(nodes));
                nodes = orderByNodes(nodes, planTask.getId());
                //logger.debug("节点数组排序后 => " + JSONUtil.toJsonStr(nodes));

                List<TaskCaseStepDetailDto> execScripts = new ArrayList<>();
                List<TaskCaseStepDetailDto> unExecScripts = new ArrayList<>();
                for (Object node : nodes) {
                    JSONObject nodeJson = JSONUtil.parseObj(node);
                    logger.info("开始处理节点："+nodeJson);
                    String nodeType = nodeJson.getStr(MsgConstant.JSONOBJECT_KEY_NODETYPE);
                    if (ObjectUtil.equal(MsgConstant.START, nodeType) || ObjectUtil.equal(MsgConstant.END, nodeType)) {
                        continue;
                    }
                    AtuPlanSceneCaseTask query = new AtuPlanSceneCaseTask();
                    query.setTaskId(planTask.getId());
                    query.setNodeId(nodeJson.getStr(MsgConstant.ID));
                    AtuPlanSceneCaseTask sceneCaseTask = atuPlanSceneCaseTaskService.queryResultFileByTaskIdAndNodeId(query);

                    TaskCaseStepDetailDto stepDto = new TaskCaseStepDetailDto();
                    // 查询场景各个节点的脚本数据
                    stepDto.setStepId(nodeJson.getStr(MsgConstant.SCRIPT_ID));
                    stepDto.setStepName(nodeJson.getStr(MsgConstant.SCRIPT_NAME));
                    JSONObject nodeScriptJson = downloadScriptFile(nodeJson.getStr(MsgConstant.SCRIPT_URL));
                    if (ObjectUtil.isNotNull(nodeScriptJson)) {
                        // 节点执行结果文件由场景任务表中获取，有循环则查最后一条数据

                        String execResultFile = ObjectUtil.isNotNull(sceneCaseTask) ? sceneCaseTask.getExecResultFile() : "";
                        // 获取脚本步骤明细
                        List<TaskCaseStepDetailDto> stepList = findScriptStepDetail(execResultFile, nodeScriptJson, stepDto);
                        stepDto.setStepList(stepList);

                    }
                    if (ObjectUtil.isNotEmpty(sceneCaseTask)) {
                        execScripts.add(stepDto);
                    } else {
                        unExecScripts.add(stepDto);
                    }
                }
                steps.addAll(execScripts);
                steps.addAll(unExecScripts);

            } else {
                // 获取脚本步骤明细
                steps = findScriptStepDetail(planTask.getExecResultFile(), scriptJson, new TaskCaseStepDetailDto());
            }

            List<AtuVariableDto> variableDtoList = new ArrayList<>();
            if (ObjectUtil.isNotNull(variables)) {
                Map<String, Object> caseParamMap = new HashMap<>();
                if (StrUtil.isNotEmpty(planTask.getCaseParam())) {
                    caseParamMap = JSONUtil.parseObj(planTask.getCaseParam());
                }
                for (Object variable : variables) {
                    JSONObject variableJson = JSONUtil.parseObj(variable);
                    AtuVariableDto variableDto = new AtuVariableDto();
                    boolean input = false;
                    boolean output = false;
                    if (ObjectUtil.isNotNull(variableJson.get(MsgConstant.INPUT))) {
                        input = (boolean) variableJson.get(MsgConstant.INPUT);
                        logger.debug("解析到的 input 数据为 " + input);
                    }
                    if (ObjectUtil.isNotNull(variableJson.get(MsgConstant.OUTPUT))) {
                        output = (boolean) variableJson.get(MsgConstant.OUTPUT);
                    }
                    if (!input && !output) {
                        continue;
                    }

                    String name = variableJson.getStr(MsgConstant.NAME);
                    variableDto.setName(name);
                    variableDto.setType(variableJson.getStr(MsgConstant.TYPE));
                    // 脚本参数匹配实际值
                    if (input) {
                        if (caseParamMap.containsKey(name)) {
                            variableDto.setValue(caseParamMap.get(name).toString());
                        } else {
                            variableDto.setValue(variableJson.getStr(MsgConstant.DEFAULT_VALUE));
                        }
                        variableDto.setInput(true);
                        variableDtoList.add(variableDto);
                    }
                    if (output) {
                        AtuVariableDto variableDtoCopy = new AtuVariableDto();
                        BeanUtils.copyProperties(variableDto, variableDtoCopy);
                        variableDtoCopy.setInput(false);
                        variableDtoCopy.setValue(variableJson.getStr(MsgConstant.DEFAULT_VALUE));
                        variableDtoList.add(variableDtoCopy);
                    }
                }
            }
            // 脚本参数匹配实际值
            taskDetailDto.setCaseParams(variableDtoList);
            taskDetailDto.setSteps(steps);
        }
        // 查询引擎信息
        String engineId = "";
        // 判断是否场景用例，场景用例则查询场景任务表中的引擎信息
        if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, planTask.getCaseType())) {
            // 查询场景任务设备ID列表
            List<String> ids = new ArrayList<>();
            ids.add(planTask.getId());
            List<AtuPlanSceneCaseTask> atuPlanSceneCaseTasks = atuPlanSceneCaseTaskService.querySceneCaseTasksByTaskIds(ids);
            if (!CollectionUtils.isEmpty(atuPlanSceneCaseTasks)) {
                engineId = atuPlanSceneCaseTasks.get(0).getEngineId();
            }
        } else {
            engineId = planTask.getEngineId();
        }
        if (StringUtils.isNotBlank(engineId)) {
            CdEngineInfoDetailDto engineInfo = deviceFeignClient.queryEngineInfo(engineId);
            if (engineInfo != null) {
                taskDetailDto.setEngineIp(engineInfo.getIpAddr());
            }
        }
        // 查询设备信息
        Set<String> deviceIdList = new HashSet<>();
        // 判断是否场景用例，场景用例则查询多个设备信息
        if (ObjectUtil.equal(PlanConstant.SCRIPT_TYPE_SCENE, planTask.getCaseType())) {
            // 查询场景任务设备ID列表
            deviceIdList = atuPlanSceneCaseTaskService.queryDeviceIdListByTaskId(planTask.getId());
        } else {
            deviceIdList.add(planTask.getDeviceId());
        }

        if (planTask.getCaseType().equals(PlanConstant.SCRIPT_TYPE_SCENE)) {
            List<SceneNodeDto> sceneNodes = new ArrayList<>();
            //获取每个节点最后一次执行的内容
            List<AtuPlanSceneCaseTask> query = this.atuPlanSceneCaseTaskService.queryNodeInfo(planTask.getId());
            for (AtuPlanSceneCaseTask caseTask : query) {
                if (StrUtil.isNotBlank(caseTask.getVideoUrl())) {
                    SceneNodeDto node = new SceneNodeDto();
                    BeanUtils.copyProperties(caseTask, node);
                    //视频播放的设备类型(1、移动 2、PC)
                    switch (caseTask.getNodeType()) {
                        case PlanConstant.SCRIPT_TYPE_API:
                        case PlanConstant.SCRIPT_TYPE_BS:
                        case PlanConstant.SCRIPT_TYPE_CS:
                            node.setDeviceType(PlanConstant.DEVICE_TYPE_PC);
                            break;
                        case PlanConstant.SCRIPT_TYPE_ANDROID:
                        case PlanConstant.SCRIPT_TYPE_IOS:
                            node.setDeviceType(PlanConstant.DEVICE_TYPE_MOBILE);
                            break;
                    }
                    sceneNodes.add(node);
                }
            }
            taskDetailDto.setSceneData(sceneNodes);
        } else if (StrUtil.isNotBlank(planTask.getVideoUrl())) {
            List<SceneNodeDto> sceneNodes = new ArrayList<>();
            SceneNodeDto node = new SceneNodeDto();
            BeanUtils.copyProperties(planTask, node);
            switch (planTask.getCaseType()) {
                case PlanConstant.SCRIPT_TYPE_API:
                case PlanConstant.SCRIPT_TYPE_BS:
                case PlanConstant.SCRIPT_TYPE_CS:
                    node.setDeviceType(PlanConstant.DEVICE_TYPE_PC);
                    break;
                case PlanConstant.SCRIPT_TYPE_ANDROID:
                case PlanConstant.SCRIPT_TYPE_IOS:
                    node.setDeviceType(PlanConstant.DEVICE_TYPE_MOBILE);
                    break;
            }
            sceneNodes.add(node);
            taskDetailDto.setSceneData(sceneNodes);
        }

        try {
            List<Map<String, Object>> deviceInfoList = deviceFeignClient.queryDeviceInfo(deviceIdList);
            if (CollUtil.isNotEmpty(deviceInfoList)) {
                taskDetailDto.setDeviceInfoList(deviceInfoList);
            }
        } catch (Exception e) {
            logger.error("查询设备信息异常", e);
        }
        return taskDetailDto;
    }

    private JSONArray orderByNodes(JSONArray nodes, String taskId) {
        // 根据任务ID查询已经执行的节点结果
        AtuPlanSceneCaseTask sceneCaseTask = new AtuPlanSceneCaseTask();
        sceneCaseTask.setTaskId(taskId);
        OrderBy orderBy = new SqlOrderBy();
        orderBy.asc(PlanConstant.CREATED_TIME);
        List<AtuPlanSceneCaseTask> sceneCaseTasks = atuPlanSceneCaseTaskService.query(sceneCaseTask, orderBy);
        if (CollUtil.isEmpty(sceneCaseTasks)) {
            return nodes;
        }
        Map<String, Date> sceneCaseMap = sceneCaseTasks.stream()
                .collect(Collectors.toMap(AtuPlanSceneCaseTask::getNodeId, AtuPlanSceneCaseTask::getCreatedTime,
                        (v1, v2) -> v1));
        // 将执行顺序加入到节点集合中
        JSONArray nodeArray = new JSONArray();
        for (Object node : nodes) {
            JSONObject nodeJson = JSONUtil.parseObj(node);
            String nodeId = nodeJson.getStr("id");
            if (sceneCaseMap.containsKey(nodeId)) {
                nodeJson.set(PlanConstant.CREATED_TIME, sceneCaseMap.get(nodeId));
            } else {
                nodeJson.set(PlanConstant.CREATED_TIME, new Date());
            }
            nodeArray.add(nodeJson);
        }
        //logger.debug("节点数组增加时间排序前 => " + JSONUtil.toJsonStr(nodeArray));
        // 节点集合进行排序
        nodeArray.sort(Comparator.comparing(obj -> ((JSONObject) obj).getDate(PlanConstant.CREATED_TIME)));
        return nodeArray;
    }

    /**
     * 获取脚本步骤明细
     *
     * @param execResultFile 执行结果文件地址
     * @param scriptJson     脚本信息
     */
    private List<TaskCaseStepDetailDto> findScriptStepDetail(String execResultFile, JSONObject scriptJson,
                                                             TaskCaseStepDetailDto detailDto) {
        // 获取脚本执行结果信息
        Map<String, StepExecuteResult> stepResultMap = new HashMap<>();
        if (StrUtil.isNotEmpty(execResultFile)) {
            // 读取结果文件信息
            File file = null;
            try {
                file = simpleStorageService.downloadAsFile(NKSecurityContext.getTenantId(), execResultFile);
                String absolutePath = file.getAbsolutePath();
                String canonicalPath = file.getCanonicalPath();
                logger.debug("absolutePath => " + absolutePath);
                logger.debug("canonicalPath => " + canonicalPath);
                FileReader reader = new FileReader(file);
                List<String> lines = reader.readLines();
                for (String line : lines) {
                    if (StrUtil.isBlank(line)) {
                        continue;
                    }
                    StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
                    processStepResult(stepExecuteResult, stepResultMap, "");
                }
            } catch (Exception e) {
                logger.error("读取结果文件失败", e);
            } finally {
                // 删除下载的临时文件
                if (file != null) {
                    if (!file.delete()) {
                        logger.error("临时文件删除失败， file => " + file.getAbsolutePath());
                    }
                }
            }
        }

        JSONArray commands = JSONUtil.parseArray(scriptJson.get(MsgConstant.COMMANDS));
        return processStepData(commands, stepResultMap, scriptJson, detailDto, 0, false, false, "");
    }

    /**
     * 步骤执行结果处理
     *
     * @param stepExecuteResult 步骤执行结果信息
     * @param stepResultMap     步骤执行结果集合
     */
    private void processStepResult(StepExecuteResult stepExecuteResult, Map<String, StepExecuteResult> stepResultMap,
                                   String parentStepId) {
        String stepId = stepExecuteResult.getStepId();
        if (StrUtil.isNotBlank(parentStepId)) {
            stepId = parentStepId + "." + stepId;
        }
        stepResultMap.put(stepId, stepExecuteResult);
        if (CollUtil.isNotEmpty(stepExecuteResult.getChildResult())) {
            for (StepExecuteResult executeResult : stepExecuteResult.getChildResult()) {
                processStepResult(executeResult, stepResultMap, stepId);
            }
        }
    }

    /**
     * 处理步骤数据
     *
     * @param jsonArray     组件列表
     * @param stepResultMap 步骤结果集合
     * @param scriptJson    脚本json
     * @return 步骤集合
     */
    private List<TaskCaseStepDetailDto> processStepData(JSONArray jsonArray, Map<String, StepExecuteResult> stepResultMap,
                                                        JSONObject scriptJson, TaskCaseStepDetailDto parentDetail, int num,
                                                        boolean success, boolean executed, String parentStepId) {
        List<TaskCaseStepDetailDto> stepList = new ArrayList<>();
        if (ObjectUtil.isNull(jsonArray) || jsonArray.size() <= 0) {
            return stepList;
        }
        if (num == PlanConstant.MAX_STEP_ANALYSIS_NUM) {
            return stepList;
        }
        num++;
        for (Object command : jsonArray) {
            JSONObject commandJson = JSONUtil.parseObj(command);
            //logger.debug("commandJson => {}", JSONUtil.toJsonStr(commandJson));
            TaskCaseStepDetailDto stepDetailDto = new TaskCaseStepDetailDto();
            stepDetailDto.setStepId(commandJson.getStr(MsgConstant.ID));
            // 设置步骤名称
            String commandStr = commandJson.getStr(MsgConstant.COMMAND);
            JSONObject extAttrs = JSONUtil.parseObj(commandJson.getStr(MsgConstant.EXTATTRS));
            String title = "";
            String stepType = extAttrs.getStr(MsgConstant.EXTATTRS_STEPTYPE);
            // title: 点击元素名称 < stopName：步骤名称
            if (StrUtil.isNotBlank(extAttrs.getStr(MsgConstant.TITLE))) {
                title = extAttrs.getStr(MsgConstant.TITLE);
            }
            if (StrUtil.isNotBlank(extAttrs.getStr(MsgConstant.STEPNAME))) {
                title = extAttrs.getStr(MsgConstant.STEPNAME);
            }
            String commandName = extAttrs.getStr(MsgConstant.COMMAND_NAME);
            // command:组件英文名 < commandName:组件中文名
            String stepName = StrUtil.isBlank(commandName) ? commandStr + "   " + title : commandName + "   " + title;
            stepDetailDto.setStepName(WordUtils.replaceHtmlSpace(stepName));
            // 设置步骤描述
            List<Map<String, String>> stepDescList = new ArrayList<>();
            JSONArray targets = JSONUtil.parseArray(commandJson.getStr(MsgConstant.TARGETS));
            for (Object target : targets) {
                JSONObject targetJson = JSONUtil.parseObj(target);
                Map<String, String> stepDescMap = new HashMap<>();
                stepDescMap.put(MsgConstant.USING, targetJson.getStr(MsgConstant.USING));
                JSONObject value = JSONUtil.parseObj(targetJson.getStr(MsgConstant.VALUE));
                stepDescMap.put(MsgConstant.SELECTOR, value.getStr(MsgConstant.SELECTOR));
                stepDescList.add(stepDescMap);
            }
            stepDetailDto.setStepDescList(stepDescList);
            // 设置步骤类型 1-PC；2-移动
            String componentLib = commandJson.getStr(MsgConstant.COMPONENTLIB);
            stepDetailDto.setStepType(MsgConstant.STEPDETAILDTO_StepType_0);
            if (PlanConstant.PC_COMPONENT_LIB.equalsIgnoreCase(componentLib)) {
                stepDetailDto.setStepType(MsgConstant.STEPDETAILDTO_StepType_1);
            }
            if (PlanConstant.MOBILE_COMPONENT_LIB.equalsIgnoreCase(componentLib)) {
                stepDetailDto.setStepType(MsgConstant.STEPDETAILDTO_StepType_2);
            }
            if (PlanConstant.INTERFACE_COMPONENT_LIB.equalsIgnoreCase(componentLib)) {
                stepDetailDto.setStepType(MsgConstant.STEPDETAILDTO_StepType_3);
            }
            // 设置预期截图
            stepDetailDto.setImgUrl(extAttrs.getStr(MsgConstant.FULL_IMAGE));
            // 是否跳过
            stepDetailDto.setSkip(commandJson.getBool(MsgConstant.SKIP));
            // 设置步骤执行结果
            boolean childSuccess = false;
            boolean childExecuted = false;
            String stepId = stepDetailDto.getStepId();
            if (StrUtil.isNotBlank(parentStepId)) {
                // 解决多个相同业务组件引用问题
                stepId = parentStepId + "." + stepId;
            }
            if (CollUtil.isNotEmpty(stepResultMap)) {
                if (stepResultMap.containsKey(stepId)) {
                    // 步骤执行结果处理
                    StepExecuteResult stepExecuteResult = stepResultMap.get(stepId);
                    if (StrUtil.isNotBlank(stepExecuteResult.getOutputMsg())) {
                        String outputMsg = stepExecuteResult.getOutputMsg()
                                .replace("\r", "")
                                .replace("\n", "");
                        stepDetailDto.setOutputMsg(outputMsg);
                    }
                    List<String> paramList = new ArrayList<>();
                    if (CollUtil.isNotEmpty(stepExecuteResult.getParamMap())) {
                        stepExecuteResult.getParamMap().forEach((key, value) -> {
                            paramList.add(key + "=" + value);
                        });
                        stepDetailDto.setParamList(paramList);
                    }
                    success = stepExecuteResult.isSuccess();
                    executed = stepExecuteResult.isExecuted();
                    childSuccess = stepExecuteResult.isSuccess();
                    childExecuted = stepExecuteResult.isExecuted();

                    stepDetailDto.setExecTime(stepExecuteResult.getExecuteTime());
                    stepDetailDto.setActualImgUrl(stepExecuteResult.getActualImgUri());
                    stepDetailDto.setErrorMsg(stepExecuteResult.getMsg());
                    // 获取响应报文
                    Map<String, Object> result = stepExecuteResult.getResult();
                    if (stepDetailDto.getStepType() != null && "3".equalsIgnoreCase(stepDetailDto.getStepType()) && CollUtil.isNotEmpty(result)) {
                        Object keywordReturn = result.get(MsgConstant.KEYWORDRETURN);
                        if (ObjectUtil.isNotEmpty(keywordReturn)) {
                            stepDetailDto.setResponseMap(keywordReturn.toString());
                        }
                    }
                }
            }
            stepDetailDto.setExecResult(childSuccess);
            stepDetailDto.setExecuted(childExecuted);
            // 处理子步骤
            if (PlanConstant.GROUP_COMMAND.equals(commandStr)) {
                // 分区步骤结果特殊处理，分区下的步骤执行结果不在分区步骤的childResult中
                stepDetailDto.setStepList(processStepData(commandJson.getJSONArray(MsgConstant.CHILDREN), stepResultMap,
                        scriptJson, stepDetailDto, num, childSuccess, childExecuted, ""));
            } else {
                stepDetailDto.setStepList(processStepData(commandJson.getJSONArray(MsgConstant.CHILDREN), stepResultMap,
                        scriptJson, stepDetailDto, num, childSuccess, childExecuted, stepId));
            }
            // 判断是否内部组件
            if (ObjectUtil.equal(PlanConstant.INTERNAL_COMPONENT_LIB, componentLib)) {
                if (ObjectUtil.equal(PlanConstant.INTERFACE_STEP_TYPE, stepType) ||
                        ObjectUtil.equal(PlanConstant.BUSINESS_STEP_TYPE, stepType)) {
                    // 接口组件和业务组件
                    // 加入到该步骤信息中
                    stepDetailDto.setStepList(processBusinessComponent(scriptJson, commandJson, stepResultMap,
                            stepDetailDto, num, stepId));
                }
            }
            stepList.add(stepDetailDto);
        }
        parentDetail.setExecResult(success);
        parentDetail.setExecuted(executed);
        parentDetail.setStepList(stepList);
        return stepList;
    }

    /**
     * 处理接口和业务组件
     *
     * @param scriptJson    脚本json
     * @param commandJson   组件JSON
     * @param stepResultMap 步骤结果
     * @return 步骤列表
     */
    private List<TaskCaseStepDetailDto> processBusinessComponent(JSONObject scriptJson, JSONObject commandJson,
                                                                 Map<String, StepExecuteResult> stepResultMap,
                                                                 TaskCaseStepDetailDto parentDetail, int num,
                                                                 String parentStepId) {
        List<TaskCaseStepDetailDto> stepList = new ArrayList<>();
        // 获取command中args里的resId
        String args = commandJson.getStr(MsgConstant.ARGS);
        if (StrUtil.isBlank(args)) {
            return stepList;
        }
        JSONObject argsJson = JSONUtil.parseObj(args);
        String resId = argsJson.getStr(MsgConstant.RESID);
        if (StrUtil.isBlank(resId)) {
            return stepList;
        }
        // 更新resId在resources-scriptDependencies中查找对应的脚本存放地址resUri
        String resources = scriptJson.getStr(MsgConstant.RESOURCES);
        if (StrUtil.isBlank(resources)) {
            return stepList;
        }
        JSONObject resourcesJson = JSONUtil.parseObj(resources);
        JSONArray scriptDependencies = resourcesJson.getJSONArray(MsgConstant.SCRIPT_DEPENDENCIES);
        if (ObjectUtil.isNull(scriptDependencies) || scriptDependencies.size() <= 0) {
            return stepList;
        }
        String resUri = "";
        String resName = "";
        for (Object scriptDependency : scriptDependencies) {
            JSONObject jsonObject = JSONUtil.parseObj(scriptDependency);
            String setId = jsonObject.getStr(MsgConstant.RESID);
            if (ObjectUtil.equal(resId, setId)) {
                resUri = jsonObject.getStr(MsgConstant.RESURI);
                // 更新组件最新名称
                try {
                    ResultWrapper<Map<String, String>> resultWrapper = scriptCaseFeignClient.queryBusComNameMap(CollUtil.newArrayList(resId));
                    if (resultWrapper != null && resultWrapper.isSuccess() && CollUtil.isNotEmpty(resultWrapper.getData())) {
                        Map<String, String> busComNameMap = resultWrapper.getData();
                        if (busComNameMap.containsKey(setId)) {
                            resName = busComNameMap.get(setId);
                        }
                    }
                } catch (Exception e) {
                    logger.error("获取业务组件最新名称异常", e);
                }
            }
        }
        if (StrUtil.isNotBlank(resName)) {
            parentDetail.setStepName(resName);
        }

        // 根据脚本地址下载脚本解析
        try {
            JSONObject scriptJsonObj = downloadScriptFile(resUri);
            // 获取脚本中的步骤信息
            if (scriptJsonObj == null) {
                return stepList;
            }
            stepList = processStepData(scriptJsonObj.getJSONArray(MsgConstant.COMMANDS), stepResultMap, scriptJsonObj,
                    parentDetail, num, false, false, parentStepId);
        } catch (Exception e) {
            logger.error("下载解析脚本文件异常", e);
        }

        return stepList;
    }

    @Override
    public Boolean updateTaskExecFile(Map<String, String> params) {
        if (StrUtil.isEmpty(params.get(MsgConstant.TASKID)) || StrUtil.isEmpty(params.get(MsgConstant.FILEPATH))) {
            throw new PlatformRuntimeException(ExecPlanError.PARAMS_IS_NOT_NULL);
        }
        AtuPlanTask planTask = atuPlanTaskService.findByPrimaryKey(params.get(MsgConstant.TASKID));
        if (ObjectUtil.isNull(planTask)) {
            throw new PlatformRuntimeException(ExecPlanError.TASK_INFO_IS_NULL);
        }
        planTask.setExecResultFile(params.get(MsgConstant.FILEPATH));
        int row = atuPlanTaskService.updateByPrimaryKey(planTask);
        return row > 0;
    }

    /**
     * 发送任务执行信息至消息队列
     *
     * @param batchId        批次ID
     * @param atuTaskExecDto 任务执行信息
     */
    @Override
    public void sendToQueue(String batchId, AtuTaskExecDto atuTaskExecDto, String type) {
        String queueName = "";
        switch (type) {
            case PlanConstant.SCRIPT_TYPE_API:
                queueName = RabbitConstant.TASK_EXEC_QUEUE_API_KEY + batchId;
                break;
            case PlanConstant.SCRIPT_TYPE_BS:
            case PlanConstant.SCRIPT_TYPE_CS:
                queueName = RabbitConstant.TASK_EXEC_QUEUE_PC_KEY + batchId;
                break;
            case PlanConstant.SCRIPT_TYPE_ANDROID:
                queueName = RabbitConstant.TASK_EXEC_QUEUE_MOB_KEY + MsgConstant.ANDROID + "." + batchId;
                break;
            case PlanConstant.SCRIPT_TYPE_IOS:
                queueName = RabbitConstant.TASK_EXEC_QUEUE_MOB_KEY + MsgConstant.IOS + "." + batchId;
                break;
            default:
                break;
        }
        logger.debug("任务执行信息 => " + JSONUtil.toJsonStr(atuTaskExecDto));
        if (StrUtil.isEmpty(queueName)) {
            logger.error("消息队列名称或路由为空");
            return;
        }
        amqpAdmin.declareQueue(new Queue(queueName));
        amqpTemplate.convertAndSend(queueName, JSONUtil.toJsonStr(atuTaskExecDto));
    }

    /**
     * 根据脚本id查询脚本详情
     *
     * @param scriptIdSet 脚本id集合
     * @return 脚本详情
     */
    private Map<String, AtuScriptDetailDto> queryScriptDetailMap(Set<String> scriptIdSet) {
        Map<String, AtuScriptDetailDto> scriptMap = new HashMap<>();
        if (CollUtil.isEmpty(scriptIdSet)) {
            return scriptMap;
        }
        // 根据脚本id查询脚本信息
        try {
            ResultWrapper<Map<String, AtuScriptDetailDto>> scriptQueryResult = publicFeignClient.queryScriptDetailMap(scriptIdSet);
            if (ObjectUtil.isNotNull(scriptQueryResult) && scriptQueryResult.isSuccess()) {
                logger.debug("查询脚本结果：{}", JSONUtil.toJsonStr(scriptQueryResult));
                if (CollUtil.isEmpty(scriptQueryResult.getData())) {
                    throw new PlatformRuntimeException(ExecPlanError.QUERY_SCRIPT_INFO_IS_NULL);
                }
                scriptMap = scriptQueryResult.getData();
                logger.info("查询脚本结果数量：" + scriptMap.size());
                logger.info("脚本详情 => " + JSONUtil.toJsonStr(scriptMap));
            }
        } catch (Exception e) {
            logger.error("获取脚本信息异常", e);
        }
        return scriptMap;
    }

    @Override
    public void createdScriptTaskInfo(AtuPlanInfo planInfo, String batchId, List<String> envIdList,
                                      List<AtuPlanInputDto> inputDataList) {
        logger.info("开始整合任务生成信息...");
        logger.info("查询脚本详细信息");
        Set<String> scriptIdSet = inputDataList.stream().map(AtuPlanInputDto::getScriptId).collect(Collectors.toSet());
        logger.info("查询脚本数量：" + scriptIdSet.size());
        Map<String, AtuScriptDetailDto> scriptMap = queryScriptDetailMap(scriptIdSet);
        // 根据环境进行分组
        Map<String, List<AtuPlanInputDto>> envGroupMap = inputDataList.stream().collect(Collectors.groupingBy(AtuPlanInputDto::getEnvId));
        logger.debug("环境输入项分类数据 => {}", JSONUtil.toJsonStr(envGroupMap));
        logger.info("根据排序好的环境进行遍历");
        for (String envId : envIdList) {
            if (!envGroupMap.containsKey(envId)) {
                logger.debug("环境[{}]不在输入项数据环境中", envId);
                continue;
            }
            List<AtuPlanInputDto> planInputDtoList = envGroupMap.get(envId);
            if (CollUtil.isEmpty(planInputDtoList)) {
                logger.debug("环境[{}]的输入项数据为空", envId);
                continue;
            }
            List<AtuExceptionCase> exceptionCaseList = new ArrayList<>();
            for (AtuPlanInputDto atuPlanInputDto : planInputDtoList) {
                String scriptId = atuPlanInputDto.getScriptId();
                AtuScriptDetailDto atuScriptDetailDto = scriptMap.get(scriptId);
                if (atuScriptDetailDto == null) {
                    logger.debug("生成异常任务数据");
                    AtuExceptionCase exceptionCase = new AtuExceptionCase();
                    exceptionCase.setBatchId(batchId);
                    exceptionCase.setScriptId(atuPlanInputDto.getScriptId());
                    exceptionCase.setScriptName(MsgConstant.EMPTY);
                    exceptionCase.setScriptJson(MsgConstant.EMPTY);
                    exceptionCase.setCaseId(IdUtil.simpleUUID());
                    exceptionCase.setCaseName(MsgConstant.EMPTY);
                    exceptionCase.setCaseType(atuPlanInputDto.getScriptType());
                    exceptionCase.setCaseParam("");
                    exceptionCase.setErrorMsg("脚本信息不存在，请检查");
                    exceptionCaseList.add(exceptionCase);
                    continue;
                }
                if (CollUtil.isEmpty(atuPlanInputDto.getDataList())) {
                    logger.debug("脚本[{}]环境[{}]输入项文件数据为空", atuPlanInputDto.getScriptId(), atuPlanInputDto.getEnvId());
                    continue;
                }
                for (Map<String, Object> dataMap : atuPlanInputDto.getDataList()) {
                    logger.debug("输入项数据：" + JSONUtil.toJsonStr(dataMap));
                    createdCaseInfoToQueue(batchId, envId, dataMap, atuScriptDetailDto, planInfo, exceptionCaseList);
                }
            }
            // 异常用例信息
            if (CollUtil.isNotEmpty(exceptionCaseList)) {
                logger.info("生成异常用例任务信息");
                planBatchApiService.casePushNotice(exceptionCaseList);
            }
        }
    }

    /**
     * 创建用例信息并推送至队列
     *
     * @param batchId            批次id
     * @param envId              环境id
     * @param dataMap            输入项行数据
     * @param atuScriptDetailDto 脚本信息
     * @param planInfo           计划信息
     * @param exceptionCaseList  异常用例集合
     */
    private void createdCaseInfoToQueue(String batchId, String envId, Map<String, Object> dataMap,
                                        AtuScriptDetailDto atuScriptDetailDto, AtuPlanInfo planInfo,
                                        List<AtuExceptionCase> exceptionCaseList) {
        logger.debug("创建用例信息并推送至队列");
        AtuBatchCaseInfoDto caseInfoDto = new AtuBatchCaseInfoDto();
        caseInfoDto.setBatchId(batchId);
        String taskId = IdUtil.simpleUUID();
        caseInfoDto.setTaskId(taskId);
        caseInfoDto.setEnvId(envId);
        String caseId = IdUtil.simpleUUID();
        if (dataMap.get(PlanConstant.SCRIPT_TEST_PURPOSE_ID) != null) {
            caseId = dataMap.get(PlanConstant.SCRIPT_TEST_PURPOSE_ID).toString();
        }
        caseInfoDto.setCaseId(caseId);
        String caseName = "";
        if (dataMap.get(PlanConstant.SCRIPT_TEST_PURPOSE_NAME) != null) {
            caseName = dataMap.get(PlanConstant.SCRIPT_TEST_PURPOSE_NAME).toString();
        }
        caseInfoDto.setCaseName(caseName);
        caseInfoDto.setCaseParam(dataMap);

        String scriptId = atuScriptDetailDto.getScriptId();
        // 复制脚本信息
        BeanUtils.copyProperties(atuScriptDetailDto, caseInfoDto);
        // 用例类型与脚本类型一致
        caseInfoDto.setCaseType(atuScriptDetailDto.getScriptType());
        // 复制计划策略
        BeanUtils.copyProperties(planInfo, caseInfoDto);
        caseInfoDto.setPlanId(planInfo.getId());
        caseInfoDto.setFailRetryCount(0);
        if (planInfo.getFailRetry()) {
            caseInfoDto.setFailRetryCount(planInfo.getFailRetryCount());
        }

        // 判断是否场景用例
        if (PlanConstant.SCRIPT_TYPE_SCENE.equals(atuScriptDetailDto.getScriptType())) {
            logger.debug("场景脚本，获取首节点信息");
            SceneFirstNodeDto sceneFirstNodeDto = new SceneFirstNodeDto();
            sceneFirstNodeDto.setTaskId(taskId);
            sceneFirstNodeDto.setCaseId(caseId);
            sceneFirstNodeDto.setScriptId(scriptId);
            sceneFirstNodeDto.setParamMap(dataMap);
            try {
                ResultWrapper<AtuSceneNodeExecDto> firstNodeResult = publicFeignClient.getFirstNode(sceneFirstNodeDto);
                AtuSceneNodeExecDto retNode = firstNodeResult.getData();
                if (firstNodeResult.isSuccess() && retNode != null) {
                    caseInfoDto.setSceneNodeExecDto(retNode);
                    caseInfoDto.setScriptPath(retNode.getSceneScriptUrl());
                } else {
                    throw new RuntimeException("获取场景脚本[ " + scriptId + "]首节点信息异常，" + firstNodeResult.getMessage());
                }
            } catch (Exception e) {
                logger.error("获取场景脚本[{}]首节点信息异常", scriptId, e);
                // 生成异常用例信息
                AtuExceptionCase exceptionCase = new AtuExceptionCase();
                BeanUtils.copyProperties(caseInfoDto, exceptionCase);

                exceptionCase.setCaseType(PlanConstant.SCRIPT_TYPE_SCENE);
                exceptionCase.setCaseParam(JSONUtil.toJsonStr(dataMap));
                exceptionCase.setErrorMsg(e.getMessage());
                exceptionCaseList.add(exceptionCase);
                return;
            }
        }
        logger.debug("发送任务用例数据至队列");
        amqpTemplate.convertAndSend(RabbitConstant.PLAN_CASE_KEY, JSONUtil.toJsonStr(caseInfoDto));
    }

    @Override
    public AtuPlanTask findByPrimaryKey(String id) {
        return this.atuPlanTaskService.findByPrimaryKey(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean taskRetry(BatchRetryDto retryDto) {
        logger.debug("查询需要重试的任务信息");
        List<AtuPlanTask> planTaskList = this.atuPlanTaskService.findByPrimaryKeys(retryDto.getTaskIdList());
        logger.debug("查询需要重试的任务信息数量：{}", planTaskList.size());
        if (CollUtil.isEmpty(planTaskList)) {
            throw new PlatformRuntimeException(ExecPlanError.TASK_INFO_IS_NULL);
        }
        AtuPlanTask firstFailTask = planTaskList.get(0);
        logger.debug("判断任务批次是否完成");
        AtuPlanBatch planBatch = planBatchApiService.findByPrimaryKey(firstFailTask.getBatchId());
        if (PlanConstant.BATCH_WAITING_STATUS.equals(planBatch.getStatus()) ||
                PlanConstant.BATCH_EXECUTING_STATUS.equals(planBatch.getStatus())) {
            throw new PlatformRuntimeException(ExecPlanError.BATCH_NOT_FINISH);
        }
        logger.debug("根据批次编号[{}]查询计划信息", firstFailTask.getBatchId());
        AtuPlanInfo planInfo = atuPlanInfoApiService.findByBatchId(firstFailTask.getBatchId());
        if (planInfo == null) {
            throw new PlatformRuntimeException(ExecPlanError.PLAN_IS_NULL);
        }

        logger.debug("更新计划和批次状态");
        String planStatus = "";
        if (planBatch.getTaskTotal() > retryDto.getTaskIdList().size()) {
            logger.debug("部分任务重试，批次状态为：{}，计划状态为：{}", PlanConstant.BATCH_EXECUTING_STATUS,
                    PlanConstant.PLAN_EXECUTING_STATUS);
            planBatch.setStatus(PlanConstant.BATCH_EXECUTING_STATUS);
            planStatus = PlanConstant.PLAN_EXECUTING_STATUS;
        } else {
            logger.debug("全部任务重试，批次状态为：{}，计划状态为：{}", PlanConstant.BATCH_WAITING_STATUS,
                    PlanConstant.PLAN_WAITING_STATUS);
            planBatch.setStatus(PlanConstant.BATCH_WAITING_STATUS);
            planStatus = PlanConstant.PLAN_WAITING_STATUS;
        }
        List<String> deletedFilePath = new ArrayList<>();
        List<String> taskIds = new ArrayList<>();
        planTaskList.forEach(task -> {
            taskIds.add(task.getId());
            String execResultFile = task.getExecResultFile();
            if (StringUtils.isNotBlank(execResultFile)){
                deletedFilePath.add(execResultFile);
                getActualImagePath(deletedFilePath, execResultFile);
            }
            String perDataPath = task.getPerDataPath();
            if (StringUtils.isNotBlank(perDataPath)) {
                deletedFilePath.add("/" + MinioPathUtils.idToPath(perDataPath)[1]);
            }
            String videoUrl = task.getVideoUrl();
            if (StringUtils.isNotBlank(videoUrl)) {
                deletedFilePath.add("/" + MinioPathUtils.idToPath(videoUrl)[1]);
            }
            // 场景用例脚本json是单独的需要删除
            if (task.getCaseType().equals("5")) {
                deletedFilePath.add("/" + MinioPathUtils.idToPath(task.getScriptJson())[1]);
            }
        });
        if (!CollectionUtils.isEmpty(taskIds)) {
            List<AtuPlanSceneCaseTask> atuPlanSceneCaseTaskList = atuPlanSceneCaseTaskService.querySceneCaseTasksByTaskIds(taskIds);
            atuPlanSceneCaseTaskList.forEach(atuPlanSceneCaseTask -> {
                String videoUrl = atuPlanSceneCaseTask.getVideoUrl();
                if (StringUtils.isNotBlank(videoUrl)) {
                    deletedFilePath.add("/" + MinioPathUtils.idToPath(videoUrl)[1]);
                }
                String perDataPath = atuPlanSceneCaseTask.getPerDataPath();
                if (StringUtils.isNotBlank(perDataPath)) {
                    deletedFilePath.add("/" + MinioPathUtils.idToPath(perDataPath)[1]);
                }
                String execResultFile = atuPlanSceneCaseTask.getExecResultFile();
                if (StringUtils.isNotBlank(execResultFile)) {
                    deletedFilePath.add(execResultFile);
                    getActualImagePath(deletedFilePath, execResultFile);
                }
            });
        }
        if (!CollectionUtils.isEmpty(deletedFilePath)) {
            AssociatedFilesDto associatedFilesDto = new AssociatedFilesDto();
            associatedFilesDto.setObjId("-1");
            associatedFilesDto.setFileIds(deletedFilePath);
            attachmentFeignClient.associatedFiles(associatedFilesDto);
        }
        planBatchApiService.updateByEntity(planBatch);
        atuPlanInfoApiService.updateStatusByLastBatchId(planBatch.getId(), planStatus);

        logger.info("查询计划数据集中未被选择的意图数据");
        Set<String> scriptIdSet = new HashSet<>();
        Map<String, List<String>> envScriptMap = new HashMap<>();
        Map<String, List<AtuPlanTask>> envScriptTaskMap = new HashMap<>();
        List<String> taskIdList = new ArrayList<>();
        for (AtuPlanTask planTask : planTaskList) {
            taskIdList.add(planTask.getId());
            scriptIdSet.add(planTask.getScriptId());
            List<String> scriptIdList = new ArrayList<>();
            if (envScriptMap.containsKey(planTask.getEnvId())) {
                scriptIdList = envScriptMap.get(planTask.getEnvId());
            }
            scriptIdList.add(planTask.getScriptId());
            envScriptMap.put(planTask.getEnvId(), scriptIdList);
            String key = planTask.getEnvId() + "-" + planTask.getScriptId();
            List<AtuPlanTask> envScriptTaskList = new ArrayList<>();
            if (envScriptTaskMap.containsKey(key)) {
                envScriptTaskList = envScriptTaskMap.get(key);
            }
            envScriptTaskList.add(planTask);
            envScriptTaskMap.put(key, envScriptTaskList);
        }
        logger.debug("获取最新的输入项数据");
        Map<String, String> taskParamsMap = getRetryTaskInputInfo(envScriptTaskMap, envScriptMap);
        logger.debug("最新输入项数据大小：{}", taskParamsMap.size());

        logger.debug("start 初始化批次缓存数据");
        taskRetryInitCache(taskIdList, planBatch);
        logger.debug("end 初始化批次缓存数据");

        logger.debug("查询脚本最新数据");
        logger.debug("脚本数量：{}", scriptIdSet.size());
        Map<String, AtuScriptDetailDto> scriptMap = queryScriptDetailMap(scriptIdSet);
        logger.debug("脚本最新信息数量：{}", scriptMap.size());

        Map<String, Object> caseTypeMap = new HashMap<>();
        caseTypeMap.put("hasPc", false);
        caseTypeMap.put("hasMob", false);
        caseTypeMap.put("hasInterface", false);
        for (AtuPlanTask planTask : planTaskList) {
            String status = planTask.getStatus();
            logger.debug("原任务状态为： {}", status);
            // 判断任务是否开始执行
            if (PlanConstant.TASK_WAIT_EXECUTE_STATUS.equals(planTask.getStatus()) ||
                    PlanConstant.TASK_START_EXECUTE_STATUS.equals(planTask.getStatus())) {
                throw new PlatformRuntimeException(ExecPlanError.TASK_NOT_FINISH);
            }
            logger.debug("修改任务脚本为最新脚本信息");
            if (scriptMap.containsKey(planTask.getScriptId())) {
                AtuScriptDetailDto atuScriptDetailDto = scriptMap.get(planTask.getScriptId());
                planTask.setVersionId(atuScriptDetailDto.getVersionId());
                planTask.setVersionName(atuScriptDetailDto.getVersionName());
                planTask.setScriptName(atuScriptDetailDto.getScriptName());
                planTask.setScriptJson(atuScriptDetailDto.getScriptPath());
                planTask.setAppId(queryAppId(planInfo.getId(),atuScriptDetailDto.getPlatformType(),
                        atuScriptDetailDto.getAppPackage(),atuScriptDetailDto.getScriptType()));
            }

            logger.debug("生成任务执行信息");
            AtuTaskExecDto taskExecDto = new AtuTaskExecDto();
            BeanUtil.copyProperties(planInfo, taskExecDto);
            taskExecDto.setPlanId(planInfo.getId());
            taskExecDto.setFailRetryNum(planInfo.getFailRetryCount());
            taskExecDto.setBatchId(planTask.getBatchId());
            taskExecDto.setTaskId(planTask.getId());
            taskExecDto.setCaseId(planTask.getCaseId());
            taskExecDto.setEnvId(planTask.getEnvId());
            String type = planTask.getCaseType();
            taskExecDto.setCaseType(type);
            String scriptPath = planTask.getScriptJson();

            Map<String, Object> caseParams = new HashMap<>();
            if (taskParamsMap.containsKey(planTask.getId())) {
                logger.debug("重试使用新数据");
                caseParams = JSONUtil.toBean(taskParamsMap.get(planTask.getId()), Map.class);
            } else {
                if (StrUtil.isNotBlank(planTask.getCaseParam())) {
                    logger.debug("使用旧数据重试");
                    caseParams = JSONUtil.toBean(planTask.getCaseParam(), Map.class);
                }
            }
            // 更新静态数据与模糊数据为最新值
            caseParams = this.handleQuoteData(caseParams, planTask.getEnvId(), planInfo.getProjectId());
            planTask.setCaseParam(JSONUtil.toJsonStr(caseParams));

            String appId = planTask.getAppId();
            AtuSceneNodeExecDto sceneNodeExecDto = null;

            //判断是否场景任务
            if (PlanConstant.SCRIPT_TYPE_SCENE.equals(planTask.getCaseType())) {
                // 调用脚本服务获取场景首节点信息，通知脚本服务场景重新发起
                logger.debug("查询场景脚本的首节点信息");
                SceneFirstNodeDto firstNodeDto = new SceneFirstNodeDto();
                firstNodeDto.setTaskId(planTask.getId());
                firstNodeDto.setCaseId(planTask.getCaseId());
                String scriptId = planTask.getScriptId();
                firstNodeDto.setScriptId(scriptId);
                firstNodeDto.setParamMap(caseParams);
                logger.debug("查询参数 => {}", JSONUtil.toJsonStr(firstNodeDto));
                AtuSceneNodeInfoDto nodeInfo;
                try {
                    ResultWrapper<AtuSceneNodeExecDto> firstNodeResult = publicFeignClient.getFirstNode(firstNodeDto);
                    if (firstNodeResult.isSuccess() && firstNodeResult.getData() != null) {
                        logger.debug("首节点信息为 => " + JSONUtil.toJsonStr(firstNodeResult));
                        sceneNodeExecDto = firstNodeResult.getData();
                        nodeInfo = sceneNodeExecDto.getNodeInfo();
                    } else {
                        throw new RuntimeException("获取场景脚本[ " + scriptId + "]首节点信息异常，" + firstNodeResult.getMessage());
                    }
                } catch (Exception e) {
                    logger.error("获取场景首节点信息异常", e);
                    throw new PlatformRuntimeException(ExecPlanError.GET_SCENE_FIRST_NODE_FAIL);
                }
                if (ObjectUtil.isNull(nodeInfo)) {
                    throw new PlatformRuntimeException(ExecPlanError.SCENE_FIRST_NODE_INFO_IS_NULL);
                }

                String sceneTaskId = IdUtil.simpleUUID();
                taskExecDto.setTaskId(sceneTaskId);
                scriptPath = nodeInfo.getScriptPath();
                caseParams = nodeInfo.getCaseParam();
                appId = queryAppId(taskExecDto.getPlanId(), nodeInfo.getPlatformType(),
                        nodeInfo.getAppPackage(), nodeInfo.getNodeType());
                type = nodeInfo.getNodeType();

                logger.debug("清理原节点任务");
                AtuPlanSceneCaseTask deleteParams = new AtuPlanSceneCaseTask();
                deleteParams.setTaskId(planTask.getId());
                int rows = atuPlanSceneCaseTaskService.deleteByExample(deleteParams);
                if (rows <= 0) {
                    logger.error("清理原场景节点任务失败");
                    throw new PlatformRuntimeException(ExecPlanError.CLEAR_SCENE_NODE_TASK_FAIL);
                }
                logger.debug("插入新节点任务");
                AtuPlanSceneCaseTask planSceneCaseTask = new AtuPlanSceneCaseTask();
                planSceneCaseTask.setId(sceneTaskId);
                planSceneCaseTask.setTaskId(planTask.getId());
                planSceneCaseTask.setScriptId(nodeInfo.getScriptId());
                planSceneCaseTask.setVersionId(nodeInfo.getVersionId());
                planSceneCaseTask.setVersionName(nodeInfo.getVersionName());
                planSceneCaseTask.setScriptName(nodeInfo.getScriptName());
                planSceneCaseTask.setScriptJson(nodeInfo.getScriptPath());
                planSceneCaseTask.setNodeId(nodeInfo.getNodeId());
                planSceneCaseTask.setNodeType(nodeInfo.getNodeType());
                if (CollUtil.isNotEmpty(nodeInfo.getCaseParam())) {
                    planSceneCaseTask.setNodeParams(JSONUtil.toJsonStr(nodeInfo.getCaseParam()));
                }
                planSceneCaseTask.setCreatedTime(new Date());
                planSceneCaseTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
                planSceneCaseTask.setScriptJson(nodeInfo.getScriptPath());
                atuPlanSceneCaseTaskService.insert(planSceneCaseTask);

            }
            taskExecDto.setScriptPath(scriptPath);
            taskExecDto.setCaseParams(caseParams);
            taskExecDto.setAppId(appId);

            logger.debug("修改原任务状态");
            planTask.setEngineId("");
            planTask.setDeviceId("");
            planTask.setAppId("");
            planTask.setStatus(PlanConstant.TASK_WAIT_EXECUTE_STATUS);
            planTask.setStartTime(null);
            planTask.setEndTime(null);
            planTask.setErrorMsg("");
            planTask.setVideoUrl("");
            planTask.setExecResultFile("");
            planTask.setBugId("");
            if (sceneNodeExecDto != null && sceneNodeExecDto.getSceneScriptUrl() != null) {
                planTask.setScriptJson(sceneNodeExecDto.getSceneScriptUrl());
            }else {
                logger.info("首节点返回sceneScriptUrl为空" + sceneNodeExecDto);
            }
            int rows = atuPlanTaskService.updateByPrimaryKey(planTask);
            if (rows <= 0) {
                throw new PlatformRuntimeException(ExecPlanError.INIT_TASK_INFO_FAIL);
            }

            logger.debug("发送任务执行信息至消息队列");
            sendToQueue(taskExecDto.getBatchId(), taskExecDto, type);

            atuPlanInfoApiService.handleAutoType(PlanConstant.SCRIPT_TYPE_SCENE, caseTypeMap);
        }

        logger.debug("计划批次重新发起委托");
        atuPlanInfoApiService.handlePlanDevice(planInfo.getId(), planInfo.getPriority(), planBatch.getId(),
                retryDto.getHasOfflineDevice(), caseTypeMap, true);
        return true;
    }

    @Override
    public void getActualImagePath(List<String> deletedFilePath, String execResultFile) {
        InputStream inputStream = null;
        try {
            inputStream = simpleStorageService.downloadAsStream(NKSecurityContext.getTenantId(), execResultFile);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            List<String> actualImgUriList = new ArrayList<>();
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                StepExecuteResult stepExecuteResult = JSONUtil.toBean(line, StepExecuteResult.class);
                String actualImgUri = stepExecuteResult.getActualImgUri();
                if (StringUtils.isNotBlank(actualImgUri)) {
                    actualImgUriList.add("/" + MinioPathUtils.idToPath(actualImgUri)[1]);
                }
            }
            deletedFilePath.addAll(actualImgUriList);
        } catch (FileDownloadException e) {
            logger.error("文件下载异常", e);
        } catch (IOException e) {
            logger.error("文件流读取异常", e);
        }
    }

    private Map<String, String> getRetryTaskInputInfo(Map<String, List<AtuPlanTask>> envScriptTaskMap,
                                                      Map<String, List<String>> envScriptMap) {
        Map<String, String> taskParamsMap = new HashMap<>();
        List<AtuPlanInputDto> inputDataList = new ArrayList<>();
        try {
            ResultWrapper<List<AtuPlanInputDto>> queryResult = scriptCaseFeignClient.queryInputByEnvIdAndScriptIdMap(envScriptMap);
            if (queryResult != null && queryResult.isSuccess()) {
                if (CollUtil.isEmpty(queryResult.getData())) {
                    logger.debug("从脚本服务获取输入项数据为空");
                    throw new PlatformRuntimeException(ExecPlanError.SCRIPT_INPUT_DATA_IS_NULL);
                }
                inputDataList = queryResult.getData();
            }
        } catch (Exception e) {
            logger.error("查询脚本输入项数据异常", e);
            throw new PlatformRuntimeException(ExecPlanError.QUERY_SCRIPT_INPUT_IS_NULL);
        }
        logger.info("脚本输入项数量：" + inputDataList.size());
        if (CollUtil.isEmpty(inputDataList)) {
            logger.debug("获取输入项数据异常");
            throw new PlatformRuntimeException(ExecPlanError.SCRIPT_INPUT_DATA_IS_NULL);
        }
        Map<String, AtuPlanInputDto> envScriptInputMap = new HashMap<>();
        for (AtuPlanInputDto inputDto : inputDataList) {
            envScriptInputMap.put(inputDto.getEnvId() + "-" + inputDto.getScriptId(), inputDto);
        }

        envScriptTaskMap.forEach((envScriptId, taskList) -> {
            if (envScriptInputMap.containsKey(envScriptId)) {
                AtuPlanInputDto atuPlanInputDto = envScriptInputMap.get(envScriptId);
                String[] resultPath = MinioPathUtils.idToPath(atuPlanInputDto.getDataUrl());
                InputStream inputStream = null;
                List<Map<String, Object>> dataList;
                try {
                    inputStream = simpleStorageService.downloadAsStream(resultPath[0], "/" + resultPath[1]);
                    ScriptInputFileDto fileData = MinioPathUtils.getFileContent(inputStream, ScriptInputFileDto.class);
                    //获取数据
                    dataList = fileData.getTableContent();

                } catch (FileDownloadException e) {
                    logger.error("读取环境[{}]脚本[{}]输入项文件失败，文件地址：{}",
                            atuPlanInputDto.getEnvId(), atuPlanInputDto.getScriptId(), atuPlanInputDto.getDataUrl());
                    logger.error("失败原因：", e);
                    throw new PlatformRuntimeException(ExecPlanError.QUERY_SCRIPT_INPUT_ERROR, envNameUtils.getEnvName(atuPlanInputDto.getEnvId()),
                            taskList.get(0).getScriptName(), e.getMessage());
                } finally {
                    if (null != inputStream) {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            logger.error("", e);
                        }
                    }
                }
                if (CollUtil.isEmpty(dataList)) {
                    logger.debug("该用例集无用例数据");
                    throw new PlatformRuntimeException(ExecPlanError.SCRIPT_INPUT_NOT_DATA);
                }
                for (AtuPlanTask planTask : taskList) {
                    String params = "";
                    for (Map<String, Object> dataMap : dataList) {
                        if (dataMap.get(PlanConstant.SCRIPT_TEST_PURPOSE_ID).equals(planTask.getCaseId())) {
                            params = JSONUtil.toJsonStr(dataMap);
                        }
                    }
                    if (StrUtil.isBlank(params)) {
                        throw new PlatformRuntimeException(ExecPlanError.SCRIPT_INPUT_DELETED);
                    }
                    taskParamsMap.put(planTask.getId(), params);
                }
            } else {
                logger.error("脚本[{}]在环境[{}]下的输入项数据已被删除",
                        envScriptId.split("-")[1],
                        envScriptId.split("-")[0]);
            }
        });
        return taskParamsMap;
    }

    /**
     * 任务重试初始化缓存
     *
     * @param taskIdList 重试任务id
     * @param planBatch  重试任务批次信息
     */
    private void taskRetryInitCache(List<String> taskIdList, AtuPlanBatch planBatch) {
        AtuPlanTask queryParams = new AtuPlanTask();
        queryParams.setBatchId(planBatch.getId());
        List<AtuPlanTask> batchTaskList = this.atuPlanTaskService.query(queryParams);
        logger.debug("查询批次下的所有任务：{}", batchTaskList.size());
        // 重试的任务信息
        List<AtuPlanTask> retryTaskList = batchTaskList.stream()
                .filter(task -> taskIdList.contains(task.getId()))
                .collect(Collectors.toList());
        logger.debug("重试的任务：{}", retryTaskList.size());

        // 获取重试的脚本id集合
        Set<String> scriptIdSet = retryTaskList.stream().map(AtuPlanTask::getScriptId).collect(Collectors.toSet());
        logger.debug("重试的脚本：{}", scriptIdSet.size());
        // 需要重试的脚本，以及该脚本下的所有任务
        Map<String, List<AtuPlanTask>> scriptTaskMap = batchTaskList.stream()
                .filter(task -> scriptIdSet.contains(task.getScriptId()))
                .collect(Collectors.groupingBy(AtuPlanTask::getScriptId));

        String clusterKeyPrefix = RedisConstant.CLUSTER_KEY_PREFIX + planBatch.getId().substring(0, 4) +
                RedisConstant.CLUSTER_KEY_SUFFIX;
        for (String scriptId : scriptIdSet) {
            // 查询脚本下的所有任务，重试和未选择重试的
            List<AtuPlanTask> allScriptTaskList = scriptTaskMap.get(scriptId);
            Set<String> taskStatusSet = allScriptTaskList.stream().map(AtuPlanTask::getStatus).collect(Collectors.toSet());
            // 获取任务的统计状态
            String allTaskStatus = coverScriptStatus(taskStatusSet);
            // 排除重试的任务
            List<AtuPlanTask> notRetryTaskList = new ArrayList<>();
            List<AtuPlanTask> scriptRetryTaskList = new ArrayList<>();
            for (AtuPlanTask atuPlanTask : allScriptTaskList) {
                if (taskIdList.contains(atuPlanTask.getId())) {
                    scriptRetryTaskList.add(atuPlanTask);
                } else {
                    notRetryTaskList.add(atuPlanTask);
                }
            }

            for (AtuPlanTask atuPlanTask : scriptRetryTaskList) {
                String taskId = atuPlanTask.getId();
                redisTemplate.opsForHash().put(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_TASK_KEY +
                        planBatch.getId() + ":" + scriptId, clusterKeyPrefix + taskId, taskId);
            }
            if (CollUtil.isEmpty(notRetryTaskList)) {
                logger.debug("该脚本的任务全部重试");
                // allTaskStatus状态数据-1，增加脚本任务关联scriptRetryTaskList，脚本状态设置为等待中
                initBatchSumCache(planBatch, allTaskStatus, scriptId, PlanConstant.TASK_WAIT_EXECUTE_STATUS,
                        true, clusterKeyPrefix);
            } else {
                logger.debug("该脚本的任务部分重试");
                // 获取剩余任务的状态
                Set<String> notRetryTaskStatusSet = notRetryTaskList.stream().map(AtuPlanTask::getStatus).collect(Collectors.toSet());
                String remainTaskStatus = coverScriptStatus(notRetryTaskStatusSet);

                // allTaskStatus状态数据-1，增加脚本任务关联scriptRetryTaskList，脚本状态设置为remainTaskStatus
                initBatchSumCache(planBatch, allTaskStatus, scriptId, remainTaskStatus, false, clusterKeyPrefix);
            }

        }
    }

    /**
     * 初始化批次统计缓存数据
     *
     * @param planBatch    批次信息
     * @param status       统计数量-1的状态
     * @param scriptId     脚本id
     * @param scriptStatus 批次脚本状态
     */
    private void initBatchSumCache(AtuPlanBatch planBatch, String status, String scriptId, String scriptStatus,
                                   boolean isAll, String clusterKeyPrefix) {
        logger.debug("重试加载批次脚本统计缓存");
        List<String> scriptKeys = new ArrayList<>();
        scriptKeys.add(clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_SUM_KEY + planBatch.getId());
        scriptKeys.add(clusterKeyPrefix + PlanConstant.PLAN_BATCH_NUM);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.BATCH_START_TIME);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.SCRIPT_TOTAL);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_WAIT_EXECUTE_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_START_EXECUTE_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_EXECUTE_SUCCESS_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_EXECUTE_FAIL_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_ASSERT_FAIL_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_CANCEL_STATUS);
        scriptKeys.add(clusterKeyPrefix + PlanConstant.TASK_TIMEOUT_STATUS);
        scriptKeys.add(clusterKeyPrefix + status);
        String scriptStatusKey = clusterKeyPrefix + RedisConstant.BATCH_SCRIPT_STATUS_KEY + planBatch.getId();
        scriptKeys.add(scriptStatusKey);
        scriptKeys.add(clusterKeyPrefix + scriptId);

        long startTime = planBatch.getStartTime().getTime();
        if (isAll) {
            startTime = 0;
        }
        redisTemplate.execute(TASK_RETRY_INIT_SCRIPT, scriptKeys, planBatch.getBatch(),
                startTime, planBatch.getScriptNum(), planBatch.getSuccessNum(), planBatch.getExecFailNum(),
                planBatch.getAssertFailNum(), planBatch.getCancelNum(), planBatch.getTimeoutNum(), Integer.parseInt(scriptStatus));
    }

    /**
     * 脚本任务状态转换
     *
     * @param taskStatusSet 任务状态集合
     * @return 结果
     */
    private String coverScriptStatus(Set<String> taskStatusSet) {
        List<String> taskStatusList = new ArrayList<>(taskStatusSet);
        if (CollUtil.isEmpty(taskStatusList)) {
            throw new RuntimeException("任务状态数据异常");
        }
        String status = taskStatusList.get(0);
        logger.debug("任务状态：{}", status);
        if (taskStatusSet.contains(PlanConstant.TASK_CANCEL_STATUS)) {
            status = PlanConstant.TASK_CANCEL_STATUS;
        }
        if (taskStatusSet.contains(PlanConstant.TASK_TIMEOUT_STATUS)) {
            status = PlanConstant.TASK_TIMEOUT_STATUS;
        }
        if (taskStatusSet.contains(PlanConstant.TASK_EXECUTE_FAIL_STATUS)) {
            status = PlanConstant.TASK_EXECUTE_FAIL_STATUS;
        }
        logger.debug("转换后的状态：{}", status);
        return status;
    }

    @Override
    public String queryEnvIdByBatchId(String batchId) {
        return this.atuPlanTaskService.queryEnvIdByBatchId(batchId);
    }

    @Override
    public List<String> queryAppIdListByBatchId(String batchId) {
        return this.atuPlanTaskService.queryAppIdListByBatchId(batchId);
    }

    @Override
    public int deleteByPlanIdLimit(String planId, int num) {
        return this.atuPlanTaskService.deleteByPlanIdLimit(planId, num);
    }

    @Override
    public int countByPlanId(String planId) {
        return this.atuPlanTaskService.countByPlanId(planId);
    }

    @Override
    public AtuTaskSendBugDto querySendBugTaskInfo(String taskId) {
        AtuTaskSendBugDto atuTaskSendBugDto = this.atuPlanTaskService.querySendBugTaskInfo(taskId);
        if (atuTaskSendBugDto != null) {
            Object resultCode = redisTemplate.opsForValue().get("UITEST-ScriptCode:" + atuTaskSendBugDto.getScriptId());
            if (resultCode != null && !resultCode.equals("")) {
                atuTaskSendBugDto.setScriptCode(resultCode.toString());
            }
        }
        return atuTaskSendBugDto;
    }


    /**
     * 通知引擎取消批次
     *
     * @param batchId 批次ID
     */
    private void noticeEngineRemoveBatch(String batchId) {
        logger.debug("查询批次绑定的设备信息");
        AtuPlanBatchDeviceLink batchDeviceLink = new AtuPlanBatchDeviceLink();
        batchDeviceLink.setBatchId(batchId);
        List<AtuPlanBatchDeviceLink> batchDeviceLinks = atuPlanBatchDeviceLinkService.query(batchDeviceLink);
        logger.debug("批次绑定的设备数量为：{}", batchDeviceLinks.size());
        if (CollUtil.isNotEmpty(batchDeviceLinks)) {
            List<DeviceListDto> deviceTypeList = new ArrayList<>();

            List<String> pcDeviceIdList = new ArrayList<>();
            List<String> mobDeviceIdList = new ArrayList<>();
            for (AtuPlanBatchDeviceLink deviceLink : batchDeviceLinks) {

                if (ObjectUtil.equal(PlanConstant.ENGINE_TYPE_PC, deviceLink.getDeviceType())) {
                    pcDeviceIdList.add(deviceLink.getDeviceId());
                } else {
                    mobDeviceIdList.add(deviceLink.getDeviceId());
                }
            }
            if (CollUtil.isNotEmpty(pcDeviceIdList)) {
                DeviceListDto deviceListDto = new DeviceListDto();
                deviceListDto.setType(PlanConstant.ENGINE_TYPE_PC);
                deviceListDto.setDeviceList(pcDeviceIdList);
                deviceTypeList.add(deviceListDto);
            }
            if (CollUtil.isNotEmpty(mobDeviceIdList)) {
                DeviceListDto deviceListDto = new DeviceListDto();
                deviceListDto.setType(PlanConstant.ENGINE_TYPE_MOBILE);
                deviceListDto.setDeviceList(mobDeviceIdList);
                deviceTypeList.add(deviceListDto);
            }
            logger.debug("根据设备获取引擎信息");
            logger.debug("设备信息 => {}", JSONUtil.toJsonStr(deviceTypeList));
            List<EngineInfoListDto> engineInfoList = new ArrayList<>();
            try {
                engineInfoList = publicFeignClient.queryEngineByType(deviceTypeList);
            } catch (Exception e) {
                logger.error("获取引擎信息异常", e);
            }
            if (CollUtil.isEmpty(engineInfoList)) {
                logger.error("引擎信息不存在");
            }
            try {
                logger.debug("发送终止任务信息至引擎");
                for (EngineInfoListDto engineInfoListDto : engineInfoList) {
                    for (Map<String, String> engineInfoMap : engineInfoListDto.getEngineInfoList()) {
                        String ip = engineInfoMap.get("ip");
                        String port = engineInfoMap.get("port");
                        String url = "http://" + ip + ":" + port + PlanConstant.PLAN_BATCH_CANCEL_URL;
                        //String url = "http://192.168.77.71:6001" + PlanConstant.PLAN_BATCH_CANCEL_URL;
                        logger.info("引擎批次取消地址 => " + url);
                        RestTemplate restTemplate = new RestTemplate();
                        HttpHeaders headers = new HttpHeaders();
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        HttpEntity<String> requestEntity = new HttpEntity<>(batchId, headers);
                        try {
                            ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
                            logger.info("引擎批次取消结果 => " + JSONUtil.toJsonStr(response.getBody()));
                        } catch (Exception e) {
                            logger.error("引擎[" + ip + ":" + port + "]批次取消失败", e);
                        }

                    }
                }
            } catch (Exception e) {
                logger.error("发送取消委托失败", e);
            }
        }
    }

    /**
     * 处理引用数据
     *
     * @param caseParamMap 需要处理的参数集合
     * @param envId        环境id
     * @param projectId    系统id
     * @return 处理结果
     */
    public Map<String, Object> handleQuoteData(Map<String, Object> caseParamMap, String envId, String projectId) {
        if (CollUtil.isEmpty(caseParamMap)) {
            return caseParamMap;
        }
        for (String key : caseParamMap.keySet()) {
            try {
                if (caseParamMap.get(key) instanceof Map) {
                    QuoteData quoteData = BeanUtil.toBean(caseParamMap.get(key), QuoteData.class);
                    if (quoteData == null) {
                        continue;
                    }
                    String quoteValue = null;
                    if (quoteData.getType().equals(PlanConstant.INPUT_SET_QUOTE_TYPE_FUZZY)) {
                        logger.debug("查询模糊数据");
                        ResultWrapper<String> bodyResult = publicFeignClient.getRuleDataByName(quoteData.getColumn(), projectId);
                        if (bodyResult != null && bodyResult.isSuccess()) {
                            quoteValue = bodyResult.getData();
                        }
                    } else {
                        logger.debug("查询静态数据");
                        quoteData.setEnvId(envId);
                        QuoteData bodyResult = publicFeignClient.selectDataUrlAndTreeName(quoteData);
                        if (bodyResult != null) {
                            quoteValue = bodyResult.getValue();
                        }
                    }
                    if (null != quoteValue) {
                        caseParamMap.put(key, quoteValue);
                    }
                }
            } catch (Exception e) {
                logger.error("获取引用数据失败", e);
            }
        }
        return caseParamMap;
    }

    @Override
    public ScriptFirstExecutionDTO getFirstExecuteInfo(String scriptId) {
        return atuPlanTaskService.getFirstExecuteInfo(scriptId);
    }

    @Override
    public Integer getScriptExecuteSuccessNum(String scriptId, LocalDate now) {
        return atuPlanTaskService.getScriptExecuteSuccessNum(scriptId, now);
    }

    @Override
    public List<AtuPlanTask> getTasksByPlanId(String planId) {
        return this.atuPlanTaskService.getTasksByPlanId(planId);
    }

    @Override
    public List<String> getTaskIdsByScriptIdsAndPlanId(List<String> oldScriptIds, String planId) {
        return this.atuPlanTaskService.getTaskIdsByScriptIdsAndPlanId(oldScriptIds, planId);
    }

    /**
     * 处理分页查询参数
     *
     * @param query
     */
    private void handlePageParam(QueryByPage<AtuPlanTaskQueryDto> query) {
        if (query.getPageNo() <= 0) {
            query.setPageNo(1);
        }
        if (query.getPageSize() <= 0 || query.getPageSize() > 500) {
            query.setPageSize(500);
        }
        OrderBy sort = query.toSqlOrderBy();
        if (sort != null && sort.isNotEmpty() && org.springframework.util.StringUtils.hasText(sort.toString())) {
            PageMethod.orderBy(sort.toString());
        }
    }
}
