Junjie
8 天以前 c319790d54867b63f27089b511d05076254e7990
Merge remote-tracking branch 'origin/master'
5个文件已修改
627 ■■■■■ 已修改文件
src/main/java/com/zy/core/task/MainProcessLane.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java 164 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java 421 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/task/MainProcessLane.java
@@ -10,6 +10,7 @@
    STATION_ENABLE_IN("station-enable-in-"),
    STATION_IN("station-in-"),
    STATION_OUT("station-out-"),
    STATION_OUT_PENDING_CONFIRM("station-out-pending-confirm-"),
    DUAL_STATION_OUT("dual-station-out-"),
    STATION_OUT_FINISH("station-out-finish-"),
    STATION_IN_ARRIVAL("station-in-arrival-"),
@@ -21,6 +22,7 @@
    FAKE_CRN_IO_FINISH("fake-crn-io-finish-"),
    FAKE_STATION_IN("fake-station-in-"),
    FAKE_STATION_OUT("fake-station-out-"),
    FAKE_STATION_OUT_PENDING_CONFIRM("fake-station-out-pending-confirm-"),
    FAKE_STATION_OUT_ORDER("fake-station-out-order-"),
    FAKE_STATION_RUN_BLOCK("fake-station-run-block-"),
    FAKE_DUAL_CRN_IO("fake-dual-crn-io-"),
