自动化立体仓库 - WMS系统
chen.llin
3 天以前 9611dc686299be640ce5e5f5990c747765161ec7
src/main/java/com/zy/asrs/task/AgvScheduler.java
@@ -21,6 +21,7 @@
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -55,23 +56,170 @@
    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);
        }
    }
    /**
@@ -115,7 +263,7 @@
        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;
        }
@@ -153,7 +301,8 @@
                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没找到,且有条码,则通过条码查询
@@ -161,7 +310,8 @@
                    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);
                }
                
@@ -176,8 +326,10 @@
                        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());
                        }
                    }
                }
@@ -202,11 +354,131 @@
    }
    /**
     * 检查并修复异常状态的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() {
@@ -219,6 +491,7 @@
                new EntityWrapper<Task>()
                    .eq("task_type", "agv")
                    .eq("wrk_sts", 8L)  // 已呼叫AGV状态
                    .andNew("(is_deleted = 0)")
            );
            
            if (agvTasks.isEmpty()) {
@@ -228,8 +501,6 @@
            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;
@@ -282,43 +553,10 @@
                    }
                }
                
                // 如果工作档不存在或未完成,检查历史档是否已完结
                // 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任务状态并收集到列表
@@ -328,8 +566,10 @@
                    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());
                    }
                }
            }
@@ -345,7 +585,7 @@
            }
            
            if (completedCount > 0) {
                log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档或确认接收后超时)", completedCount);
                log.info("本次检查完结了{}个AGV呼叫单(工作档已完成或已转历史档)", completedCount);
            }
        } catch (Exception e) {
            log.error("检查工作档已完成或历史档完结任务并完结AGV呼叫单异常", e);