package com.zy.asrs.task.handler; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.zy.asrs.entity.Task; import com.zy.asrs.entity.TaskLog; import com.zy.asrs.entity.WrkMast; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.LocCache; import com.zy.asrs.mapper.BasDevpMapper; import com.zy.asrs.mapper.BasStationMapper; import com.zy.asrs.mapper.WrkMastMapper; import com.zy.asrs.service.ApiLogService; import com.zy.asrs.service.LocCacheService; import com.zy.asrs.service.TaskLogService; import com.zy.asrs.service.TaskService; import com.zy.common.constant.ApiInterfaceConstant; import com.zy.common.properties.AgvProperties; import com.zy.common.utils.HttpHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** * @author pang.jiabao * @description AGV交互相关定时任务处理类 * @createDate 2025/11/18 14:42 */ @Slf4j @Service public class AgvHandler { @Resource private WrkMastMapper wrkMastMapper; @Resource private ApiLogService apiLogService; @Resource private TaskService taskService; @Resource private TaskLogService taskLogService; @Resource private BasStationMapper basStationMapper; @Resource private BasDevpMapper basDevpMapper; @Resource private LocCacheService locCacheService; @Resource private AgvProperties agvProperties; /** * 站点轮询计数器,用于平均分配站点 * Key: 站点组标识(如 "east" 或 "west"),Value: 当前轮询索引 */ private final Map siteRoundRobinCounters = new ConcurrentHashMap<>(); /** * 呼叫agv搬运 */ public void callAgv(List taskList) { if (!agvProperties.isSendTask()) { return; } for (Task task : taskList) { // 如果任务状态已经是8(已呼叫AGV,正在搬运),则不再发送指令 if (task.getWrkSts() != null && task.getWrkSts() == 8L) { log.debug("任务ID:{}状态已是8(正在搬运),跳过发送", task.getId()); continue; } // 如果任务没有分配站点,先分配站点(只有为空时才分配,已经分配了不要清空) String staNo = task.getStaNo(); if (staNo == null || staNo.isEmpty()) { String errorMsg = allocateSiteForTask(task); if (errorMsg != null) { // 无法分配站点,只在日志中记录,不记录到任务中(app不需要知道) log.warn("任务ID:{}无法分配站点:{}", task.getId(), errorMsg); continue; } // 检查是否成功分配了站点(如果返回null且staNo仍为空,说明所有站点都在搬运,等待下次再试) staNo = task.getStaNo(); if (staNo == null || staNo.isEmpty()) { // 所有站点都在搬运,暂不分配,等待下次定时任务再尝试 continue; } log.info("任务ID:{}已分配站点:{}", task.getId(), staNo); } // 检查目标站点是否有效(不为0且存在) if (staNo != null && !staNo.isEmpty()) { try { Integer siteNo = Integer.parseInt(staNo); // 检查站点是否为0(无效站点),如果是0则不发送,但不清空站点 if (siteNo == null || siteNo == 0) { log.warn("任务ID:{}的目标站点{}无效(为0),跳过发送,保留站点分配", task.getId(), staNo); continue; } List basDevpList = basDevpMapper.selectList(new EntityWrapper().eq("dev_no", siteNo)); if (basDevpList == null || basDevpList.isEmpty()) { // 站点不存在,跳过发送,不清空站点 log.warn("任务ID:{}的目标站点{}不存在,跳过发送,保留站点分配", task.getId(), staNo); continue; } } catch (NumberFormatException e) { // 站点格式错误,跳过发送,不清空站点 log.warn("任务ID:{}的目标站点{}格式错误,跳过发送,保留站点分配", task.getId(), staNo); continue; } } else { // 没有站点,跳过 continue; } // 检查站点是否有状态8的同类型任务,有则跳过(不清空站点) if (staNo != null && !staNo.isEmpty() && task.getIoType() != null) { // 根据当前任务类型,只检查同类型的正在搬运任务(状态8) // 入库任务(ioType < 100):只检查入库类型的正在搬运任务 // 出库任务(ioType >= 100):只检查出库类型的正在搬运任务 List ioTypes; String taskType; if (task.getIoType() < 100) { // 入库任务:只检查入库类型(1, 10, 53, 57) ioTypes = Arrays.asList(1, 10, 53, 57); taskType = "入库"; } else { // 出库任务:只检查出库类型(101, 110, 103, 107) ioTypes = Arrays.asList(101, 110, 103, 107); taskType = "出库"; } // 检查状态为8(已呼叫AGV,正在搬运)的同类型任务 List transportingTasks = taskService.selectList( new EntityWrapper() .eq("sta_no", staNo) .eq("task_type", "agv") .eq("wrk_sts", 8L) // 只检查正在搬运状态的任务 .in("io_type", ioTypes) .ne("id", task.getId()) // 排除当前任务本身 ); if (!transportingTasks.isEmpty()) { log.info("站点{}有{}个正在搬运的{}AGV任务,跳过当前任务ID:{}", staNo, transportingTasks.size(), taskType, task.getId()); continue; } } // 呼叫agv String response = ""; boolean success = false; String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CREATE_TASK_PATH; String namespace = ""; switch (task.getIoType()) { case 1: case 10: case 53: case 57: namespace = "入库"; break; case 3: namespace = "转移"; break; case 101: case 110: case 103: case 107: namespace = "出库"; break; default: } String body = getRequest(task,namespace); // 获取当前重试次数 int currentRetryCount = getRetryCount(task); int maxRetryCount = agvProperties.getCallRetry().getMaxRetryCount(); boolean retryEnabled = agvProperties.getCallRetry().isEnabled(); // 如果重试次数已达到最大值,跳过本次发送 if (retryEnabled && currentRetryCount >= maxRetryCount) { log.warn("{}呼叫agv搬运 - 任务ID:{}已达到最大重试次数({}),停止重试", namespace, task.getId(), maxRetryCount); // 记录最终失败信息 task.setErrorTime(new Date()); task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次)", maxRetryCount)); taskService.updateById(task); continue; } // 打印请求信息(包含重试次数) if (currentRetryCount > 0) { log.info("{}呼叫agv搬运(第{}次重试) - 请求地址:{}", namespace, currentRetryCount + 1, url); } else { log.info("{}呼叫agv搬运 - 请求地址:{}", namespace, url); } log.info("{}呼叫agv搬运 - 请求参数:{}", namespace, body); try { // 使用仙工M4接口 response = new HttpHandler.Builder() .setUri(ApiInterfaceConstant.AGV_IP) .setPath(ApiInterfaceConstant.AGV_CREATE_TASK_PATH) .setJson(body) .build() .doPost(); // 打印返回参数 log.info("{}呼叫agv搬运 - 返回参数:{}", namespace, response); // 检查响应是否为空 if (response == null || response.trim().isEmpty()) { String errorMsg = "AGV接口返回为空"; log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); continue; } JSONObject jsonObject = JSON.parseObject(response); if (jsonObject == null) { String errorMsg = "响应JSON解析失败,响应内容:" + response; log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); continue; } Integer code = jsonObject.getInteger("code"); if (code != null && code.equals(200)) { // 呼叫成功,清除重试次数和错误信息 success = true; task.setWrkSts(8L); task.setMemo(clearRetryInfo(task.getMemo())); // 清除重试信息 task.setErrorTime(null); task.setErrorMemo(null); taskService.updateById(task); log.info("{}呼叫agv搬运成功 - 任务ID:{}", namespace, task.getId()); } else { String message = jsonObject.getString("message"); String errorMsg = String.format("错误码:%s,错误信息:%s", code, message); log.error("{}呼叫agv搬运失败 - 任务ID:{},{}", namespace, task.getId(), errorMsg); handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); } } catch (Exception e) { String errorMsg = "异常信息:" + e.getMessage(); log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},{}", namespace, task.getId(), url, body, errorMsg, e); handleCallFailure(task, namespace, errorMsg, retryEnabled, maxRetryCount, currentRetryCount); } finally { try { // 保存接口日志 apiLogService.save( namespace + "呼叫agv搬运", url, null, "127.0.0.1", body, response, success ); } catch (Exception e) { log.error(namespace + "呼叫agv保存接口日志异常:", e); } } } } /** * 处理AGV呼叫失败的情况 * @param task 任务对象 * @param namespace 命名空间(入库/出库/转移) * @param errorMsg 错误信息 * @param retryEnabled 是否启用重试 * @param maxRetryCount 最大重试次数 * @param currentRetryCount 当前重试次数 */ private void handleCallFailure(Task task, String namespace, String errorMsg, boolean retryEnabled, int maxRetryCount, int currentRetryCount) { if (retryEnabled && currentRetryCount < maxRetryCount) { // 增加重试次数 int newRetryCount = currentRetryCount + 1; task.setMemo(updateRetryCount(task.getMemo(), newRetryCount)); task.setErrorTime(new Date()); task.setErrorMemo(String.format("AGV呼叫失败(第%d次重试):%s", newRetryCount, errorMsg)); taskService.updateById(task); log.info("{}呼叫agv搬运失败 - 任务ID:{},已重试{}次,将在下次定时任务时继续重试(最多{}次)", namespace, task.getId(), newRetryCount, maxRetryCount); } else { // 不启用重试或已达到最大重试次数,停止重试 task.setErrorTime(new Date()); if (retryEnabled) { task.setErrorMemo(String.format("AGV呼叫失败,已达到最大重试次数(%d次):%s", maxRetryCount, errorMsg)); } else { task.setErrorMemo(String.format("AGV呼叫失败(重试未启用):%s", errorMsg)); } taskService.updateById(task); log.warn("{}呼叫agv搬运失败 - 任务ID:{},停止重试。错误信息:{}", namespace, task.getId(), errorMsg); } } /** * 从memo字段中获取重试次数 * memo格式:如果包含"retryCount:数字",则返回该数字,否则返回0 * @param task 任务对象 * @return 重试次数 */ private int getRetryCount(Task task) { String memo = task.getMemo(); if (memo == null || memo.trim().isEmpty()) { return 0; } try { // 查找 "retryCount:数字" 格式 String prefix = "retryCount:"; int index = memo.indexOf(prefix); if (index >= 0) { int startIndex = index + prefix.length(); int endIndex = memo.indexOf(",", startIndex); if (endIndex < 0) { endIndex = memo.length(); } String countStr = memo.substring(startIndex, endIndex).trim(); return Integer.parseInt(countStr); } } catch (Exception e) { log.warn("解析任务ID:{}的重试次数失败,memo:{}", task.getId(), memo, e); } return 0; } /** * 更新memo字段中的重试次数 * @param memo 原始memo内容 * @param retryCount 新的重试次数 * @return 更新后的memo内容 */ private String updateRetryCount(String memo, int retryCount) { if (memo == null) { memo = ""; } // 移除旧的retryCount信息 String cleanedMemo = clearRetryInfo(memo); // 添加新的retryCount信息 if (cleanedMemo.isEmpty()) { return "retryCount:" + retryCount; } else { return cleanedMemo + ",retryCount:" + retryCount; } } /** * 清除memo字段中的重试信息 * @param memo 原始memo内容 * @return 清除后的memo内容 */ private String clearRetryInfo(String memo) { if (memo == null || memo.trim().isEmpty()) { return ""; } // 移除 "retryCount:数字" 格式的内容 String result = memo.replaceAll("retryCount:\\d+", "").trim(); // 清理多余的逗号 result = result.replaceAll("^,|,$", "").replaceAll(",,+", ","); return result; } /** * 构造请求内容(仙工M4格式) */ private String getRequest(Task task, String nameSpace) { JSONObject object = new JSONObject(); // taskId使用任务ID,格式:T + 任务ID object.put("taskId", "T" + task.getId()); // fromBin使用源库位编号(sourceLocNo),如果为空则使用源站点编号(sourceStaNo)作为备选 String fromBin = task.getSourceLocNo(); if (fromBin == null || fromBin.isEmpty()) { fromBin = task.getSourceStaNo(); } if (fromBin == null || fromBin.isEmpty() || "0".equals(fromBin)) { log.warn("任务{}的源库位和源站点都为空,使用默认值", task.getId()); fromBin = "0"; } object.put("fromBin", fromBin); // toBin使用目标站点编号 object.put("toBin", task.getStaNo()); // robotGroup从invWh字段获取,如果没有则根据站点编号判断 String robotGroup = task.getInvWh(); if (robotGroup == null || robotGroup.isEmpty()) { robotGroup = determineRobotGroupByStation(task.getStaNo()); } object.put("robotGroup", robotGroup); // kind根据任务类型映射 String kind = ""; switch (nameSpace) { case "入库": // 判断是否为空托入库:ioType=10 或 emptyMk="Y" if (task.getIoType() == 10 || "Y".equals(task.getEmptyMk())) { kind = "空托入库"; } else { kind = "实托入库"; } break; case "出库": kind = "实托出库"; break; case "转移": kind = "货物转运"; break; default: kind = "货物转运"; } object.put("kind", kind); return object.toJSONString(); } /** * 为任务分配站点(定时任务中调用) * @param task 任务对象 * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo */ private String allocateSiteForTask(Task task) { // 根据任务的invWh(机器人组)判断是东侧还是西侧 String robotGroup = task.getInvWh(); List targetStations; String groupKey; if (robotGroup != null && robotGroup.equals(agvProperties.getRobotGroupEast())) { // 东侧站点 targetStations = agvProperties.getEastStations(); groupKey = "east"; } else if (robotGroup != null && robotGroup.equals(agvProperties.getRobotGroupWest())) { // 西侧站点 targetStations = agvProperties.getWestStations(); groupKey = "west"; } else { // 默认使用东侧 targetStations = agvProperties.getEastStations(); groupKey = "east"; log.warn("任务ID:{}的机器人组{}未识别,使用默认东侧站点", task.getId(), robotGroup); } if (targetStations.isEmpty()) { String errorMsg = "没有可用的目标站点配置"; log.warn("任务ID:{}", errorMsg, task.getId()); return errorMsg; } // 将站点字符串列表转换为整数列表 List siteIntList = targetStations.stream() .map(Integer::parseInt) .collect(Collectors.toList()); // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点 List sites = basDevpMapper.selectList( new EntityWrapper() .eq("in_enable", "Y") .in("dev_no", siteIntList) .ne("dev_no", 0) // 排除dev_no=0的无效站点 ).stream() .map(BasDevp::getDevNo) .filter(devNo -> devNo != null && devNo != 0) // 再次过滤,确保不为null或0 .collect(Collectors.toList()); if (sites.isEmpty()) { String errorMsg = "没有能入站点"; log.warn("任务ID:{}", errorMsg, task.getId()); return errorMsg; } // 先检查站点配置(canining="Y"可入),排除dev_no=0的无效站点 List devListWithConfig = basDevpMapper.selectList(new EntityWrapper() .in("dev_no", sites) .eq("in_enable", "Y") .eq("canining", "Y") .ne("dev_no", 0) // 排除dev_no=0的无效站点 ).stream() .filter(dev -> dev.getDevNo() != null && dev.getDevNo() != 0) // 再次过滤,确保不为null或0 .collect(Collectors.toList()); if (devListWithConfig.isEmpty()) { // 站点配置不允许入库(canining != "Y"),暂不分配,等待配置开通(只在定时任务中记录日志) log.warn("任务ID:{}没有可入站点(站点未开通可入允许:canining='Y'),暂不分配站点,等待配置开通", task.getId()); return null; // 返回null,表示暂不分配,等待配置开通 } // 获取没有出库任务的站点(从已配置可入的站点中筛选) List configuredSites = devListWithConfig.stream() .map(BasDevp::getDevNo) .collect(Collectors.toList()); List canInSites = basDevpMapper.getCanInSites(configuredSites); if (canInSites.isEmpty()) { // 所有已配置可入的站点都有出库任务,暂不分配,等待下次定时任务再尝试(只在定时任务中记录日志) log.warn("任务ID:{}没有可入站点(请等待出库完成),暂不分配站点,等待下次定时任务再尝试", task.getId()); return null; // 返回null,表示暂不分配,等待下次定时任务再尝试 } // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入),排除dev_no=0的无效站点 List devList = basDevpMapper.selectList(new EntityWrapper() .in("dev_no", canInSites) .eq("in_enable", "Y") .eq("canining", "Y") .ne("dev_no", 0) // 排除dev_no=0的无效站点 ).stream() .filter(dev -> dev.getDevNo() != null && dev.getDevNo() != 0) // 再次过滤,确保不为null或0 .collect(Collectors.toList()); if (devList.isEmpty()) { // 理论上不应该到这里,因为前面已经检查过了,但为了安全起见还是保留 String errorMsg = "没有可入站点(in_enable='Y'且canining='Y')"; log.warn("任务ID:{}", errorMsg, task.getId()); return errorMsg; } // 先按规则排序(入库任务数排序) devList.sort(Comparator.comparing(BasDevp::getInQty)); // 根据任务类型确定要检查的io_type列表 Integer taskIoType = task.getIoType(); List checkIoTypes = null; String taskTypeName = ""; if (taskIoType != null) { if (taskIoType < 100) { // 入库任务:只检查入库类型(1, 10, 53, 57) checkIoTypes = Arrays.asList(1, 10, 53, 57); taskTypeName = "入库"; } else { // 出库任务:只检查出库类型(101, 110, 103, 107) checkIoTypes = Arrays.asList(101, 110, 103, 107); taskTypeName = "出库"; } } // 筛选出任务数最少的站点列表(按规则排序后的候选站点) int minInQty = devList.get(0).getInQty(); List minTaskSites = devList.stream() .filter(dev -> dev.getInQty() == minInQty) .collect(Collectors.toList()); // 根据配置选择分配策略,确定优先分配的站点顺序 List orderedSites = new ArrayList<>(); String strategy = agvProperties.getSiteAllocation().getStrategy(); boolean enableRoundRobin = agvProperties.getSiteAllocation().isEnableRoundRobin(); if (minTaskSites.size() > 1 && enableRoundRobin && "round-robin".equals(strategy)) { // 轮询分配:先按轮询策略排序 AtomicInteger counter = siteRoundRobinCounters.computeIfAbsent(groupKey, k -> new AtomicInteger(0)); int startIndex = counter.get() % minTaskSites.size(); // 将轮询选中的站点放在最前面 orderedSites.addAll(minTaskSites.subList(startIndex, minTaskSites.size())); orderedSites.addAll(minTaskSites.subList(0, startIndex)); // 添加其他站点(任务数更多的) orderedSites.addAll(devList.stream() .filter(dev -> dev.getInQty() > minInQty) .collect(Collectors.toList())); log.debug("使用轮询分配策略,站点组:{},轮询起始索引:{}", groupKey, startIndex); } else if (minTaskSites.size() > 1 && enableRoundRobin && "random".equals(strategy)) { // 随机分配:先随机排序任务数最少的站点 List shuffledMinSites = new ArrayList<>(minTaskSites); Collections.shuffle(shuffledMinSites); orderedSites.addAll(shuffledMinSites); // 添加其他站点(任务数更多的) orderedSites.addAll(devList.stream() .filter(dev -> dev.getInQty() > minInQty) .collect(Collectors.toList())); log.debug("使用随机分配策略"); } else { // 默认:按入库任务数排序(已经排序好了) orderedSites = devList; } // 依次检查每个站点是否在搬运,找到第一个空闲站点就分配 BasDevp selectedSite = null; for (BasDevp dev : orderedSites) { String staNo = String.valueOf(dev.getDevNo()); // 如果任务类型不为空,检查该站点是否有正在搬运的同类型任务 boolean isTransporting = false; if (checkIoTypes != null && !checkIoTypes.isEmpty()) { List transportingTasks = taskService.selectList( new EntityWrapper() .eq("sta_no", staNo) .eq("task_type", "agv") .eq("wrk_sts", 8L) // 只检查正在搬运状态的任务 .in("io_type", checkIoTypes) ); isTransporting = !transportingTasks.isEmpty(); if (isTransporting) { log.debug("站点{}有{}个正在搬运的{}AGV任务,检查下一个站点", staNo, transportingTasks.size(), taskTypeName); continue; // 该站点正在搬运,检查下一个站点 } } // 找到第一个空闲站点,分配 selectedSite = dev; log.info("任务ID:{}按规则应分配到站点{},该站点空闲,分配成功", task.getId(), staNo); break; } // 如果所有站点都在搬运,则不分配站点(只在定时任务中记录日志,不返回错误信息) if (selectedSite == null) { // log.warn("任务ID:{},暂不分配站点,等待空闲 - 所有候选站点都有正在搬运的{}任务", // task.getId(), taskIoType != null && taskIoType < 100 ? "入库" : "出库"); return null; // 返回null,表示暂不分配,等待下次定时任务再尝试 } Integer endSite = selectedSite.getDevNo(); // 检查站点是否有效(不能为0或null) if (endSite == null || endSite == 0) { String errorMsg = String.format("分配的站点无效(dev_no=%s)", endSite); log.error("任务ID:{},{}", task.getId(), errorMsg); return errorMsg; } // 入库暂存+1 basDevpMapper.incrementInQty(endSite); // 更新任务的站点编号 task.setStaNo(String.valueOf(endSite)); taskService.updateById(task); log.info("任务ID:{}已分配站点:{}", task.getId(), endSite); return null; // 分配成功,返回null } /** * 根据站点编号判断机器人组 * @param staNo 站点编号 * @return 机器人组名称 */ private String determineRobotGroupByStation(String staNo) { if (staNo == null || staNo.isEmpty()) { return agvProperties.getRobotGroupEast(); // 默认使用东侧机器人组 } // 从配置中获取站点列表 Set eastStations = new HashSet<>(agvProperties.getEastStations()); Set westStations = new HashSet<>(agvProperties.getWestStations()); // 判断站点属于哪一侧 if (eastStations.contains(staNo)) { return agvProperties.getRobotGroupEast(); // 东侧机器人 } else if (westStations.contains(staNo)) { return agvProperties.getRobotGroupWest(); // 西侧机器人 } else { log.warn("站点编号不在配置列表中,使用默认机器人组:{}", staNo); return agvProperties.getRobotGroupEast(); // 默认使用东侧机器人组 } } /** * 任务完成转历史 释放暂存点 */ @Transactional(rollbackFor = Exception.class) public void moveTaskToHistory(List taskList) { // 写入历史表,保持ID一致 for (Task task : taskList) { TaskLog log = new TaskLog(); BeanUtils.copyProperties(task, log); // 保持ID一致,不设置为null taskLogService.insert(log); } // 批量删除原任务 List taskIds = taskList.stream().map(Task::getId).collect(Collectors.toList()); taskService.delete(new EntityWrapper().in("id",taskIds)); // 批量更新暂存点状态 List locOList = new ArrayList<>(); List locFList = new ArrayList<>(); for (Task task : taskList) { String sourceStaNo = task.getSourceStaNo(); String staNo = task.getStaNo(); if (task.getIoType() == 3) { locOList.add(sourceStaNo); locFList.add(staNo); } else if (task.getIoType() < 100) { locOList.add(sourceStaNo); } else { locFList.add(staNo); } } if (!locOList.isEmpty()) { basStationMapper.updateLocStsBatch(locOList, "O"); } if (!locFList.isEmpty()) { basStationMapper.updateLocStsBatch(locFList, "F"); } log.info("agv任务档转历史成功:{}", taskIds); } /** * 货物到达出库口,生成agv任务 */ public void createAgvOutTasks(List sites) { // 获取到可用出库站点的任务 List wrkMastList = wrkMastMapper.selectList(new EntityWrapper().eq("call_agv", 1).in("sta_no",sites)); for(WrkMast wrkMast:wrkMastList) { // todo 计算agv目标暂存位 int endSite = 2004; // 插入agv任务 Task task = new Task(wrkMast.getWrkNo(), 7L, wrkMast.getIoType(), String.valueOf(wrkMast.getStaNo()), String.valueOf(endSite), null, wrkMast.getBarcode()); taskService.insert(task); // 更新任务档agv搬运标识 wrkMast.setCallAgv(2); wrkMastMapper.updateById(wrkMast); // 更新暂存位状态 S.入库预约 basStationMapper.updateLocStsBatch( Collections.singletonList(String.valueOf(endSite)), "S"); } } /** * 取消AGV任务(仙工M4接口) * @param task 任务对象 * @return 是否成功 */ public boolean cancelAgvTask(Task task) { if (!agvProperties.isSendTask()) { return false; } if (task == null || task.getId() == null) { log.error("取消AGV任务失败:任务或任务ID为空"); return false; } String response = ""; boolean success = false; String url = ApiInterfaceConstant.AGV_IP + ApiInterfaceConstant.AGV_CANCEL_TASK_PATH; String namespace = ""; String kind = ""; // 根据任务类型确定kind和namespace switch (task.getIoType()) { case 1: case 10: case 53: case 57: namespace = "入库"; kind = "实托入库"; break; case 3: namespace = "转移"; kind = "货物转运"; break; case 101: case 110: case 103: case 107: namespace = "出库"; kind = "实托出库"; break; default: kind = "货物转运"; } // 构造取消任务请求 JSONObject cancelRequest = new JSONObject(); cancelRequest.put("taskId", "T" + task.getId()); cancelRequest.put("kind", kind); String body = cancelRequest.toJSONString(); try { response = new HttpHandler.Builder() .setUri(ApiInterfaceConstant.AGV_IP) .setPath(ApiInterfaceConstant.AGV_CANCEL_TASK_PATH) .setJson(body) .build() .doPost(); JSONObject jsonObject = JSON.parseObject(response); if (jsonObject.getInteger("code") != null && jsonObject.getInteger("code").equals(200)) { success = true; log.info(namespace + "取消AGV任务成功:{}", task.getId()); } else { log.error(namespace + "取消AGV任务失败!!!url:{};request:{};response:{}", url, body, response); } } catch (Exception e) { log.error(namespace + "取消AGV任务异常", e); } finally { try { // 保存接口日志 apiLogService.save( namespace + "取消AGV任务", url, null, "127.0.0.1", body, response, success ); } catch (Exception e) { log.error(namespace + "取消AGV任务保存接口日志异常:", e); } } return success; } }