| | |
| | | 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; |
| | |
| | | private BasDevpMapper basDevpMapper; |
| | | |
| | | @Resource |
| | | private LocCacheService locCacheService; |
| | | |
| | | @Resource |
| | | private AgvProperties agvProperties; |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | 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()) { |
| | | Integer allocatedSite = allocateSiteForTask(task); |
| | | if (allocatedSite == null) { |
| | | log.warn("任务ID:{}无法分配站点,跳过本次发送", task.getId()); |
| | | continue; // 无法分配站点,跳过本次发送 |
| | | String errorMsg = allocateSiteForTask(task); |
| | | if (errorMsg != null) { |
| | | // 无法分配站点,只在日志中记录,不记录到任务中(app不需要知道) |
| | | log.warn("任务ID:{}无法分配站点:{}", task.getId(), errorMsg); |
| | | continue; |
| | | } |
| | | staNo = String.valueOf(allocatedSite); |
| | | task.setStaNo(staNo); |
| | | taskService.updateById(task); |
| | | // 检查是否成功分配了站点(如果返回null且staNo仍为空,说明所有站点都在搬运,等待下次再试) |
| | | staNo = task.getStaNo(); |
| | | if (staNo == null || staNo.isEmpty()) { |
| | | // 所有站点都在搬运,暂不分配,等待下次定时任务再尝试 |
| | | continue; |
| | | } |
| | | log.info("任务ID:{}已分配站点:{}", task.getId(), staNo); |
| | | } |
| | | |
| | | // 检查目标站点是否有正在搬运的同类型AGV任务(出库和入库互不干扰) |
| | | // 只有状态8(已呼叫AGV,正在搬运)的任务才会阻塞,状态7(待呼叫)的任务不阻塞 |
| | | // 这样可以避免所有任务都卡在呼叫状态,按id最小的优先呼叫 |
| | | // 检查目标站点是否有效(不为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<BasDevp> basDevpList = basDevpMapper.selectList(new EntityWrapper<BasDevp>().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):只检查入库类型的正在搬运任务 |
| | |
| | | taskType = "出库"; |
| | | } |
| | | |
| | | // 只检查状态为8(已呼叫AGV,正在搬运)的同类型任务 |
| | | // 检查状态为8(已呼叫AGV,正在搬运)的同类型任务 |
| | | List<Task> transportingTasks = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("sta_no", staNo) |
| | |
| | | ); |
| | | |
| | | if (!transportingTasks.isEmpty()) { |
| | | log.info("站点{}有{}个正在搬运的{}AGV任务,跳过本次发送,等待搬运完成。当前任务ID:{}", |
| | | log.info("站点{}有{}个正在搬运的{}AGV任务,跳过当前任务ID:{}", |
| | | staNo, transportingTasks.size(), taskType, task.getId()); |
| | | continue; // 跳过本次发送,等待下次 |
| | | continue; |
| | | } |
| | | } |
| | | |
| | |
| | | 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() |
| | |
| | | |
| | | // 检查响应是否为空 |
| | | if (response == null || response.trim().isEmpty()) { |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},AGV接口返回为空", namespace, task.getId()); |
| | | 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) { |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},响应JSON解析失败,响应内容:{}", namespace, task.getId(), response); |
| | | 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"); |
| | | log.error("{}呼叫agv搬运失败 - 任务ID:{},错误码:{},错误信息:{}", |
| | | namespace, task.getId(), code, 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) { |
| | | log.error("{}呼叫agv搬运异常 - 任务ID:{},请求地址:{},请求参数:{},异常信息:{}", |
| | | namespace, task.getId(), url, body, e.getMessage(), 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 { |
| | | // 保存接口日志 |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 处理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; |
| | | } |
| | | |
| | | /** |
| | |
| | | /** |
| | | * 为任务分配站点(定时任务中调用) |
| | | * @param task 任务对象 |
| | | * @return 分配的站点编号,如果无法分配则返回null |
| | | * @return 如果无法分配站点,返回错误信息;如果分配成功,返回null并更新task的staNo |
| | | */ |
| | | private Integer allocateSiteForTask(Task task) { |
| | | private String allocateSiteForTask(Task task) { |
| | | // 根据任务的invWh(机器人组)判断是东侧还是西侧 |
| | | String robotGroup = task.getInvWh(); |
| | | List<String> targetStations; |
| | |
| | | } |
| | | |
| | | if (targetStations.isEmpty()) { |
| | | log.warn("任务ID:{}没有可用的目标站点配置", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有可用的目标站点配置"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 将站点字符串列表转换为整数列表 |
| | |
| | | .map(Integer::parseInt) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 判断能入站点(in_enable="Y"表示能入) |
| | | // 判断能入站点(in_enable="Y"表示能入),排除dev_no=0的无效站点 |
| | | List<Integer> sites = basDevpMapper.selectList( |
| | | new EntityWrapper<BasDevp>() |
| | | .eq("in_enable", "Y") |
| | | .in("dev_no", siteIntList) |
| | | ).stream().map(BasDevp::getDevNo).collect(Collectors.toList()); |
| | | .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()) { |
| | | log.warn("任务ID:{}没有能入站点", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有能入站点"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 获取没有出库任务的站点 |
| | | List<Integer> canInSites = basDevpMapper.getCanInSites(sites); |
| | | if (canInSites.isEmpty()) { |
| | | log.warn("任务ID:{}没有可入站点(请等待出库完成)", task.getId()); |
| | | return null; |
| | | String errorMsg = "请等待出库完成"; |
| | | log.warn("任务ID:{}没有可入站点({})", task.getId(), errorMsg); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入) |
| | | // 寻找入库任务最少的站点(且必须in_enable="Y"能入 和 canining="Y"可入),排除dev_no=0的无效站点 |
| | | List<BasDevp> devList = basDevpMapper.selectList(new EntityWrapper<BasDevp>() |
| | | .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()) { |
| | | log.warn("任务ID:{}没有可入站点(in_enable='Y'且canining='Y')", task.getId()); |
| | | return null; |
| | | String errorMsg = "没有可入站点(in_enable='Y'且canining='Y')"; |
| | | log.warn("任务ID:{}", errorMsg, task.getId()); |
| | | return errorMsg; |
| | | } |
| | | |
| | | // 先按规则排序(入库任务数排序) |
| | |
| | | break; |
| | | } |
| | | |
| | | // 如果所有站点都在搬运,则不分配站点 |
| | | // 如果所有站点都在搬运,则不分配站点(只在定时任务中记录日志,不返回错误信息) |
| | | if (selectedSite == null) { |
| | | log.warn("任务ID:{}的所有候选站点都有正在搬运的{}任务,暂不分配站点,等待空闲", |
| | | task.getId(), taskIoType != null && taskIoType < 100 ? "入库" : "出库"); |
| | | return 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 endSite; |
| | | return null; // 分配成功,返回null |
| | | } |
| | | |
| | | /** |