src/main/java/com/zy/core/utils/CrnOperateProcessUtils.java
@@ -141,10 +141,10 @@
                        WrkStsType.NEW_OUTBOUND.sts,
                        WrkStsType.NEW_LOC_MOVE.sts));
        taskQueue.sort(Comparator.comparingInt((WrkMast wrkMast) -> resolveTaskTypeRank(wrkMast, lastIo))
                .thenComparingInt(this::resolveBatchOutboundRank)
                .thenComparingInt(this::resolveBatchSeqOrder)
                .thenComparingDouble(this::resolveTaskIoPri)
                .thenComparingLong(this::resolveTaskQueueTime)
                .thenComparingInt(this::resolveBatchOutboundRank)
                .thenComparingInt(this::resolveBatchSeqOrder)
                .thenComparingInt(this::resolveTaskQueueNo));
        for (WrkMast wrkMast : taskQueue) {
@@ -775,96 +775,96 @@
        if (basCrnp == null || basCrnp.getCrnNo() == null) {
            return;
        }
            String key = RedisKeyType.PLANNER_SCHEDULE.key + "CRN-" + basCrnp.getCrnNo();
            List<Object> items = redisUtil.lGet(key, 0, -1);
            if (items == null || items.isEmpty()) {
                return;
        String key = RedisKeyType.PLANNER_SCHEDULE.key + "CRN-" + basCrnp.getCrnNo();
        List<Object> items = redisUtil.lGet(key, 0, -1);
        if (items == null || items.isEmpty()) {
            return;
        }
        CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
        if (crnThread == null) {
            return;
        }
        CrnProtocol crnProtocol = crnThread.getStatus();
        if (crnProtocol == null) {
            return;
        }
        List<WrkMast> running = wrkMastService.list(new QueryWrapper<WrkMast>()
                .eq("crn_no", basCrnp.getCrnNo())
                .in("wrk_sts", WrkStsType.INBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN.sts, WrkStsType.LOC_MOVE_RUN.sts, WrkStsType.CRN_MOVE_RUN.sts)
        );
        if (!running.isEmpty()) {
            return;
        }
        if (!(crnProtocol.getMode() == CrnModeType.AUTO.id
                && crnProtocol.getTaskNo() == 0
                && crnProtocol.getStatus() == CrnStatusType.IDLE.id
                && crnProtocol.getLoaded() == 0
                && crnProtocol.getForkPos() == 0
                && crnProtocol.getAlarm() == 0)) {
            return;
        }
        for (Object v : items) {
            String s = String.valueOf(v);
            JSONObject obj = null;
            try { obj = JSON.parseObject(s); } catch (Exception ignore) {}
            if (obj == null) {
                continue;
            }
            Integer startEpochSec = obj.getInteger("startEpochSec");
            Integer endEpochSec = obj.getInteger("endEpochSec");
            Integer taskId = obj.getInteger("taskId");
            String taskType = obj.getString("taskType");
            if (startEpochSec == null || taskId == null || taskType == null) {
                continue;
            }
            int earlySlackSec = 5;
            int lateSlackSec = 10;
            Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
            if (systemConfigMapObj != null) {
                try {
                    HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
                    String es = systemConfigMap.getOrDefault("plannerEarlySlackSec", "60");
                    String ls = systemConfigMap.getOrDefault("plannerLateSlackSec", "10");
                    earlySlackSec = Integer.parseInt(es);
                    lateSlackSec = Integer.parseInt(ls);
                } catch (Exception ignore) {}
            }
            if (nowSec < startEpochSec - earlySlackSec) {
                continue;
            }
            if (endEpochSec != null && nowSec > endEpochSec + lateSlackSec) {
                redisUtil.lRemove(key, 1, s);
                continue;
            }
            CrnThread crnThread = (CrnThread) SlaveConnection.get(SlaveType.Crn, basCrnp.getCrnNo());
            if (crnThread == null) {
                return;
            }
            CrnProtocol crnProtocol = crnThread.getStatus();
            if (crnProtocol == null) {
                return;
            }
            List<WrkMast> running = wrkMastService.list(new QueryWrapper<WrkMast>()
                    .eq("crn_no", basCrnp.getCrnNo())
                    .in("wrk_sts", WrkStsType.INBOUND_RUN.sts, WrkStsType.OUTBOUND_RUN.sts, WrkStsType.LOC_MOVE_RUN.sts, WrkStsType.CRN_MOVE_RUN.sts)
            );
            if (!running.isEmpty()) {
                return;
            }
            if (!(crnProtocol.getMode() == CrnModeType.AUTO.id
                    && crnProtocol.getTaskNo() == 0
                    && crnProtocol.getStatus() == CrnStatusType.IDLE.id
                    && crnProtocol.getLoaded() == 0
                    && crnProtocol.getForkPos() == 0
                    && crnProtocol.getAlarm() == 0)) {
                return;
            WrkMast wrkMast = wrkMastService.selectByWorkNo(taskId);
            if (wrkMast == null) {
                redisUtil.lRemove(key, 1, s);
                continue;
            }
            for (Object v : items) {
                String s = String.valueOf(v);
                JSONObject obj = null;
                try { obj = JSON.parseObject(s); } catch (Exception ignore) {}
                if (obj == null) {
                    continue;
                }
                Integer startEpochSec = obj.getInteger("startEpochSec");
                Integer endEpochSec = obj.getInteger("endEpochSec");
                Integer taskId = obj.getInteger("taskId");
                String taskType = obj.getString("taskType");
                if (startEpochSec == null || taskId == null || taskType == null) {
                    continue;
                }
                int earlySlackSec = 5;
                int lateSlackSec = 10;
                Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
                if (systemConfigMapObj != null) {
                    try {
                        HashMap<String, String> systemConfigMap = (HashMap<String, String>) systemConfigMapObj;
                        String es = systemConfigMap.getOrDefault("plannerEarlySlackSec", "60");
                        String ls = systemConfigMap.getOrDefault("plannerLateSlackSec", "10");
                        earlySlackSec = Integer.parseInt(es);
                        lateSlackSec = Integer.parseInt(ls);
                    } catch (Exception ignore) {}
                }
                if (nowSec < startEpochSec - earlySlackSec) {
                    continue;
                }
                if (endEpochSec != null && nowSec > endEpochSec + lateSlackSec) {
            if ("IN".equalsIgnoreCase(taskType)) {
                boolean result = this.crnExecuteInPlanner(basCrnp, crnThread, wrkMast);//入库
                if (result) {
                    redisUtil.lRemove(key, 1, s);
                    continue;
                    break;
                }
                WrkMast wrkMast = wrkMastService.selectByWorkNo(taskId);
                if (wrkMast == null) {
            } else if ("OUT".equalsIgnoreCase(taskType)) {
                boolean result = this.crnExecuteOutPlanner(basCrnp, crnThread, wrkMast);//出库
                if (result) {
                    redisUtil.lRemove(key, 1, s);
                    continue;
                    break;
                }
                if ("IN".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteInPlanner(basCrnp, crnThread, wrkMast);//入库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
                } else if ("OUT".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteOutPlanner(basCrnp, crnThread, wrkMast);//出库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
                } else if ("MOVE".equalsIgnoreCase(taskType)) {
                    boolean result = this.crnExecuteMovePlanner(basCrnp, crnThread, wrkMast);//移库
                    if (result) {
                        redisUtil.lRemove(key, 1, s);
                        break;
                    }
            } else if ("MOVE".equalsIgnoreCase(taskType)) {
                boolean result = this.crnExecuteMovePlanner(basCrnp, crnThread, wrkMast);//移库
                if (result) {
                    redisUtil.lRemove(key, 1, s);
                    break;
                }
            }
        }
    }
    private synchronized boolean crnExecuteMovePlanner(BasCrnp basCrnp, CrnThread crnThread, WrkMast wrkMast) {
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -32,6 +32,7 @@
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -301,22 +302,43 @@
    public void submitCrnStationOutTasks(MainProcessLane lane, long minIntervalMs) {
        List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
                .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
                .isNotNull("crn_no"));
                .isNotNull("crn_no")
                .orderByAsc("io_time", "wrk_no"));
        MainProcessLane pendingConfirmLane = resolveStationOutPendingConfirmLane(lane);
        LinkedHashSet<Integer> sourceStationIdSet = new LinkedHashSet<>();
        for (WrkMast wrkMast : wrkMasts) {
            Integer laneKey = wrkMast == null ? null : wrkMast.getSourceStaNo();
            if (laneKey == null) {
                laneKey = wrkMast == null ? null : wrkMast.getWrkNo();
            if (wrkMast == null || wrkMast.getWrkNo() == null) {
                continue;
            }
            mainProcessTaskSubmitter.submitKeyedSerialTask(
                    lane,
                    laneKey,
                    "crnStationOutExecute",
                    pendingConfirmLane,
                    wrkMast.getWrkNo(),
                    "confirmPendingCrnStationOutDispatch",
                    minIntervalMs,
                    () -> crnStationOutExecute(wrkMast)
                    () -> stationOutboundDispatchProcessor.confirmPendingCrnStationOutDispatch(wrkMast)
            );
            if (wrkMast.getSourceStaNo() != null) {
                sourceStationIdSet.add(wrkMast.getSourceStaNo());
            }
        }
        for (Integer sourceStationId : sourceStationIdSet) {
            mainProcessTaskSubmitter.submitKeyedSerialTask(
                    lane,
                    sourceStationId,
                    "dispatchNextCrnStationOutTask",
                    minIntervalMs,
                    () -> stationOutboundDispatchProcessor.dispatchNextCrnStationOutTask(sourceStationId)
            );
        }
    }
    private MainProcessLane resolveStationOutPendingConfirmLane(MainProcessLane lane) {
        if (lane == MainProcessLane.FAKE_STATION_OUT) {
            return MainProcessLane.FAKE_STATION_OUT_PENDING_CONFIRM;
        }
        return MainProcessLane.STATION_OUT_PENDING_CONFIRM;
    }
    public void submitDualCrnStationOutTasks(long minIntervalMs) {
        submitDualCrnStationOutTasks(MainProcessLane.DUAL_STATION_OUT, minIntervalMs);
    }
src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
@@ -31,6 +31,7 @@
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -39,7 +40,7 @@
@Component
public class StationOutboundDispatchProcessor {
    private static final int PENDING_DISPATCH_EXPIRE_SECONDS = 60 * 10;
    private static final int PENDING_DISPATCH_EXPIRE_SECONDS = 60 * 60 * 24;
    private static final long DEFAULT_PENDING_DISCOVER_TIMEOUT_MS = 60_000L;
    private static final long DEFAULT_PENDING_SESSION_PROTECT_MS = 90_000L;
    private static final long DEFAULT_RECENT_DISPATCH_PROTECT_MS = 60_000L;
@@ -77,172 +78,140 @@
            if (wrkMast == null || wrkMast.getWrkNo() == null) {
                return;
            }
            Object pendingObj = redisUtil.get(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkMast.getWrkNo());
            if (pendingObj != null) {
                if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.OUTBOUND_RUN_COMPLETE.sts)) {
                    clearPendingDispatch(wrkMast.getWrkNo());
                    return;
                }
                StationObjModel pendingStationObjModel = getOutboundSourceStation(wrkMast);
                if (pendingStationObjModel == null
                        || pendingStationObjModel.getDeviceNo() == null
                        || pendingStationObjModel.getStationId() == null) {
                    clearPendingDispatch(wrkMast.getWrkNo());
                    return;
                }
                StationThread pendingStationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, pendingStationObjModel.getDeviceNo());
                if (pendingStationThread != null) {
                    List<Integer> taskNoList = pendingStationThread.getAllTaskNoList();
                    if (taskNoList != null && taskNoList.contains(wrkMast.getWrkNo())) {
                        Date now = new Date();
                        wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
                        wrkMast.setSystemMsg("");
                        wrkMast.setIoTime(now);
                        wrkMast.setModiTime(now);
                        if (wrkMastService.updateById(wrkMast)) {
                            wrkAnalysisService.markOutboundStationStart(wrkMast, now);
                            notifyUtils.notify(String.valueOf(SlaveType.Devp), pendingStationObjModel.getDeviceNo(),
                                    String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(),
                                    NotifyMsgType.STATION_OUT_TASK_RUN, null);
                            clearPendingDispatch(wrkMast.getWrkNo());
                            News.info("输送设备已发现任务号,任务转运行中。deviceNo={},源站={},工作号={}",
                                    pendingStationObjModel.getDeviceNo(), pendingStationObjModel.getStationId(), wrkMast.getWrkNo());
                        }
                        return;
                    }
                }
                long createdAt;
                try {
                    createdAt = Long.parseLong(String.valueOf(pendingObj));
                } catch (Exception ignore) {
                    createdAt = System.currentTimeMillis();
                }
                long pendingDiscoverTimeoutMs = resolvePendingDiscoverTimeoutMs();
                if (System.currentTimeMillis() - createdAt < pendingDiscoverTimeoutMs) {
                    return;
                }
                PendingDispatchGuardResult guardResult = evaluatePendingDispatchGuard(wrkMast, pendingStationObjModel, pendingStationThread);
                if (guardResult.keepPending()) {
                    News.info("输送站点等待源站执行超时后继续保持等待。工作号={},源站={},原因={}",
                            wrkMast.getWrkNo(),
                            pendingStationObjModel.getStationId(),
                            guardResult.reason());
                    markPendingDispatch(wrkMast.getWrkNo());
                    return;
                }
                clearPendingDispatch(wrkMast.getWrkNo());
                News.warn("输送站点执行确认超时,且未发现有效活动链路,已释放重试资格。工作号={}", wrkMast.getWrkNo());
            }
            StationObjModel stationObjModel = getOutboundSourceStation(wrkMast);
            if (stationObjModel == null || stationObjModel.getDeviceNo() == null || stationObjModel.getStationId() == null) {
            if (shouldStopAfterPendingDispatchCheck(wrkMast)) {
                return;
            }
            long sameStationCount = wrkMastService.count(new QueryWrapper<WrkMast>()
                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
                    .eq("source_sta_no", stationObjModel.getStationId()));
            if (sameStationCount > 1) {
                News.taskError(wrkMast.getWrkNo(), "出库异常:同一源站存在多笔搬运完成任务,不下发命令。源站={},数量={}",
                        stationObjModel.getStationId(), sameStationCount);
                return;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
            if (stationThread == null) {
                return;
            }
            Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = stationMap == null ? null : stationMap.get(stationObjModel.getStationId());
            if (stationProtocol == null) {
                return;
            }
            Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
            if (lock != null) {
                return;
            }
            if (!(stationProtocol.isAutoing()
                    && stationProtocol.isLoading()
                    && stationProtocol.getTaskNo() == 0)) {
                return;
            }
            Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
            List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList();
            OutOrderDispatchDecision dispatchDecision =
                    stationOutboundDecisionSupport.resolveOutboundDispatchDecision(
                            stationProtocol.getStationId(),
                            wrkMast,
                            outOrderList,
                            pathLenFactor
                    );
            Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
            if (moveStaNo == null) {
                return;
            }
            DispatchLimitConfig limitConfig =
                    stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
            int currentStationTaskCount = stationDispatchLoadSupport.countCurrentStationTask();
            LoadGuardState loadGuardState = stationDispatchLoadSupport.buildLoadGuardState(limitConfig);
            LoopHitResult loopHitResult =
                    stationDispatchLoadSupport.findPathLoopHit(
                            limitConfig,
                            stationProtocol.getStationId(),
                            moveStaNo,
                            loadGuardState,
                            wrkMast,
                            pathLenFactor
                    );
            if (stationDispatchLoadSupport.isDispatchBlocked(
                    limitConfig,
                    currentStationTaskCount,
                    loadGuardState,
                    loopHitResult.isThroughLoop())) {
                return;
            }
            StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
                    stationThread,
                    wrkMast,
                    stationProtocol.getStationId(),
                    moveStaNo,
                    pathLenFactor
            );
            if (command == null) {
                News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
                return;
            }
            boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
            if (!offered) {
                return;
            }
            if (stationMoveCoordinator != null) {
                stationMoveCoordinator.recordDispatch(
                        wrkMast.getWrkNo(),
                        stationProtocol.getStationId(),
                        "crnStationOutExecute",
                        command,
                        false
                );
            }
            markPendingDispatch(wrkMast.getWrkNo());
            News.info("输送站点出库命令已入设备执行链路,等待源站执行。站点号={},工作号={},命令数据={}",
                    stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
            redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
            loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
            stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
            dispatchCrnStationOutTask(wrkMast);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void confirmPendingCrnStationOutDispatch(WrkMast wrkMast) {
        try {
            shouldStopAfterPendingDispatchCheck(wrkMast);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void dispatchNextCrnStationOutTask(Integer sourceStationId) {
        try {
            if (sourceStationId == null) {
                return;
            }
            List<WrkMast> sameSourceTaskList = loadCrnOutboundCompleteTasksBySourceStation(sourceStationId);
            if (sameSourceTaskList.isEmpty()) {
                return;
            }
            WrkMast dispatchCandidate = null;
            int pendingTaskCount = 0;
            int dispatchableTaskCount = 0;
            for (WrkMast sameSourceTask : sameSourceTaskList) {
                if (sameSourceTask == null || sameSourceTask.getWrkNo() == null) {
                    continue;
                }
                if (hasPendingDispatch(sameSourceTask.getWrkNo())) {
                    pendingTaskCount++;
                    continue;
                }
                dispatchableTaskCount++;
                if (dispatchCandidate == null) {
                    dispatchCandidate = sameSourceTask;
                }
            }
            if (dispatchCandidate == null) {
                return;
            }
            if (dispatchableTaskCount > 1) {
                News.taskError(dispatchCandidate.getWrkNo(),
                        "出库异常:同一源站存在多笔待下发搬运完成任务,不下发命令。源站={},待下发数量={},待确认数量={}",
                        sourceStationId, dispatchableTaskCount, pendingTaskCount);
                return;
            }
            if (pendingTaskCount > 0 || sameSourceTaskList.size() > 1) {
                News.info("源站出库候选任务已确定。源站={},候选工作号={},待确认数量={},待下发数量={}",
                        sourceStationId,
                        dispatchCandidate.getWrkNo(),
                        pendingTaskCount,
                        dispatchableTaskCount);
            }
            dispatchCrnStationOutTask(dispatchCandidate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private boolean shouldStopAfterPendingDispatchCheck(WrkMast wrkMast) {
        if (wrkMast == null || wrkMast.getWrkNo() == null) {
            return true;
        }
        Object pendingDispatchMarker = redisUtil.get(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkMast.getWrkNo());
        if (pendingDispatchMarker == null) {
            return false;
        }
        return processPendingDispatch(wrkMast, pendingDispatchMarker);
    }
    private boolean processPendingDispatch(WrkMast wrkMast, Object pendingDispatchMarker) {
        if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.OUTBOUND_RUN_COMPLETE.sts)) {
            clearPendingDispatch(wrkMast);
            return true;
        }
        StationObjModel sourceStationObjModel = getOutboundSourceStation(wrkMast);
        if (sourceStationObjModel == null
                || sourceStationObjModel.getDeviceNo() == null
                || sourceStationObjModel.getStationId() == null) {
            clearPendingDispatch(wrkMast);
            return true;
        }
        StationThread sourceStationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, sourceStationObjModel.getDeviceNo());
        if (sourceStationThread != null) {
            List<Integer> taskNoList = sourceStationThread.getAllTaskNoList();
            if (taskNoList != null && taskNoList.contains(wrkMast.getWrkNo())) {
                Date now = new Date();
                wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
                wrkMast.setSystemMsg("");
                wrkMast.setIoTime(now);
                wrkMast.setModiTime(now);
                if (wrkMastService.updateById(wrkMast)) {
                    wrkAnalysisService.markOutboundStationStart(wrkMast, now);
                    notifyUtils.notify(String.valueOf(SlaveType.Devp), sourceStationObjModel.getDeviceNo(),
                            String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(),
                            NotifyMsgType.STATION_OUT_TASK_RUN, null);
                    clearPendingDispatch(wrkMast);
                    News.info("输送设备已发现任务号,任务转运行中。deviceNo={},源站={},工作号={}",
                            sourceStationObjModel.getDeviceNo(), sourceStationObjModel.getStationId(), wrkMast.getWrkNo());
                }
                return true;
            }
        }
        long createdAt;
        try {
            createdAt = Long.parseLong(String.valueOf(pendingDispatchMarker));
        } catch (Exception ignore) {
            createdAt = System.currentTimeMillis();
        }
        long pendingDiscoverTimeoutMs = resolvePendingDiscoverTimeoutMs();
        if (System.currentTimeMillis() - createdAt < pendingDiscoverTimeoutMs) {
            return true;
        }
        PendingDispatchGuardResult guardResult = evaluatePendingDispatchGuard(wrkMast, sourceStationObjModel, sourceStationThread);
        if (guardResult.keepPending()) {
            News.info("输送站点等待源站执行超时后继续保持等待。工作号={},源站={},原因={}",
                    wrkMast.getWrkNo(),
                    sourceStationObjModel.getStationId(),
                    guardResult.reason());
            markPendingDispatch(wrkMast.getWrkNo());
            return true;
        }
        clearPendingDispatch(wrkMast);
        News.warn("输送站点执行确认超时,且未发现有效活动链路,已释放重试资格。工作号={}", wrkMast.getWrkNo());
        return false;
    }
    public void dualCrnStationOutExecute(WrkMast wrkMast) {
@@ -322,14 +291,131 @@
        if (wrkMast == null || wrkMast.getSourceStaNo() == null) {
            return null;
        }
        BasStation basStation = basStationService.getById(wrkMast.getSourceStaNo());
        return getOutboundSourceStation(wrkMast.getSourceStaNo());
    }
    private StationObjModel getOutboundSourceStation(Integer sourceStationId) {
        if (sourceStationId == null) {
            return null;
        }
        BasStation basStation = basStationService.getById(sourceStationId);
        if (basStation == null || basStation.getDeviceNo() == null) {
            return null;
        }
        StationObjModel stationObjModel = new StationObjModel();
        stationObjModel.setStationId(wrkMast.getSourceStaNo());
        stationObjModel.setStationId(sourceStationId);
        stationObjModel.setDeviceNo(basStation.getDeviceNo());
        return stationObjModel;
    }
    private void dispatchCrnStationOutTask(WrkMast wrkMast) {
        if (wrkMast == null || wrkMast.getWrkNo() == null) {
            return;
        }
        StationObjModel stationObjModel = getOutboundSourceStation(wrkMast);
        if (stationObjModel == null || stationObjModel.getDeviceNo() == null || stationObjModel.getStationId() == null) {
            return;
        }
        StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
        if (stationThread == null) {
            return;
        }
        Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
        StationProtocol stationProtocol = stationMap == null ? null : stationMap.get(stationObjModel.getStationId());
        if (stationProtocol == null) {
            return;
        }
        Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
        if (lock != null) {
            return;
        }
        if (!(stationProtocol.isAutoing()
                && stationProtocol.isLoading()
                && stationProtocol.getTaskNo() == 0)) {
            return;
        }
        Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
        List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList();
        OutOrderDispatchDecision dispatchDecision =
                stationOutboundDecisionSupport.resolveOutboundDispatchDecision(
                        stationProtocol.getStationId(),
                        wrkMast,
                        outOrderList,
                        pathLenFactor
                );
        Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
        if (moveStaNo == null) {
            return;
        }
        DispatchLimitConfig limitConfig =
                stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
        int currentStationTaskCount = stationDispatchLoadSupport.countCurrentStationTask();
        LoadGuardState loadGuardState = stationDispatchLoadSupport.buildLoadGuardState(limitConfig);
        LoopHitResult loopHitResult =
                stationDispatchLoadSupport.findPathLoopHit(
                        limitConfig,
                        stationProtocol.getStationId(),
                        moveStaNo,
                        loadGuardState,
                        wrkMast,
                        pathLenFactor
                );
        if (stationDispatchLoadSupport.isDispatchBlocked(
                limitConfig,
                currentStationTaskCount,
                loadGuardState,
                loopHitResult.isThroughLoop())) {
            return;
        }
        StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
                stationThread,
                wrkMast,
                stationProtocol.getStationId(),
                moveStaNo,
                pathLenFactor
        );
        if (command == null) {
            News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
            return;
        }
        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
        if (!offered) {
            return;
        }
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    "crnStationOutExecute",
                    command,
                    false
            );
        }
        markPendingDispatch(wrkMast.getWrkNo());
        News.info("输送站点出库命令已入设备执行链路,等待源站执行。站点号={},工作号={},命令数据={}",
                stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
        stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
    }
    private List<WrkMast> loadCrnOutboundCompleteTasksBySourceStation(Integer sourceStationId) {
        if (sourceStationId == null) {
            return new ArrayList<>();
        }
        return wrkMastService.list(new QueryWrapper<WrkMast>()
                .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
                .eq("source_sta_no", sourceStationId)
                .isNotNull("crn_no")
                .orderByAsc("io_time", "wrk_no"));
    }
    private boolean hasPendingDispatch(Integer wrkNo) {
@@ -350,6 +436,13 @@
        redisUtil.del(RedisKeyType.STATION_OUT_PENDING_DISPATCH_.key + wrkNo);
    }
    private void clearPendingDispatch(WrkMast wrkMast) {
        if (wrkMast == null || wrkMast.getWrkNo() == null) {
            return;
        }
        clearPendingDispatch(wrkMast.getWrkNo());
    }
    private PendingDispatchGuardResult evaluatePendingDispatchGuard(WrkMast wrkMast,
                                                                    StationObjModel stationObjModel,
                                                                    StationThread stationThread) {
src/main/resources/application.yml
@@ -1,6 +1,6 @@
# 系统版本信息
app:
  version: 3.0.1.1
  version: 3.0.1.2
  version-type: prd  # prd 或 dev
  i18n:
    default-locale: zh-CN