Junjie
3 天以前 3e793a6d2173889f4d006f2c8174f3eec4992745
src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java
@@ -2,18 +2,18 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.core.common.Cools;
import com.core.exception.CoolException;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.LocMast;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.LocMastService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.entity.FindCrnNoResult;
import com.zy.common.model.NavigateNode;
import com.zy.common.model.StartupDto;
import com.zy.common.service.CommonService;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.News;
import com.zy.core.cache.SlaveConnection;
@@ -29,7 +29,6 @@
import com.zy.core.model.protocol.StationProtocol;
import com.zy.core.model.protocol.StationTaskBufferItem;
import com.zy.core.move.StationMoveCoordinator;
import com.zy.core.move.StationMoveSession;
import com.zy.core.thread.StationThread;
import com.zy.core.utils.station.model.OutOrderDispatchDecision;
import com.zy.core.utils.station.model.RerouteCommandPlan;
@@ -38,24 +37,30 @@
import com.zy.core.utils.station.model.RerouteExecutionResult;
import com.zy.core.utils.station.model.RerouteSceneType;
import com.zy.core.utils.WmsOperateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Slf4j
@Component
public class StationRerouteProcessor {
    private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2;
    private static final int STATION_IDLE_RECOVER_SECONDS = 10;
    private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30;
    private static final long STATION_MOVE_RESET_WAIT_MS = 1000L;
    private static final int RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS = 8 * 60;
    private static final int RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_SECONDS = 60 * 60 * 24;
    private static final long CHECK_STATION_OUT_ORDER_SLOW_THRESHOLD_MS = 200L;
    private static final long EXECUTE_REROUTE_PLAN_SLOW_THRESHOLD_MS = 200L;
    private static final String RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX = "堵塞重分配请求WMS失败";
    private static final String RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING =
            RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX + ",请检查WMS重新分配库位接口";
    @Autowired
    private BasDevpService basDevpService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
@@ -74,30 +79,15 @@
    private StationOutboundDecisionSupport stationOutboundDecisionSupport;
    @Autowired
    private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport;
    public void checkStationRunBlock() {
        try {
            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
            for (BasDevp basDevp : basDevps) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
                if (stationThread == null) {
                    continue;
                }
                for (StationProtocol stationProtocol : stationThread.getStatus()) {
                    if (stationProtocol == null || stationProtocol.getStationId() == null) {
                        continue;
                    }
                    checkStationRunBlock(basDevp, stationProtocol.getStationId());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Autowired
    private NavigateUtils navigateUtils;
    public void checkStationRunBlock(BasDevp basDevp, Integer stationId) {
        try {
            if (basDevp == null || basDevp.getDevpNo() == null || stationId == null) {
                return;
            }
            if (shouldSkipRunBlockStation(basDevp, stationId)) {
                return;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
@@ -131,10 +121,18 @@
            if (lock != null) {
                return;
            }
            redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15);
            redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 30);
            if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) {
                executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
                if (stationMoveCoordinator != null) {
                    stationMoveCoordinator.withTaskDispatchLock(stationProtocol.getTaskNo(),
                            () -> {
                                executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
                                return null;
                            });
                } else {
                    executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
                }
                return;
            }
@@ -158,74 +156,20 @@
        }
    }
    public void checkStationIdleRecover() {
        try {
            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
            for (BasDevp basDevp : basDevps) {
                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
                if (stationThread == null) {
                    continue;
                }
                for (StationProtocol stationProtocol : stationThread.getStatus()) {
                    if (stationProtocol != null && stationProtocol.getStationId() != null) {
                        checkStationIdleRecover(basDevp, stationProtocol.getStationId());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void checkStationIdleRecover(BasDevp basDevp, Integer stationId) {
        try {
            if (basDevp == null || basDevp.getDevpNo() == null || stationId == null) {
                return;
            }
            if (!isIdleRecoverCandidateStation(basDevp, stationId)) {
                return;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                return;
            }
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap == null ? null : statusMap.get(stationId);
            if (stationProtocol == null
                    || !stationProtocol.isAutoing()
                    || !stationProtocol.isLoading()
                    || stationProtocol.getTaskNo() <= 0
                    || stationProtocol.isRunBlock()) {
                return;
            }
            checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void checkStationOutOrder() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
        for (BasDevp basDevp : basDevps) {
            List<StationObjModel> orderList = basDevp.getOutOrderList$();
            for (StationObjModel stationObjModel : orderList) {
                checkStationOutOrder(basDevp, stationObjModel);
            }
        }
    }
    public void checkStationOutOrder(BasDevp basDevp, StationObjModel stationObjModel) {
        long totalStartMs = System.currentTimeMillis();
        try {
            if (basDevp == null || basDevp.getDevpNo() == null || stationObjModel == null || stationObjModel.getStationId() == null) {
                return;
            }
            long runtimeStartMs = System.currentTimeMillis();
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                return;
            }
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap == null ? null : statusMap.get(stationObjModel.getStationId());
            long runtimeCostMs = System.currentTimeMillis() - runtimeStartMs;
            if (stationProtocol == null
                    || !stationProtocol.isAutoing()
                    || !stationProtocol.isLoading()
@@ -235,17 +179,25 @@
                return;
            }
            long loadWrkStartMs = System.currentTimeMillis();
            WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
            long loadWrkCostMs = System.currentTimeMillis() - loadWrkStartMs;
            if (wrkMast == null
                    || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)
                    || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
                return;
            }
            if (stationOutboundDecisionSupport.shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
            long skipCheckStartMs = System.currentTimeMillis();
            boolean skipForExistingRoute = stationOutboundDecisionSupport
                    .shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId());
            long skipCheckCostMs = System.currentTimeMillis() - skipCheckStartMs;
            if (skipForExistingRoute) {
                return;
            }
            long pathFactorStartMs = System.currentTimeMillis();
            Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
            long pathFactorCostMs = System.currentTimeMillis() - pathFactorStartMs;
            RerouteContext context = RerouteContext.create(
                    RerouteSceneType.OUT_ORDER,
                    basDevp,
@@ -259,68 +211,29 @@
                    .withSuppressDispatchGuard()
                    .withOutOrderDispatchLock()
                    .withResetSegmentCommandsBeforeDispatch();
            executeSharedReroute(context);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void watchCircleStation() {
        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
        for (BasDevp basDevp : basDevps) {
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                continue;
            }
            for (StationProtocol stationProtocol : stationThread.getStatus()) {
                if (stationProtocol == null || stationProtocol.getStationId() == null) {
                    continue;
                }
                watchCircleStation(basDevp, stationProtocol.getStationId());
            }
        }
    }
    public void watchCircleStation(BasDevp basDevp, Integer stationId) {
        try {
            if (basDevp == null || basDevp.getDevpNo() == null || stationId == null) {
                return;
            }
            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
            if (stationThread == null) {
                return;
            }
            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
            StationProtocol stationProtocol = statusMap == null ? null : statusMap.get(stationId);
            if (stationProtocol == null
                    || !stationProtocol.isAutoing()
                    || !stationProtocol.isLoading()
                    || stationProtocol.getTaskNo() <= 0
                    || !stationOutboundDecisionSupport.isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
                return;
            }
            WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
            if (wrkMast == null
                    || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)
                    || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
                return;
            }
            Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
            RerouteContext context = RerouteContext.create(
                    RerouteSceneType.WATCH_CIRCLE,
                    basDevp,
                    stationThread,
                    stationProtocol,
                    wrkMast,
                    basDevp.getOutOrderIntList(),
            long rerouteStartMs = System.currentTimeMillis();
            RerouteExecutionResult result = executeSharedReroute(context);
            long rerouteCostMs = System.currentTimeMillis() - rerouteStartMs;
            long totalCostMs = System.currentTimeMillis() - totalStartMs;
            log.info("checkStationOutOrder profile, taskNo={}, stationId={}, deviceNo={}, batch={}, batchSeq={}, finalTargetStationId={}, pathLenFactor={}, runtimeCostMs={}ms, loadWrkCostMs={}ms, skipCheckCostMs={}ms, pathFactorCostMs={}ms, rerouteCostMs={}ms, totalCostMs={}ms, result={}",
                    stationProtocol.getTaskNo(),
                    stationProtocol.getStationId(),
                    stationObjModel.getDeviceNo(),
                    wrkMast.getBatch(),
                    wrkMast.getBatchSeq(),
                    wrkMast.getStaNo(),
                    pathLenFactor,
                    "watchCircleStation"
            ).withSuppressDispatchGuard()
                    .withOutOrderDispatchLock()
                    .withResetSegmentCommandsBeforeDispatch();
            executeSharedReroute(context);
                    runtimeCostMs,
                    loadWrkCostMs,
                    skipCheckCostMs,
                    pathFactorCostMs,
                    rerouteCostMs,
                    totalCostMs,
                    result == null ? null : result.skipReason());
            if (totalCostMs > CHECK_STATION_OUT_ORDER_SLOW_THRESHOLD_MS) {
                log.warn("checkStationOutOrder slow, taskNo={}, stationId={}, totalCostMs={}ms, rerouteCostMs={}ms, loadWrkCostMs={}ms, pathFactorCostMs={}ms",
                        stationProtocol.getTaskNo(), stationProtocol.getStationId(), totalCostMs, rerouteCostMs, loadWrkCostMs, pathFactorCostMs);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
@@ -411,6 +324,7 @@
    }
    public RerouteDecision resolveSharedRerouteDecision(RerouteContext context) {
        long startMs = System.currentTimeMillis();
        if (context == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return RerouteDecision.skip("missing-runtime-dependency");
        }
@@ -422,9 +336,13 @@
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER
                && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) {
            Integer targetStationId = context.wrkMast().getStaNo();
            return targetStationId == null || Objects.equals(targetStationId, currentStationId)
            RerouteDecision decision = targetStationId == null || Objects.equals(targetStationId, currentStationId)
                    ? RerouteDecision.skip("same-station")
                    : RerouteDecision.proceed(targetStationId);
            log.info("resolveSharedRerouteDecision profile, sceneType={}, taskNo={}, currentStationId={}, targetStationId={}, decision={}, decisionCostMs={}ms",
                    context.sceneType(), context.wrkMast().getWrkNo(), currentStationId, targetStationId,
                    decision.skip() ? decision.skipReason() : "proceed", System.currentTimeMillis() - startMs);
            return decision;
        }
        OutOrderDispatchDecision dispatchDecision =
@@ -435,57 +353,163 @@
                        context.pathLenFactor()
                );
        Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
        if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) {
            return RerouteDecision.skip("same-station");
        }
        return RerouteDecision.proceed(targetStationId, dispatchDecision);
        RerouteDecision decision = targetStationId == null || Objects.equals(targetStationId, currentStationId)
                ? RerouteDecision.skip("same-station")
                : RerouteDecision.proceed(targetStationId, dispatchDecision);
        log.info("resolveSharedRerouteDecision profile, sceneType={}, taskNo={}, currentStationId={}, targetStationId={}, decision={}, circle={}, decisionCostMs={}ms",
                context.sceneType(), context.wrkMast().getWrkNo(), currentStationId, targetStationId,
                decision.skip() ? decision.skipReason() : "proceed",
                dispatchDecision != null && dispatchDecision.isCircle(),
                System.currentTimeMillis() - startMs);
        return decision;
    }
    public boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast,
                                                   Integer stationId,
                                                   List<Integer> runBlockReassignLocStationList) {
        return wrkMast != null
                && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)
                && stationId != null
                && runBlockReassignLocStationList != null
                && runBlockReassignLocStationList.contains(stationId);
    }
    public boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) {
        if (taskNo == null || taskNo <= 0 || stationId == null) {
        if (wrkMast == null || stationId == null) {
            return false;
        }
        long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L;
        StationMoveSession session = stationMoveCoordinator == null ? null : stationMoveCoordinator.loadSession(taskNo);
        if (session != null && session.isActive() && session.getLastIssuedAt() != null) {
            if (Objects.equals(stationId, session.getCurrentStationId())
                    || Objects.equals(stationId, session.getDispatchStationId())
                    || session.containsStation(stationId)) {
                long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt();
                if (elapsedMs < thresholdMs) {
                    stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
                    News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距上次下发={}ms,routeVersion={}",
                            stationId, taskNo, elapsedMs, session.getRouteVersion());
                    return true;
                }
            }
        }
        if (!stationDispatchRuntimeStateSupport.hasRecentIssuedMoveCommand(taskNo, stationId, thresholdMs)) {
        if (!Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)) {
            return false;
        }
        stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
        News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距最近命令下发<{}ms,routeVersion={}",
                stationId, taskNo, thresholdMs, session == null ? null : session.getRouteVersion());
        if (runBlockReassignLocStationList == null || !runBlockReassignLocStationList.contains(stationId)) {
            return false;
        }
        if (shouldForceRunBlockPathReroute(wrkMast, stationId, runBlockReassignLocStationList)) {
            return false;
        }
        return true;
    }
    private boolean isIdleRecoverCandidateStation(BasDevp basDevp, Integer stationId) {
    private boolean shouldForceRunBlockPathReroute(WrkMast wrkMast,
                                                   Integer stationId,
                                                   List<Integer> runBlockReassignLocStationList) {
        if (wrkMast == null || stationId == null) {
            return false;
        }
        Integer nearestStationId = resolveNearestRunBlockDirectReassignStationId(wrkMast, runBlockReassignLocStationList);
        return nearestStationId != null && !Objects.equals(stationId, nearestStationId);
    }
    private Integer resolveNearestRunBlockDirectReassignStationId(WrkMast wrkMast,
                                                                  List<Integer> runBlockReassignLocStationList) {
        if (wrkMast == null
                || wrkMast.getStaNo() == null
                || navigateUtils == null
                || runBlockReassignLocStationList == null
                || runBlockReassignLocStationList.isEmpty()) {
            return null;
        }
        Integer targetStationId = wrkMast.getStaNo();
        Integer cachedStationId = loadCachedNearestRunBlockDirectReassignStationId(targetStationId, runBlockReassignLocStationList);
        if (cachedStationId != null) {
            return cachedStationId;
        }
        Integer nearestStationId = null;
        int nearestPathLen = Integer.MAX_VALUE;
        for (Integer candidateStationId : runBlockReassignLocStationList) {
            if (candidateStationId == null) {
                continue;
            }
            List<NavigateNode> path = navigateUtils.calcOptimalPathByStationId(candidateStationId, targetStationId, wrkMast.getWrkNo(), null);
            if (path == null || path.isEmpty()) {
                continue;
            }
            int pathLen = countStationNodes(path);
            if (pathLen <= 0) {
                continue;
            }
            if (pathLen < nearestPathLen
                    || (pathLen == nearestPathLen && nearestStationId != null && candidateStationId < nearestStationId)) {
                nearestStationId = candidateStationId;
                nearestPathLen = pathLen;
            }
        }
        cacheNearestRunBlockDirectReassignStationId(targetStationId, runBlockReassignLocStationList, nearestStationId);
        return nearestStationId;
    }
    private Integer loadCachedNearestRunBlockDirectReassignStationId(Integer targetStationId,
                                                                     List<Integer> runBlockReassignLocStationList) {
        String cacheKey = buildNearestRunBlockDirectReassignCacheKey(targetStationId, runBlockReassignLocStationList);
        if (cacheKey == null || redisUtil == null) {
            return null;
        }
        Object cacheValue = redisUtil.get(cacheKey);
        if (cacheValue == null) {
            return null;
        }
        try {
            Integer stationId = Integer.valueOf(String.valueOf(cacheValue));
            return runBlockReassignLocStationList.contains(stationId) ? stationId : null;
        } catch (Exception ignore) {
            return null;
        }
    }
    private void cacheNearestRunBlockDirectReassignStationId(Integer targetStationId,
                                                             List<Integer> runBlockReassignLocStationList,
                                                             Integer nearestStationId) {
        String cacheKey = buildNearestRunBlockDirectReassignCacheKey(targetStationId, runBlockReassignLocStationList);
        if (cacheKey == null || nearestStationId == null || redisUtil == null) {
            return;
        }
        redisUtil.set(cacheKey, nearestStationId, RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_SECONDS);
    }
    private String buildNearestRunBlockDirectReassignCacheKey(Integer targetStationId,
                                                              List<Integer> runBlockReassignLocStationList) {
        if (targetStationId == null || runBlockReassignLocStationList == null || runBlockReassignLocStationList.isEmpty()) {
            return null;
        }
        List<Integer> normalizedStationIdList = new ArrayList<>();
        for (Integer stationId : runBlockReassignLocStationList) {
            if (stationId != null && !normalizedStationIdList.contains(stationId)) {
                normalizedStationIdList.add(stationId);
            }
        }
        if (normalizedStationIdList.isEmpty()) {
            return null;
        }
        Collections.sort(normalizedStationIdList);
        return RedisKeyType.STATION_RUN_BLOCK_DIRECT_REASSIGN_NEAREST_CACHE_.key
                + targetStationId
                + "_"
                + JSON.toJSONString(normalizedStationIdList);
    }
    private int countStationNodes(List<NavigateNode> path) {
        if (path == null || path.isEmpty()) {
            return 0;
        }
        int count = 0;
        for (NavigateNode node : path) {
            if (extractStationId(node) != null) {
                count++;
            }
        }
        return count;
    }
    private Integer extractStationId(NavigateNode node) {
        if (node == null || Cools.isEmpty(node.getNodeValue())) {
            return null;
        }
        try {
            JSONObject valueObject = JSONObject.parseObject(node.getNodeValue());
            return valueObject == null ? null : valueObject.getInteger("stationId");
        } catch (Exception ignore) {
            return null;
        }
    }
    private boolean shouldSkipRunBlockStation(BasDevp basDevp, Integer stationId) {
        if (basDevp == null || stationId == null) {
            return false;
        }
        return !containsStation(basDevp.getBarcodeStationList$(), stationId)
                && !containsStation(basDevp.getInStationList$(), stationId)
                && !containsStation(basDevp.getOutStationList$(), stationId);
        return containsStation(basDevp.getBarcodeStationList$(), stationId)
                || containsStation(basDevp.getInStationList$(), stationId);
    }
    private boolean containsStation(List<StationObjModel> stationList, Integer stationId) {
@@ -505,19 +529,10 @@
                                                                  StationProtocol stationProtocol,
                                                                  Integer taskNo,
                                                                  Integer stationId) {
        long startMs = System.currentTimeMillis();
        boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE;
        if (context.checkRecentDispatch()
                && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) {
            return RerouteExecutionResult.skip("recent-dispatch");
        }
        int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo);
        if (currentTaskBufferCommandCount > 0 && !runBlockReroute) {
            if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
                News.info("输送站点任务停留超时,但缓存区仍存在当前任务命令,已跳过重算。站点号={},工作号={},当前任务命令数={}",
                        stationId,
                        taskNo,
                        currentTaskBufferCommandCount);
            }
            return RerouteExecutionResult.skip("buffer-has-current-task");
        }
        if (currentTaskBufferCommandCount > 0 && runBlockReroute) {
@@ -526,53 +541,65 @@
                    taskNo,
                    currentTaskBufferCommandCount);
        }
        if (!runBlockReroute
        long suppressStartMs = System.currentTimeMillis();
        boolean suppressDispatch = !runBlockReroute
                && context.checkSuppressDispatch()
                && stationMoveCoordinator != null
                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command());
        long suppressCostMs = System.currentTimeMillis() - suppressStartMs;
        if (suppressDispatch) {
            return RerouteExecutionResult.skip("dispatch-suppressed");
        }
        if (context.requireOutOrderDispatchLock()
                && !stationDispatchRuntimeStateSupport.tryAcquireOutOrderDispatchLock(taskNo, stationId, OUT_ORDER_DISPATCH_LIMIT_SECONDS)) {
        long outOrderLockStartMs = System.currentTimeMillis();
        boolean outOrderLockAcquired = !context.requireOutOrderDispatchLock()
                || stationDispatchRuntimeStateSupport.tryAcquireOutOrderDispatchLock(taskNo, stationId, OUT_ORDER_DISPATCH_LIMIT_SECONDS);
        long outOrderLockCostMs = System.currentTimeMillis() - outOrderLockStartMs;
        if (!outOrderLockAcquired) {
            return RerouteExecutionResult.skip("out-order-lock");
        }
        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending");
        long sceneLockStartMs = System.currentTimeMillis();
        boolean sceneLockAcquired = isBlank(context.executionLockKey())
                || stationDispatchRuntimeStateSupport.tryAcquireLock(context.executionLockKey(), context.executionLockSeconds());
        long sceneLockCostMs = System.currentTimeMillis() - sceneLockStartMs;
        if (!sceneLockAcquired) {
            return RerouteExecutionResult.skip("scene-lock");
        }
        if (runBlockReroute) {
            if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
                stationMoveCoordinator.cancelSession(taskNo);
            }
            if (context.resetSegmentCommandsBeforeDispatch()) {
                stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS);
            }
        }
        if (!runBlockReroute
                && context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            stationMoveCoordinator.cancelSession(taskNo);
        }
        if (!isBlank(context.executionLockKey())
                && !stationDispatchRuntimeStateSupport.tryAcquireLock(context.executionLockKey(), context.executionLockSeconds())) {
                return RerouteExecutionResult.skip("scene-lock");
        }
        if (!runBlockReroute && context.resetSegmentCommandsBeforeDispatch()) {
        long resetCostMs = 0L;
        if (context.resetSegmentCommandsBeforeDispatch()) {
            long resetStartMs = System.currentTimeMillis();
            stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS);
            resetCostMs = System.currentTimeMillis() - resetStartMs;
        }
        int clearedCommandCount = 0;
        if (context.clearIdleIssuedCommands()) {
            clearedCommandCount = stationDispatchRuntimeStateSupport.clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId);
        }
        long cancelSessionCostMs = 0L;
        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            long cancelSessionStartMs = System.currentTimeMillis();
            stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending");
            stationMoveCoordinator.cancelSession(taskNo);
            cancelSessionCostMs = System.currentTimeMillis() - cancelSessionStartMs;
        }
        long registerDispatchStartMs = System.currentTimeMillis();
        preRegisterDispatchSession(context, plan);
        long registerDispatchCostMs = System.currentTimeMillis() - registerDispatchStartMs;
        long offerDispatchStartMs = System.currentTimeMillis();
        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
        long offerDispatchCostMs = System.currentTimeMillis() - offerDispatchStartMs;
        if (!offered) {
            return RerouteExecutionResult.skip("dispatch-dedup");
        }
        applyRerouteDispatchEffects(context, plan, clearedCommandCount);
        long totalCostMs = System.currentTimeMillis() - startMs;
        log.info("executeReroutePlanWithTaskLock profile, sceneType={}, taskNo={}, stationId={}, suppressCostMs={}ms, outOrderLockCostMs={}ms, sceneLockCostMs={}ms, resetCostMs={}ms, cancelSessionCostMs={}ms, registerDispatchCostMs={}ms, offerDispatchCostMs={}ms, totalCostMs={}ms",
                context.sceneType(), taskNo, stationId, suppressCostMs, outOrderLockCostMs, sceneLockCostMs, resetCostMs, cancelSessionCostMs, registerDispatchCostMs, offerDispatchCostMs, totalCostMs);
        if (totalCostMs > EXECUTE_REROUTE_PLAN_SLOW_THRESHOLD_MS) {
            log.warn("executeReroutePlanWithTaskLock slow, sceneType={}, taskNo={}, stationId={}, totalCostMs={}ms, resetCostMs={}ms, registerDispatchCostMs={}ms, offerDispatchCostMs={}ms",
                    context.sceneType(), taskNo, stationId, totalCostMs, resetCostMs, registerDispatchCostMs, offerDispatchCostMs);
        }
        return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount);
    }
