| | |
| | | import javax.annotation.Resource; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.Collections; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | |
| | | private SchedulerProperties schedulerProperties; |
| | | |
| | | /** |
| | | * 呼叫agv搬运 |
| | | * 记录上次处理的任务ID,用于轮询处理 |
| | | * 确保每次处理不同的任务,避免一直处理同一个任务 |
| | | */ |
| | | private Long lastProcessedTaskId = null; |
| | | |
| | | /** |
| | | * 记录上次分配站点的任务ID,用于轮询处理 |
| | | */ |
| | | private Long lastAllocatedTaskId = null; |
| | | |
| | | /** |
| | | * 分配站点定时任务 |
| | | * 查询状态7(待呼叫AGV)但没有分配站点的任务,为其分配可用站点 |
| | | * 只负责分配站点,不呼叫AGV |
| | | * 每次只处理一个任务,避免高并发执行 |
| | | */ |
| | | @Scheduled(cron = "0/5 * * * * ? ") |
| | | private void allocateSite() { |
| | | if (!schedulerProperties.isEnabled()) { |
| | | log.debug("定时任务allocateSite:调度器未启用,跳过执行"); |
| | | return; |
| | | } |
| | | |
| | | // 构建查询条件:查询所有待呼叫AGV但没有分配站点的任务 |
| | | EntityWrapper<Task> wrapper = new EntityWrapper<Task>(); |
| | | wrapper.eq("wrk_sts", 7); // 待呼叫AGV状态 |
| | | wrapper.eq("task_type", "agv"); // AGV任务类型 |
| | | wrapper.andNew("(is_deleted = 0)"); |
| | | wrapper.andNew() |
| | | .isNull("sta_no") |
| | | .or() |
| | | .eq("sta_no", "") |
| | | .or() |
| | | .eq("sta_no", "0"); |
| | | wrapper.orderBy("id", true); // 按id升序排序 |
| | | |
| | | // 如果上次处理过任务,从下一个任务开始查询(轮询) |
| | | if (lastAllocatedTaskId != null) { |
| | | wrapper.gt("id", lastAllocatedTaskId); |
| | | } |
| | | |
| | | // 查询待分配站点的任务,每次只查询一个 |
| | | List<Task> taskList = taskService.selectList( |
| | | wrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
| | | ); |
| | | |
| | | // 如果从上次任务之后没有找到任务,从头开始查询(实现循环轮询) |
| | | if (taskList.isEmpty() && lastAllocatedTaskId != null) { |
| | | lastAllocatedTaskId = null; // 重置,从头开始 |
| | | taskList = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("wrk_sts", 7) |
| | | .eq("task_type", "agv") |
| | | .andNew("(is_deleted = 0)") |
| | | .andNew() |
| | | .isNull("sta_no") |
| | | .or() |
| | | .eq("sta_no", "") |
| | | .or() |
| | | .eq("sta_no", "0") |
| | | .orderBy("id", true) |
| | | .last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
| | | ); |
| | | } |
| | | |
| | | if(taskList.isEmpty()) { |
| | | log.debug("定时任务allocateSite:没有待分配站点的任务(wrk_sts=7,task_type=agv,sta_no为空)"); |
| | | return; |
| | | } |
| | | |
| | | // 调用分配站点逻辑 |
| | | Task task = taskList.get(0); |
| | | String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId()); |
| | | log.info("定时任务allocateSite:开始为任务ID:{}分配站点(wrk_no={},ioType={})", |
| | | displayTaskId, task.getWrkNo(), task.getIoType()); |
| | | |
| | | String errorMsg = agvHandler.allocateSiteForTask(task); |
| | | |
| | | // 检查是否成功分配了站点 |
| | | String staNo = task.getStaNo(); |
| | | if (errorMsg == null && staNo != null && !staNo.isEmpty() && !staNo.equals("0")) { |
| | | // 分配站点成功 |
| | | lastAllocatedTaskId = task.getId(); |
| | | log.info("定时任务allocateSite:任务ID:{}成功分配站点:{},更新lastAllocatedTaskId为{}", |
| | | displayTaskId, staNo, lastAllocatedTaskId); |
| | | } else { |
| | | // 无法分配站点,不更新lastAllocatedTaskId,下次会重新尝试 |
| | | log.info("定时任务allocateSite:任务ID:{}无法分配站点:{},不更新lastAllocatedTaskId(当前:{}),下次将重新尝试", |
| | | displayTaskId, errorMsg != null ? errorMsg : "所有站点都被占用", lastAllocatedTaskId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 呼叫AGV定时任务 |
| | | * 查询状态7(待呼叫AGV)且已分配站点的任务,呼叫AGV |
| | | * 呼叫成功后,状态从7(待呼叫AGV)变为8(正在搬运) |
| | | * 每次只处理一个任务,避免高并发执行 |
| | | * 使用轮询机制,确保不会一直处理同一个任务 |
| | | */ |
| | | @Scheduled(cron = "0/5 * * * * ? ") |
| | | private void callAgv() { |
| | | if (!schedulerProperties.isEnabled()) { |
| | | log.debug("定时任务callAgv:调度器未启用,跳过执行"); |
| | | return; |
| | | } |
| | | // 查询待呼叫agv任务,按id升序排序(id最小的优先呼叫) |
| | | |
| | | // 构建查询条件:查询状态7(待呼叫AGV)且已分配站点的任务 |
| | | EntityWrapper<Task> wrapper = new EntityWrapper<Task>(); |
| | | wrapper.eq("wrk_sts", 7); // 待呼叫AGV状态 |
| | | wrapper.eq("task_type", "agv"); // AGV任务类型 |
| | | wrapper.andNew("(is_deleted = 0)"); |
| | | wrapper.isNotNull("sta_no"); // 必须有站点分配 |
| | | wrapper.ne("sta_no", ""); // 站点不能为空字符串 |
| | | wrapper.ne("sta_no", "0"); // 站点不能为0 |
| | | wrapper.orderBy("id", true); // 按id升序排序 |
| | | |
| | | // 如果上次处理过任务,从下一个任务开始查询(轮询) |
| | | if (lastProcessedTaskId != null) { |
| | | wrapper.gt("id", lastProcessedTaskId); |
| | | } |
| | | |
| | | // 查询待呼叫agv任务,每次只查询一个 |
| | | List<Task> taskList = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("wrk_sts", 7) |
| | | .orderBy("id", true) // 按id升序,id最小的优先 |
| | | wrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
| | | ); |
| | | |
| | | // 如果从上次任务之后没有找到任务,从头开始查询(实现循环轮询) |
| | | if (taskList.isEmpty() && lastProcessedTaskId != null) { |
| | | lastProcessedTaskId = null; // 重置,从头开始 |
| | | taskList = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("wrk_sts", 7) |
| | | .eq("task_type", "agv") |
| | | .andNew("(is_deleted = 0)") |
| | | .isNotNull("sta_no") |
| | | .ne("sta_no", "") |
| | | .ne("sta_no", "0") |
| | | .orderBy("id", true) |
| | | .last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY") |
| | | ); |
| | | } |
| | | |
| | | if(taskList.isEmpty()) { |
| | | log.debug("定时任务callAgv:没有待呼叫AGV的任务(wrk_sts=7,task_type=agv,sta_no不为空)"); |
| | | return; |
| | | } |
| | | agvHandler.callAgv(taskList); |
| | | |
| | | // 调用处理逻辑:呼叫AGV,成功后状态从7变为8 |
| | | Task task = taskList.get(0); |
| | | String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId()); |
| | | log.info("定时任务callAgv:开始处理任务ID:{}(wrk_no={},ioType={},sta_no={})", |
| | | displayTaskId, task.getWrkNo(), task.getIoType(), task.getStaNo()); |
| | | boolean processed = agvHandler.callAgv(taskList); |
| | | |
| | | // 只有当任务成功处理(成功呼叫AGV,状态从7变为8)时,才更新lastProcessedTaskId |
| | | // 如果任务被跳过(站点被占用等),不更新lastProcessedTaskId,下次会重新尝试 |
| | | if (processed) { |
| | | lastProcessedTaskId = task.getId(); |
| | | log.info("定时任务callAgv:任务ID:{}成功呼叫AGV,状态已从7变为8,更新lastProcessedTaskId为{},下次将处理下一个任务", |
| | | displayTaskId, lastProcessedTaskId); |
| | | } else { |
| | | log.info("定时任务callAgv:任务ID:{}被跳过,不更新lastProcessedTaskId(当前:{}),下次将重新尝试处理此任务", |
| | | displayTaskId, lastProcessedTaskId); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | if (!schedulerProperties.isEnabled()) { |
| | | return; |
| | | } |
| | | List<Task> taskList = taskService.selectList(new EntityWrapper<Task>().eq("wrk_sts", 9)); |
| | | List<Task> taskList = taskService.selectList(new EntityWrapper<Task>().eq("wrk_sts", 9).andNew("(is_deleted = 0)")); |
| | | if(taskList.isEmpty()) { |
| | | return; |
| | | } |
| | |
| | | Wrapper<Task> taskWrapper1 = new EntityWrapper<Task>() |
| | | .eq("task_type", "agv") |
| | | .eq("wrk_sts", 8L) // 已呼叫AGV状态 |
| | | .eq("wrk_no", wrkMast.getWrkNo()); |
| | | .eq("wrk_no", wrkMast.getWrkNo()) |
| | | .andNew("(is_deleted = 0)"); |
| | | List<Task> agvTasks = taskService.selectList(taskWrapper1); |
| | | |
| | | // 如果通过wrk_no没找到,且有条码,则通过条码查询 |
| | |
| | | Wrapper<Task> taskWrapper2 = new EntityWrapper<Task>() |
| | | .eq("task_type", "agv") |
| | | .eq("wrk_sts", 8L) |
| | | .eq("barcode", wrkMast.getBarcode()); |
| | | .eq("barcode", wrkMast.getBarcode()) |
| | | .andNew("(is_deleted = 0)"); |
| | | agvTasks = taskService.selectList(taskWrapper2); |
| | | } |
| | | |
| | |
| | | if (taskService.updateById(agvTask)) { |
| | | completedTasks.add(agvTask); |
| | | completedCount++; |
| | | // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID |
| | | String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId()); |
| | | log.info("入库任务工作档已入库成功,完结AGV呼叫单,taskId:{},wrkNo:{},barcode:{}", |
| | | agvTask.getId(), wrkMast.getWrkNo(), wrkMast.getBarcode()); |
| | | displayTaskId, wrkMast.getWrkNo(), wrkMast.getBarcode()); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 检查并修复异常状态的AGV任务:正在搬运但没有分配站点 |
| | | * 这种情况可能是数据异常或并发问题导致的 |
| | | */ |
| | | @Scheduled(cron = "0/30 * * * * ? ") |
| | | private void checkAbnormalTasksWithoutSite() { |
| | | if (!schedulerProperties.isEnabled()) { |
| | | return; |
| | | } |
| | | try { |
| | | // 查询状态为8(正在搬运)但没有分配站点的任务 |
| | | List<Task> abnormalTasks = taskService.selectList( |
| | | new EntityWrapper<Task>() |
| | | .eq("task_type", "agv") |
| | | .eq("wrk_sts", 8L) // 正在搬运 |
| | | .andNew("(is_deleted = 0)") |
| | | .andNew() |
| | | .isNull("sta_no") |
| | | .or() |
| | | .eq("sta_no", "") |
| | | .or() |
| | | .eq("sta_no", "0") |
| | | ); |
| | | |
| | | if (abnormalTasks.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | log.warn("检测到{}个异常状态的AGV任务:正在搬运但没有分配站点,开始修复", abnormalTasks.size()); |
| | | |
| | | Date now = new Date(); |
| | | int fixedCount = 0; |
| | | int completedCount = 0; |
| | | |
| | | for (Task task : abnormalTasks) { |
| | | String displayTaskId = (task.getWrkNo() != null) ? String.valueOf(task.getWrkNo()) : String.valueOf(task.getId()); |
| | | |
| | | // 检查工作档和历史档状态 |
| | | WrkMast wrkMast = null; |
| | | WrkMastLog wrkMastLog = null; |
| | | |
| | | if (task.getWrkNo() != null) { |
| | | wrkMast = wrkMastService.selectOne( |
| | | new EntityWrapper<WrkMast>().eq("wrk_no", task.getWrkNo()) |
| | | ); |
| | | wrkMastLog = wrkMastLogService.selectOne( |
| | | new EntityWrapper<WrkMastLog>().eq("wrk_no", task.getWrkNo()) |
| | | ); |
| | | } |
| | | |
| | | // 如果工作档已完成或已转历史档,直接结束任务 |
| | | boolean shouldComplete = false; |
| | | String reason = ""; |
| | | |
| | | if (wrkMastLog != null) { |
| | | shouldComplete = true; |
| | | reason = "工作档已转历史档"; |
| | | } else if (wrkMast != null) { |
| | | Long wrkSts = wrkMast.getWrkSts(); |
| | | Integer ioType = task.getIoType(); |
| | | |
| | | if (wrkSts != null && ioType != null) { |
| | | // 入库任务:状态4或5 |
| | | if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) && |
| | | (wrkSts == 4L || wrkSts == 5L)) { |
| | | shouldComplete = true; |
| | | reason = String.format("工作档已完成(入库),状态:%d", wrkSts); |
| | | } |
| | | // 出库任务:状态14或15 |
| | | else if ((ioType == 101 || ioType == 110 || ioType == 103 || ioType == 107) && |
| | | (wrkSts == 14L || wrkSts == 15L)) { |
| | | shouldComplete = true; |
| | | reason = String.format("工作档已完成(出库),状态:%d", wrkSts); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (shouldComplete) { |
| | | // 工作档已完成,直接结束任务 |
| | | task.setWrkSts(9L); |
| | | task.setModiTime(now); |
| | | if (taskService.updateById(task)) { |
| | | try { |
| | | agvHandler.moveTaskToHistory(Collections.singletonList(task)); |
| | | completedCount++; |
| | | log.info("修复异常任务:{},{},已结束任务并转移到历史表,taskId:{}", |
| | | reason, displayTaskId, displayTaskId); |
| | | } catch (Exception e) { |
| | | log.error("修复异常任务:转移任务到历史表失败,taskId:{}", displayTaskId, e); |
| | | } |
| | | } |
| | | } else { |
| | | // 工作档未完成,尝试分配站点或重置状态 |
| | | // 先尝试分配站点 |
| | | String errorMsg = agvHandler.allocateSiteForTask(task); |
| | | if (errorMsg == null && task.getStaNo() != null && !task.getStaNo().isEmpty() && !task.getStaNo().equals("0")) { |
| | | // 分配站点成功 |
| | | fixedCount++; |
| | | log.info("修复异常任务:已为任务分配站点,taskId:{},站点:{}", displayTaskId, task.getStaNo()); |
| | | } else { |
| | | // 无法分配站点,重置状态为7(待呼叫AGV),等待下次分配 |
| | | task.setWrkSts(7L); |
| | | task.setModiTime(now); |
| | | if (taskService.updateById(task)) { |
| | | fixedCount++; |
| | | log.warn("修复异常任务:无法分配站点,重置状态为7(待呼叫AGV),taskId:{},原因:{}", |
| | | displayTaskId, errorMsg != null ? errorMsg : "所有站点都被占用"); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (fixedCount > 0 || completedCount > 0) { |
| | | log.info("修复异常任务完成:修复了{}个任务,结束了{}个已完成工作档的任务", fixedCount, completedCount); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("检查并修复异常状态的AGV任务异常", e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查AGV任务对应的工作档是否已完成或已转历史档并完结 |
| | | * 处理被跳过的AGV任务: |
| | | * 1. 如果工作档已完成(wrk_sts=4,5,14,15)或已转历史档并完结,则完结AGV任务 |
| | | * 2. 如果工作档和历史档都没有数据,但是AGV已确认接收命令后超过1小时处于搬运中(状态8),也结束AGV任务 |
| | | * 注意:只有AGV确认接收命令后(plcStrTime不为空)才开始计时,如果AGV接受命令失败,会继续呼叫AGV |
| | | * 1. 如果工作档已完成(wrk_sts=4,5,14,15),则完结AGV任务 |
| | | * 2. 如果工作档进入历史档,立即结束AGV任务 |
| | | * 3. 如果入库成功,也结束掉搬运任务(已在checkInboundCompletedTasks中实现) |
| | | */ |
| | | @Scheduled(cron = "0/10 * * * * ? ") |
| | | private void checkCompletedTasksInHistory() { |
| | |
| | | new EntityWrapper<Task>() |
| | | .eq("task_type", "agv") |
| | | .eq("wrk_sts", 8L) // 已呼叫AGV状态 |
| | | .andNew("(is_deleted = 0)") |
| | | ); |
| | | |
| | | if (agvTasks.isEmpty()) { |
| | |
| | | Date now = new Date(); |
| | | int completedCount = 0; |
| | | List<Task> completedTasks = new ArrayList<>(); |
| | | // 1小时的毫秒数(从AGV确认接收命令开始计时) |
| | | long oneHourInMillis = 60 * 60 * 1000L; |
| | | |
| | | for (Task agvTask : agvTasks) { |
| | | boolean isCompleted = false; |
| | |
| | | } |
| | | } |
| | | |
| | | // 如果工作档不存在或未完成,检查历史档是否已完结 |
| | | // 1. 如果工作档进入历史档,立即结束AGV任务(只要历史档存在就结束) |
| | | if (!isCompleted && wrkMastLog != null) { |
| | | Integer ioType = agvTask.getIoType(); |
| | | long logWrkSts = wrkMastLog.getWrkSts(); |
| | | |
| | | if (ioType != null) { |
| | | // 入库任务:状态5(库存更新完成) |
| | | if ((ioType == 1 || ioType == 10 || ioType == 53 || ioType == 57) && |
| | | logWrkSts == 5L) { |
| | | isCompleted = true; |
| | | reason = String.format("工作档已转历史档并完结,历史档状态:%d", logWrkSts); |
| | | } |
| | | // 出库任务:状态15(出库更新完成) |
| | | else if ((ioType == 101 || ioType == 110 || ioType == 103 || ioType == 107) && |
| | | logWrkSts == 15L) { |
| | | isCompleted = true; |
| | | reason = String.format("工作档已转历史档并完结,历史档状态:%d", logWrkSts); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 如果工作档和历史档都没有数据,检查是否超过1小时(从AGV确认接收命令开始计时) |
| | | if (!isCompleted && wrkMast == null && wrkMastLog == null) { |
| | | // 只有AGV确认接收命令后(plcStrTime不为空)才开始计时 |
| | | // 如果plcStrTime为空,说明AGV还没有确认接收命令,会继续呼叫,不结束任务 |
| | | Date agvConfirmedTime = agvTask.getPlcStrTime(); |
| | | |
| | | if (agvConfirmedTime != null) { |
| | | long timeDiff = now.getTime() - agvConfirmedTime.getTime(); |
| | | if (timeDiff >= oneHourInMillis) { |
| | | isCompleted = true; |
| | | long hours = timeDiff / (60 * 60 * 1000L); |
| | | long minutes = (timeDiff % (60 * 60 * 1000L)) / (60 * 1000L); |
| | | reason = String.format("工作档和历史档都不存在,AGV确认接收命令后超过1小时(实际:%d小时%d分钟)处于搬运中,自动结束", hours, minutes); |
| | | } |
| | | } |
| | | // 如果plcStrTime为空,说明AGV还没有确认接收命令,不结束任务,继续等待呼叫 |
| | | isCompleted = true; |
| | | reason = String.format("工作档已转历史档,立即结束AGV任务,历史档状态:%d", wrkMastLog.getWrkSts()); |
| | | } |
| | | |
| | | // 如果已完成,更新AGV任务状态并收集到列表 |
| | |
| | | if (taskService.updateById(agvTask)) { |
| | | completedTasks.add(agvTask); |
| | | completedCount++; |
| | | // taskId使用工作号(wrk_no),如果工作号为空则使用任务ID |
| | | String displayTaskId = (agvTask.getWrkNo() != null) ? String.valueOf(agvTask.getWrkNo()) : String.valueOf(agvTask.getId()); |
| | | log.info("{},完结AGV呼叫单,taskId:{},wrkNo:{},barcode:{},站点:{}", |
| | | reason, agvTask.getId(), agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo()); |
| | | reason, displayTaskId, agvTask.getWrkNo(), agvTask.getBarcode(), agvTask.getStaNo()); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | if (completedCount > 0) { |
| | | log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档或确认接收后超时)", completedCount); |
| | | log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档)", completedCount); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("检查工作档已完成或历史档完结任务并完结AGV呼叫单异常", e); |