@@ -583,6 +610,24 @@
        }
        RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision);
        return executeReroutePlan(context, plan);
    }
    private void preRegisterDispatchSession(RerouteContext context, RerouteCommandPlan plan) {
        if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return;
        }
        if (stationMoveCoordinator == null) {
            return;
        }
        OutOrderDispatchDecision dispatchDecision =
                plan.decision() == null ? null : plan.decision().dispatchDecision();
        stationMoveCoordinator.recordDispatch(
                context.wrkMast().getWrkNo(),
                context.stationProtocol().getStationId(),
                plan.dispatchScene(),
                plan.command(),
                dispatchDecision != null && dispatchDecision.isCircle()
        );
    }
    private void applyRerouteDispatchEffects(RerouteContext context,
@@ -603,26 +648,6 @@
                dispatchDecision,
                plan.command()
        );
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    plan.dispatchScene(),
                    plan.command(),
                    dispatchDecision != null && dispatchDecision.isCircle()
            );
        }
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
            stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis()));
            News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}",
                    STATION_IDLE_RECOVER_SECONDS,
                    stationProtocol.getStationId(),
                    plan.command().getTargetStaNo(),
                    wrkMast.getWrkNo(),
                    clearedCommandCount,
                    JSON.toJSONString(plan.command()));
            return;
        }
        if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
            News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}",
                    stationProtocol.getStationId(),
@@ -633,51 +658,6 @@
        if (context.sceneType() == RerouteSceneType.OUT_ORDER) {
            News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo());
        }
    }
    private void checkStationIdleRecover(BasDevp basDevp,
                                         StationThread stationThread,
                                         StationProtocol stationProtocol,
                                         List<Integer> outOrderList) {
        if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) {
            return;
        }
        if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) {
            return;
        }
        StationTaskIdleTrack idleTrack = stationDispatchRuntimeStateSupport.touchIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId());
        if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
            return;
        }
        if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) {
            return;
        }
        WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
        if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) {
            return;
        }
        Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo());
        if (lock != null) {
            return;
        }
        Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
        RerouteContext context = RerouteContext.create(
                RerouteSceneType.IDLE_RECOVER,
                basDevp,
                stationThread,
                stationProtocol,
                wrkMast,
                outOrderList,
                pathLenFactor,
                "checkStationIdleRecover"
        ).withCancelSessionBeforeDispatch()
                .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS)
                .withResetSegmentCommandsBeforeDispatch()
                .clearIdleIssuedCommands(idleTrack);
        executeSharedReroute(context);
    }
    private void executeRunBlockDirectReassign(BasDevp basDevp,
@@ -692,27 +672,46 @@
                stationProtocol.getTaskNo()
        );
        if (currentTaskBufferCommandCount > 0) {
            News.info("输送站点运行堵塞重分配已跳过,缓存区仍存在当前任务命令。站点号={},工作号={},当前任务命令数={}",
            News.info("输送站点运行堵塞重分配检测到旧分段命令残留,将先重置本地分段状态后继续重发。站点号={},工作号={},当前任务命令数={}",
                    stationProtocol.getStationId(),
                    stationProtocol.getTaskNo(),
                    currentTaskBufferCommandCount);
            return;
        }
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
        if (stationDispatchRuntimeStateSupport.hasRunBlockDirectReassignLimit(
                wrkMast.getWrkNo(),
                stationProtocol.getStationId())) {
            News.info("输送站点运行堵塞重分配已跳过,8分钟内不允许重复申请。站点号={},工作号={}",
                    stationProtocol.getStationId(),
                    wrkMast.getWrkNo());
            return;
        }
        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
        if (Cools.isEmpty(response)) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,接口未响应!!!response:{}", response);
            return;
        }
        JSONObject jsonObject = JSON.parseObject(response);
        if (!jsonObject.getInteger("code").equals(200)) {
            News.error("请求WMS接口失败!!!response:{}", response);
        JSONObject jsonObject;
        try {
            jsonObject = JSON.parseObject(response);
        } catch (Exception e) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口响应解析异常!!!response:{}", response, e);
            return;
        }
        if (jsonObject == null || !Integer.valueOf(200).equals(jsonObject.getInteger("code"))) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS接口失败!!!response:{}", response);
            return;
        }
        StartupDto dto = jsonObject.getObject("data", StartupDto.class);
        if (dto == null || Cools.isEmpty(dto.getLocNo())) {
            appendStationSystemWarning(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING);
            News.taskError(wrkMast.getWrkNo(), "请求WMS重新分配库位接口失败,WMS未返回目标库位!!!response:{}", response);
            return;
        }
        clearStationSystemWarningByPrefix(stationProtocol, RUN_BLOCK_DIRECT_REASSIGN_WMS_WARNING_PREFIX);
        String sourceLocNo = wrkMast.getLocNo();
        String locNo = dto.getLocNo();
@@ -777,11 +776,14 @@
        if (!wrkMastService.updateById(wrkMast)) {
            return;
        }
        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
        if (!offered) {
            return;
        }
        stationDispatchRuntimeStateSupport.recordRunBlockDirectReassignLimit(
                wrkMast.getWrkNo(),
                stationProtocol.getStationId(),
                RUN_BLOCK_DIRECT_REASSIGN_LIMIT_SECONDS);
        stationDispatchRuntimeStateSupport.signalSegmentReset(wrkMast.getWrkNo(), STATION_MOVE_RESET_WAIT_MS);
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.markCancelPending(wrkMast.getWrkNo(), "reroute_pending");
            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
@@ -790,17 +792,40 @@
                    false
            );
        }
        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
        if (!offered) {
            News.warn("输送站点堵塞直派命令入队被拒绝(可能重复),站点号={},工作号={}", stationProtocol.getStationId(), wrkMast.getWrkNo());
        }
    }
    private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) {
        if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) {
            return false;
    private void appendStationSystemWarning(StationProtocol stationProtocol, String warning) {
        if (stationProtocol == null || Cools.isEmpty(warning)) {
            return;
        }
        if (Objects.equals(currentStationId, wrkMast.getStaNo())) {
            return false;
        String currentWarning = stationProtocol.getSystemWarning();
        if (Cools.isEmpty(currentWarning)) {
            stationProtocol.setSystemWarning(warning);
            return;
        }
        return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)
                || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts);
        if (currentWarning.contains(warning)) {
            return;
        }
        stationProtocol.setSystemWarning(currentWarning + ";" + warning);
    }
    private void clearStationSystemWarningByPrefix(StationProtocol stationProtocol, String warningPrefix) {
        if (stationProtocol == null || Cools.isEmpty(warningPrefix) || Cools.isEmpty(stationProtocol.getSystemWarning())) {
            return;
        }
        String[] warningParts = stationProtocol.getSystemWarning().split(";");
        List<String> keepWarningList = new ArrayList<>();
        for (String warningPart : warningParts) {
            if (Cools.isEmpty(warningPart) || warningPart.startsWith(warningPrefix)) {
                continue;
            }
            keepWarningList.add(warningPart);
        }
        stationProtocol.setSystemWarning(String.join(";", keepWarningList));
    }
    private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) {