From f2e91a3304aca45a248cc4b66996a30532700949 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期四, 26 三月 2026 09:53:24 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/core/utils/StationOperateProcessUtils.java | 1400 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 1,195 insertions(+), 205 deletions(-)

diff --git a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
index a6f36f0..df52f38 100644
--- a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -19,6 +19,9 @@
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.NavigateUtils;
 import com.zy.common.utils.RedisUtil;
+import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.move.StationMoveDispatchMode;
+import com.zy.core.move.StationMoveSession;
 import com.zy.core.News;
 import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
@@ -39,6 +42,7 @@
 public class StationOperateProcessUtils {
     private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
     private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2;
+    private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10;
     private static final int STATION_IDLE_RECOVER_SECONDS = 10;
     private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30;
     private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60;
@@ -73,6 +77,8 @@
     private StationTaskLoopService stationTaskLoopService;
     @Autowired
     private WrkAnalysisService wrkAnalysisService;
+    @Autowired
+    private StationMoveCoordinator stationMoveCoordinator;
 
     //鎵ц杈撻�佺珯鐐瑰叆搴撲换鍔�
     public synchronized void stationInExecute() {
@@ -157,7 +163,17 @@
                         wrkMast.setModiTime(now);
                         if (wrkMastService.updateById(wrkMast)) {
                             wrkAnalysisService.markInboundStationStart(wrkMast, now);
-                            MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                            boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute");
+                            if (offered && stationMoveCoordinator != null) {
+                                // 鍒濆鍏ュ簱鍛戒护涔熺撼鍏� session 璺熻釜锛屽悗缁仠鐣欐仮澶�/缁曞湀/鍫靛閲嶇畻鎵嶈兘鍩轰簬鍚屼竴鏉¤矾绾跨姸鎬佸垽鏂��
+                                stationMoveCoordinator.recordDispatch(
+                                        wrkMast.getWrkNo(),
+                                        stationProtocol.getStationId(),
+                                        "stationInExecute",
+                                        command,
+                                        false
+                                );
+                            }
                             News.info("杈撻�佺珯鐐瑰叆搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command));
                             redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5);
                             loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
@@ -213,6 +229,8 @@
                         && stationProtocol.isLoading()
                         && stationProtocol.getTaskNo() == 0
                 ) {
+                    // 鍏堢畻褰撳墠浠诲姟鍦ㄦ壒娆″嚭搴撲腑鐨勮矾寰勫�惧悜绯绘暟锛屽啀甯︾潃杩欎釜绯绘暟鍘诲喅绛栫洰鏍囩珯锛�
+                    // 杩欐牱鍚屼竴鎵规涓嶅悓搴忓彿浠诲姟鍦ㄦ帓搴忕偣銆佺粫鍦堢偣鍜屽牭濉為噸绠楁椂浼氬緱鍒颁竴鑷寸殑鐩爣瑁佸喅銆�
                     Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
                     OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
                             stationProtocol.getStationId(),
@@ -251,7 +269,16 @@
                     wrkMast.setModiTime(now);
                     if (wrkMastService.updateById(wrkMast)) {
                         wrkAnalysisService.markOutboundStationStart(wrkMast, now);
-                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
+                        if (offered && stationMoveCoordinator != null) {
+                            stationMoveCoordinator.recordDispatch(
+                                    wrkMast.getWrkNo(),
+                                    stationProtocol.getStationId(),
+                                    "crnStationOutExecute",
+                                    command,
+                                    false
+                            );
+                        }
                         News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                         redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
                         redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
@@ -319,7 +346,17 @@
                     wrkMast.setSystemMsg("");
                     wrkMast.setIoTime(new Date());
                     if (wrkMastService.updateById(wrkMast)) {
-                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute");
+                        if (offered && stationMoveCoordinator != null) {
+                            // 鍙屽伐浣嶅爢鍨涙満杞叆杈撻�佺嚎鍚庡悓鏍疯鐧昏 session锛屽惁鍒欏悗缁噸绠楀彧鑳界湅鍒� PLC 鍛戒护锛岀湅涓嶅埌璺嚎璇箟銆�
+                            stationMoveCoordinator.recordDispatch(
+                                    wrkMast.getWrkNo(),
+                                    stationProtocol.getStationId(),
+                                    "dualCrnStationOutExecute",
+                                    command,
+                                    false
+                            );
+                        }
                         notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null);
                         News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                         redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
@@ -371,6 +408,9 @@
         if (wrkMast == null || wrkMast.getWrkNo() == null) {
             return;
         }
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.finishSession(wrkMast.getWrkNo());
+        }
         Date now = new Date();
         wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts);
         wrkMast.setIoTime(now);
@@ -418,6 +458,9 @@
                 }
 
                 if (complete) {
+                    if (stationMoveCoordinator != null) {
+                        stationMoveCoordinator.finishSession(wrkNo);
+                    }
                     wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts);
                     wrkMast.setIoTime(new Date());
                     wrkMastService.updateById(wrkMast);
@@ -463,123 +506,28 @@
                         }
                         redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15);
 
-                        if (wrkMast.getIoType() == WrkIoType.IN.id && runBlockReassignLocStationList.contains(stationProtocol.getStationId())) {
-                            //绔欑偣澶勪簬閲嶆柊鍒嗛厤搴撲綅鍖哄煙
-                            //杩愯鍫靛锛岄噸鏂扮敵璇蜂换鍔�
-                            String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
-                            if (Cools.isEmpty(response)) {
-                                News.taskError(wrkMast.getWrkNo(), "璇锋眰WMS閲嶆柊鍒嗛厤搴撲綅鎺ュ彛澶辫触锛屾帴鍙f湭鍝嶅簲锛侊紒锛乺esponse锛歿}", response);
-                                continue;
-                            }
-                            JSONObject jsonObject = JSON.parseObject(response);
-                            if (jsonObject.getInteger("code").equals(200)) {
-                                StartupDto dto = jsonObject.getObject("data", StartupDto.class);
-
-                                String sourceLocNo = wrkMast.getLocNo();
-                                String locNo = dto.getLocNo();
-
-                                LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo);
-                                if (sourceLocMast == null) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶄俊鎭笉瀛樺湪", sourceLocNo);
-                                    continue;
-                                }
-
-                                if (!sourceLocMast.getLocSts().equals("S")) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶇姸鎬佷笉澶勪簬鍏ュ簱棰勭害", sourceLocNo);
-                                    continue;
-                                }
-
-                                LocMast locMast = locMastService.queryByLoc(locNo);
-                                if (locMast == null) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅淇℃伅涓嶅瓨鍦�", locNo);
-                                    continue;
-                                }
-
-                                if (!locMast.getLocSts().equals("O")) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅鐘舵�佷笉澶勪簬绌哄簱浣�", locNo);
-                                    continue;
-                                }
-
-                                FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
-                                if (findCrnNoResult == null) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
-                                    continue;
-                                }
-                                Integer crnNo = findCrnNoResult.getCrnNo();
-
-                                Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId());
-                                if (targetStationId == null) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationProtocol.getStationId());
-                                    continue;
-                                }
-
-                                StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0);
-                                if (command == null) {
-                                    News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
-                                    continue;
-                                }
-
-                                //鏇存柊婧愬簱浣�
-                                sourceLocMast.setLocSts("O");
-                                sourceLocMast.setModiTime(new Date());
-                                locMastService.updateById(sourceLocMast);
-
-                                //鏇存柊鐩爣搴撲綅
-                                locMast.setLocSts("S");
-                                locMast.setModiTime(new Date());
-                                locMastService.updateById(locMast);
-
-                                //鏇存柊宸ヤ綔妗f暟鎹�
-                                wrkMast.setLocNo(locNo);
-                                wrkMast.setStaNo(targetStationId);
-
-                                if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
-                                    wrkMast.setCrnNo(crnNo);
-                                } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
-                                    wrkMast.setDualCrnNo(crnNo);
-                                } else {
-                                    throw new CoolException("鏈煡璁惧绫诲瀷");
-                                }
-
-                                if (wrkMastService.updateById(wrkMast)) {
-                                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
-                                }
-                            } else {
-                                News.error("璇锋眰WMS鎺ュ彛澶辫触锛侊紒锛乺esponse锛歿}", response);
-                            }
-                        } else {
-                            //杩愯鍫靛锛岄噸鏂拌绠楄矾绾�
-                            Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                            OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
-                                    stationProtocol.getStationId(),
-                                    wrkMast,
-                                    outOrderStationIds,
-                                    pathLenFactor
-                            );
-                            Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-                            if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
-                                continue;
-                            }
-
-                            StationCommand command = stationThread.getRunBlockRerouteCommand(
-                                    wrkMast.getWrkNo(),
-                                    stationProtocol.getStationId(),
-                                    moveStaNo,
-                                    0,
-                                    pathLenFactor
-                            );
-                            if (command == null) {
-                                News.taskInfo(wrkMast.getWrkNo(),
-                                        "杈撻�佺珯鐐瑰牭濉為噸瑙勫垝鏈壘鍒板彲涓嬪彂璺嚎锛屽綋鍓嶇珯鐐�={}锛岀洰鏍囩珯鐐�={}",
-                                        stationProtocol.getStationId(),
-                                        moveStaNo);
-                                continue;
-                            }
-
-                            MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
-                            syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderStationIds, dispatchDecision, command);
-                            News.info("杈撻�佺珯鐐瑰牭濉炲悗閲嶆柊璁$畻璺緞鍛戒护涓嬪彂鎴愬姛锛岀珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
+                        if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) {
+                            executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
+                            continue;
                         }
+
+                        Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
+                        // 杩愯鍫靛涓嶅崟鐙喅瀹氫笟鍔$洰鏍囩珯锛屼粛鐒跺鐢ㄥ嚭搴撴帓搴�/缁曞湀鐨勭洰鏍囪鍐筹紝
+                        // 杩欓噷鍙槸瑕佹眰鐢� run-block 涓撶敤绠楄矾锛屽苟鍦ㄩ噸鍙戝墠娓呮帀鏃� session/segment 鐘舵�併��
+                        RerouteContext context = RerouteContext.create(
+                                RerouteSceneType.RUN_BLOCK_REROUTE,
+                                basDevp,
+                                stationThread,
+                                stationProtocol,
+                                wrkMast,
+                                outOrderStationIds,
+                                pathLenFactor,
+                                "checkStationRunBlock_reroute"
+                        ).withRunBlockCommand()
+                                .withSuppressDispatchGuard()
+                                .withCancelSessionBeforeDispatch()
+                                .withResetSegmentCommandsBeforeDispatch();
+                        executeSharedReroute(context);
                     }
                 }
             }
@@ -661,6 +609,11 @@
                     continue;
                 }
 
+                // 鎺掑簭鐐规湰韬凡缁忓牭濉炴椂锛屼笉鍦� out-order 閲屽仛浜屾鍐崇瓥锛岀粺涓�浜ょ粰 run-block 閲嶈鍒掑鐞嗐��
+                if (stationProtocol.isRunBlock()) {
+                    continue;
+                }
+
                 if (!stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) {
                     continue;
                 }
@@ -675,40 +628,26 @@
                 if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
                     continue;
                 }
-
-                if (isWatchingCircleArrival(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
+                // 鍙湁娲诲姩涓殑鐜版湁璺嚎鎵嶄細鍘嬪埗 out-order锛汢LOCKED 璺嚎瑕佸厑璁告帓搴忕偣閲嶆柊鍚姩銆�
+                if (shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
                     continue;
                 }
 
                 Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
-                        stationProtocol.getStationId(),
+                RerouteContext context = RerouteContext.create(
+                        RerouteSceneType.OUT_ORDER,
+                        basDevp,
+                        stationThread,
+                        stationProtocol,
                         wrkMast,
                         outOrderStationIds,
-                        pathLenFactor
-                );
-                Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-                if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
-                    continue;
-                }
-
-                StationCommand command = buildOutboundMoveCommand(
-                        stationThread,
-                        wrkMast,
-                        stationProtocol.getStationId(),
-                        moveStaNo,
-                        pathLenFactor
-                );
-                if (command == null) {
-                    News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                    continue;
-                }
-                if (!tryAcquireOutOrderDispatchLock(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
-                    continue;
-                }
-                syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderStationIds, dispatchDecision, command);
-                MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
-                News.info(dispatchDecision.isCircle() ? "{}浠诲姟杩涜缁曞湀" : "{}浠诲姟鐩存帴鍘荤洰鏍囩偣", wrkMast.getWrkNo());
+                        pathLenFactor,
+                        "checkStationOutOrder"
+                ).withDispatchDeviceNo(stationObjModel.getDeviceNo())
+                        .withSuppressDispatchGuard()
+                        .withOutOrderDispatchLock()
+                        .withResetSegmentCommandsBeforeDispatch();
+                executeSharedReroute(context);
             }
         }
     }
@@ -737,11 +676,8 @@
                     continue;
                 }
 
-                StationCommand circleCommand = getWatchCircleCommand(stationProtocol.getTaskNo());
-                if (circleCommand == null) {
-                    continue;
-                }
-                if (!stationProtocol.getStationId().equals(circleCommand.getTargetStaNo())) {
+                // 缁曞湀瑙﹀彂浼樺厛璇� session 鐨勪笅涓�鍐崇瓥绔欙紝legacy WATCH_CIRCLE key 鍙仛鍏煎鍥為��銆�
+                if (!isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
                     continue;
                 }
 
@@ -755,35 +691,20 @@
                 if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
                     continue;
                 }
-
                 Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
-                        stationProtocol.getStationId(),
+                RerouteContext context = RerouteContext.create(
+                        RerouteSceneType.WATCH_CIRCLE,
+                        basDevp,
+                        stationThread,
+                        stationProtocol,
                         wrkMast,
                         outOrderList,
-                        pathLenFactor
-                );
-                Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-                if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
-                    continue;
-                }
-
-                StationCommand command = buildOutboundMoveCommand(
-                        stationThread,
-                        wrkMast,
-                        stationProtocol.getStationId(),
-                        moveStaNo,
-                        pathLenFactor
-                );
-                if (command == null) {
-                    News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                    continue;
-                }
-                if (!tryAcquireOutOrderDispatchLock(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
-                    continue;
-                }
-                syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderList, dispatchDecision, command);
-                MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                        pathLenFactor,
+                        "watchCircleStation"
+                ).withSuppressDispatchGuard()
+                        .withOutOrderDispatchLock()
+                        .withResetSegmentCommandsBeforeDispatch();
+                executeSharedReroute(context);
             }
         }
     }
@@ -806,6 +727,238 @@
         );
     }
 
+    RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context,
+                                               RerouteDecision decision) {
+        if (context == null) {
+            return RerouteCommandPlan.skip("missing-context");
+        }
+        if (decision == null) {
+            return RerouteCommandPlan.skip("missing-decision");
+        }
+        if (decision.skip()) {
+            return RerouteCommandPlan.skip(decision.skipReason());
+        }
+        if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) {
+            return RerouteCommandPlan.skip("missing-runtime-dependency");
+        }
+        Integer currentStationId = context.stationProtocol().getStationId();
+        Integer targetStationId = decision.targetStationId();
+        if (currentStationId == null || targetStationId == null) {
+            return RerouteCommandPlan.skip("missing-target-station");
+        }
+        if (Objects.equals(currentStationId, targetStationId)) {
+            return RerouteCommandPlan.skip("same-station");
+        }
+
+        StationCommand command = context.useRunBlockCommand()
+                ? context.stationThread().getRunBlockRerouteCommand(
+                context.wrkMast().getWrkNo(),
+                currentStationId,
+                targetStationId,
+                0,
+                context.pathLenFactor()
+        )
+                : buildOutboundMoveCommand(
+                context.stationThread(),
+                context.wrkMast(),
+                currentStationId,
+                targetStationId,
+                context.pathLenFactor()
+        );
+        if (command == null) {
+            if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
+                News.taskInfo(context.wrkMast().getWrkNo(),
+                        "杈撻�佺珯鐐瑰牭濉為噸瑙勫垝鏈壘鍒板彲涓嬪彂璺嚎锛屽綋鍓嶇珯鐐�={}锛岀洰鏍囩珯鐐�={}",
+                        currentStationId,
+                        targetStationId);
+            } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
+                News.taskInfo(context.wrkMast().getWrkNo(),
+                        "绔欑偣浠诲姟鍋滅暀瓒呮椂鍚庨噸绠楄矾寰勫け璐ワ紝褰撳墠绔欑偣={}锛岀洰鏍囩珯鐐�={}",
+                        currentStationId,
+                        targetStationId);
+            } else {
+                News.taskInfo(context.wrkMast().getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+            }
+            return RerouteCommandPlan.skip("missing-command");
+        }
+        return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene());
+    }
+
+    RerouteExecutionResult executeReroutePlan(RerouteContext context,
+                                              RerouteCommandPlan plan) {
+        if (context == null) {
+            return RerouteExecutionResult.skip("missing-context");
+        }
+        if (plan == null) {
+            return RerouteExecutionResult.skip("missing-plan");
+        }
+        if (plan.skip()) {
+            return RerouteExecutionResult.skip(plan.skipReason());
+        }
+        StationProtocol stationProtocol = context.stationProtocol();
+        if (stationProtocol == null) {
+            return RerouteExecutionResult.skip("missing-station-protocol");
+        }
+        Integer taskNo = stationProtocol.getTaskNo();
+        Integer stationId = stationProtocol.getStationId();
+        if (taskNo == null || taskNo <= 0 || stationId == null) {
+            return RerouteExecutionResult.skip("invalid-station-task");
+        }
+        boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE;
+        if (runBlockReroute) {
+            // 绔欑偣杩涘叆鍫靛鍚庯紝璁惧渚у彲鑳藉凡缁忔妸涔嬪墠棰勪笅鍙戠殑鍒嗘鍛戒护娓呮帀浜嗐��
+            // 鍏堜綔搴熸湰鍦� session/segment 鐘舵�侊紝鍐嶆寜鏂拌矾绾块噸鍙戯紝閬垮厤琚棫鐘舵�佸弽鍚戝崱浣忋��
+            if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
+                stationMoveCoordinator.cancelSession(taskNo);
+            }
+            if (context.resetSegmentCommandsBeforeDispatch()) {
+                resetSegmentMoveCommandsBeforeReroute(taskNo);
+            }
+        }
+        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) {
+            // 鍫靛閲嶈鍒掕鏇挎崲鐨勬鏄繖浜涙棫鍒嗘鍛戒护锛屼笉鑳藉啀鎶婃畫鐣� buffer 褰撴垚鏂扮殑鎷︽埅鏉′欢銆�
+            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸瑙勫垝妫�娴嬪埌鏃у垎娈靛懡浠ゆ畫鐣欙紝宸插厛娓呯悊鏈湴鐘舵�佸悗缁х画閲嶅彂銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
+                    stationId,
+                    taskNo,
+                    currentTaskBufferCommandCount);
+        }
+        if (!runBlockReroute
+                && context.checkSuppressDispatch()
+                && stationMoveCoordinator != null
+                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
+            return RerouteExecutionResult.skip("dispatch-suppressed");
+        }
+        // 杩涘叆鍫靛閲嶈鍒掑悗锛屾棫璺嚎宸茬粡琚樉寮忓彇娑堬紝鏈疆鍛戒护涓嶅啀鍙備笌 active-session suppress 鍒ゅ畾銆�
+        if (context.requireOutOrderDispatchLock()
+                && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) {
+            return RerouteExecutionResult.skip("out-order-lock");
+        }
+
+        if (!runBlockReroute
+                && context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
+            stationMoveCoordinator.cancelSession(taskNo);
+        }
+        if (!isBlank(context.executionLockKey())) {
+            Object lock = redisUtil.get(context.executionLockKey());
+            if (lock != null) {
+                return RerouteExecutionResult.skip("scene-lock");
+            }
+            redisUtil.set(context.executionLockKey(), "lock", context.executionLockSeconds());
+        }
+        if (!runBlockReroute && context.resetSegmentCommandsBeforeDispatch()) {
+            resetSegmentMoveCommandsBeforeReroute(taskNo);
+        }
+
+        int clearedCommandCount = 0;
+        if (context.clearIdleIssuedCommands()) {
+            clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId);
+        }
+
+        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
+        if (!offered) {
+            return RerouteExecutionResult.skip("dispatch-dedup");
+        }
+
+        applyRerouteDispatchEffects(context, plan, clearedCommandCount);
+        return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount);
+    }
+
+    RerouteDecision resolveSharedRerouteDecision(RerouteContext context) {
+        if (context == null || context.wrkMast() == null || context.stationProtocol() == null) {
+            return RerouteDecision.skip("missing-runtime-dependency");
+        }
+        Integer currentStationId = context.stationProtocol().getStationId();
+        if (currentStationId == null) {
+            return RerouteDecision.skip("missing-current-station");
+        }
+
+        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.skip("same-station")
+                    : RerouteDecision.proceed(targetStationId);
+        }
+
+        OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
+                currentStationId,
+                context.wrkMast(),
+                context.outOrderStationIds(),
+                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);
+    }
+
+    private RerouteExecutionResult executeSharedReroute(RerouteContext context) {
+        RerouteDecision decision = resolveSharedRerouteDecision(context);
+        if (decision.skip()) {
+            return RerouteExecutionResult.skip(decision.skipReason());
+        }
+        RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision);
+        return executeReroutePlan(context, plan);
+    }
+
+    private void applyRerouteDispatchEffects(RerouteContext context,
+                                             RerouteCommandPlan plan,
+                                             int clearedCommandCount) {
+        if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) {
+            return;
+        }
+        WrkMast wrkMast = context.wrkMast();
+        StationProtocol stationProtocol = context.stationProtocol();
+        OutOrderDispatchDecision dispatchDecision = plan.decision() == null ? null : plan.decision().dispatchDecision();
+
+        syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), context.outOrderStationIds(), 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) {
+            saveStationTaskIdleTrack(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(),
+                    wrkMast.getWrkNo(),
+                    JSON.toJSONString(plan.command()));
+            return;
+        }
+        if (context.sceneType() == RerouteSceneType.OUT_ORDER) {
+            News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}浠诲姟杩涜缁曞湀" : "{}浠诲姟鐩存帴鍘荤洰鏍囩偣", wrkMast.getWrkNo());
+        }
+    }
+
     private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
                                                         Integer sourceStationId,
                                                         Integer targetStationId,
@@ -818,6 +971,31 @@
         return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor);
     }
 
+    /**
+     * 璁$畻褰撳墠鍑哄簱浠诲姟鐨勮矾寰勫�惧悜绯绘暟銆�
+     *
+     * <p>杩欎釜绯绘暟涓嶆槸涓氬姟鐩爣绔欐湰韬紝鑰屾槸鈥滃湪澶氭潯鍙璺嚎涔嬮棿鏇村亸鍚戝摢涓�鏉♀�濈殑杈呭姪杈撳叆锛�
+     * 鐩殑鏄鍚屼竴鎵规銆佷笉鍚屽簭鍙风殑浠诲姟鍦ㄥ叡浜幆绾块噷灏介噺褰㈡垚绋冲畾銆佸彲閲嶅鐨勮矾寰勫垎甯冦��
+     *
+     * <p>杩斿洖鍊艰寖鍥村浐瀹氬湪 {@code [0, 1]}锛�
+     * 1. 闈炴壒娆″嚭搴撲换鍔★紝鐩存帴杩斿洖 {@code 0.0}锛岃〃绀轰笉寮曞叆棰濆璺緞鍋忕疆銆�
+     * 2. 褰撳墠鎵规鍙湁 1 涓湁鏁堟椿鍔ㄤ换鍔★紝杩斿洖 {@code 0.0}锛屽洜涓烘病鏈夆�滃墠鍚庨『搴忊�濆彲姣旇緝銆�
+     * 3. 鍚﹀垯鎸夆�滃綋鍓嶄换鍔″墠闈㈣繕鏈夊灏戜釜鏈夋晥鍓嶅簭浠诲姟鈥濆崰鈥滄湁鏁堟椿鍔ㄤ换鍔℃�绘暟鈥濈殑姣斾緥鏉ョ畻銆�
+     *
+     * <p>杩欓噷鐨勨�滄湁鏁堜换鍔♀�濆彧缁熻锛�
+     * 1. ioType=OUT 鐨勫嚭搴撲换鍔★紱
+     * 2. 浠嶅浜庢椿鍔ㄧ姸鎬侊紝鏈畬鎴�/鏈粨绠楋紱
+     * 3. 鏈� batchSeq锛�
+     * 4. mk != taskCancel銆�
+     *
+     * <p>缁撴灉鍚箟鍙互鐩磋鐞嗚В涓猴細
+     * 浠诲姟鎵规搴忓彿瓒婇潬鍚庯紝鍓嶉潰宸茬粡瀛樺湪鐨勬湁鏁堜换鍔¤秺澶氾紝寰楀埌鐨勭郴鏁拌秺澶э紱
+     * 鍚庣画绠楄矾鏃跺氨鏇村鏄撳拰鍓嶅簭浠诲姟褰㈡垚绋冲畾鐨勮矾寰勫垎娴侊紝鑰屼笉鏄墍鏈変换鍔¢兘璧板悓涓�鏉¢粯璁ょ煭璺��
+     *
+     * <p>娉ㄦ剰锛�
+     * 杩欎釜鏂规硶涓嶇洿鎺ュ喅瀹氱洰鏍囩珯锛屼笉璐熻矗鎺掑簭鏀捐锛屽彧鎻愪緵鈥滆矾寰勫亸濂解�濊緭鍏ャ��
+     * 鐪熸鐨勭洰鏍囩珯浠嶇敱 {@link #resolveOutboundDispatchDecision(Integer, WrkMast, List, Double)} 鍐冲畾銆�
+     */
     private Double resolveOutboundPathLenFactor(WrkMast wrkMast) {
         if (!isBatchOutboundTaskWithSeq(wrkMast)) {
             return 0.0d;
@@ -844,6 +1022,12 @@
         return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1));
     }
 
+    /**
+     * 鍒ゆ柇褰撳墠浠诲姟鏄惁鍏峰鈥滄寜鎵规鍑哄簱瑙勫垯鍙備笌鎺掑簭/璺緞鍋忓ソ璁$畻鈥濈殑鍩虹鏉′欢銆�
+     *
+     * <p>杩欓噷鍙仛鏈�鍩虹鐨勮祫鏍艰繃婊わ紝涓嶅叧蹇冨綋鍓嶆槸鍚︾湡鐨勯渶瑕佹帓搴忕偣浠嬪叆銆�
+     * 鍙涓嶆槸鎵规鍑哄簱浠诲姟锛屽悗闈㈢殑璺緞鍋忓ソ绯绘暟涓庢帓搴忕洰鏍囧喅绛栭兘搴旇鐩存帴閫�鍖栦负榛樿琛屼负銆�
+     */
     private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) {
         return wrkMast != null
                 && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id)
@@ -852,6 +1036,15 @@
                 && wrkMast.getWrkNo() != null;
     }
 
+    /**
+     * 鍔犺浇鍚屼竴鎵规涓嬩粛澶勪簬娲诲姩涓殑鍑哄簱浠诲姟銆�
+     *
+     * <p>杩欓噷鐢ㄤ簬涓ょ被璁$畻锛�
+     * 1. 璁$畻璺緞鍋忓ソ绯绘暟鏃讹紝缁熻褰撳墠浠诲姟鍓嶉潰杩樻湁澶氬皯涓湁鏁堝墠搴忎换鍔°��
+     * 2. 褰撳墠鎺掑簭鐐归噸鏂板喅绛栨椂锛屾壘鍑鸿繖涓�鎵光�滈涓湭瀹屾垚浠诲姟鈥濈殑瀹為檯鎵规搴忓彿銆�
+     *
+     * <p>宸茬粡瀹屾垚/缁撶畻鐨勪换鍔′笉鍐嶅弬涓庡綋鍓嶆壒娆$殑鎺掑簭涓庡亸濂借绠椼��
+     */
     private List<WrkMast> loadActiveBatchTaskList(String batch) {
         if (Cools.isEmpty(batch)) {
             return Collections.emptyList();
@@ -865,6 +1058,12 @@
                         WrkStsType.SETTLE_OUTBOUND.sts));
     }
 
+    /**
+     * 鍒ゆ柇鏌愭潯鎵规浠诲姟鏄惁搴旇璁″叆璺緞鍋忓ソ绯绘暟鐨勫垎姣�/鍒嗗瓙缁熻銆�
+     *
+     * <p>杩欓噷鎺掗櫎娌℃湁 batchSeq 鐨勪换鍔′互鍙婅鏄惧紡鏍囪涓� taskCancel 鐨勪换鍔★紝
+     * 閬垮厤鏃犳晥浠诲姟鎶婂悓鎵规鐨勮矾寰勫亸濂借绠楁媺鍋忋��
+     */
     private boolean isFactorCandidateTask(WrkMast wrkMast) {
         return wrkMast != null
                 && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id)
@@ -882,6 +1081,44 @@
         return list;
     }
 
+    /**
+     * 缁熶竴璁$畻褰撳墠浠诲姟鈥滄鍒诲簲璇ユ湞鍝釜鐩爣绔欑户缁繍琛屸�濄��
+     *
+     * <p>杩欐槸鍑哄簱鎺掑簭銆佺粫鍦堛�佸牭濉為噸瑙勫垝鍏辩敤鐨勭洰鏍囪鍐冲叆鍙c��
+     * 涓嶇瑙﹀彂鏉ユ簮鏄� OUT_ORDER銆乄ATCH_CIRCLE 杩樻槸 RUN_BLOCK_REROUTE锛�
+     * 鍙涓氬姟璇箟杩樻槸鈥滃綋鍓嶈繖绁ㄥ嚭搴撲换鍔′笅涓�姝ヨ寰�鍝噷璧扳�濓紝閮戒粠杩欓噷寰楀嚭鐩爣绔欍��
+     *
+     * <p>瀹冨仛涓夊眰鍒ゆ柇锛�
+     * 1. 濡傛灉褰撳墠浠诲姟鏍规湰涓嶉�傜敤鍑哄簱鎺掑簭锛岀洿鎺ヨ繑鍥炰换鍔′笟鍔$洰鏍囩珯 {@code wrkMast.staNo}銆�
+     * 2. 濡傛灉閫傜敤鍑哄簱鎺掑簭锛屽厛绠楀嚭鈥滃綋鍓嶆壒娆¤鍒欎笅锛屾鍒诲厑璁稿墠寰�鐨� dispatchStationId鈥濄��
+     * 3. 濡傛灉褰撳墠绔欑偣姝eソ灏辨槸鎺掑簭鍐崇瓥鐐癸紝鍐嶈繘涓�姝ュ垽鏂槸锛�
+     *    鐩存帴鍘荤洰鏍囩偣锛岃繕鏄厛杩涘叆缁曞湀鐩爣鐐癸紝鎴栬�呭洜涓轰弗鏍肩獥鍙i檺鍒惰�屾殏涓嶆斁琛屻��
+     *
+     * <p>杩斿洖鐨� {@link OutOrderDispatchDecision} 涓嶅彧鏄竴涓洰鏍囩珯锛�
+     * 杩樻惡甯︿簡杩欐鍐崇瓥鏄惁灞炰簬缁曞湀銆佹槸鍚︽潵鑷綋鍓嶆帓搴忕偣閲嶆柊瑁佸喅绛夎涔変俊鎭紝
+     * 渚涘悗缁棩蹇椼�乻ession 璁板綍鍜� watch-circle 鍒ゅ畾浣跨敤銆�
+     *
+     * <p>鍙傛暟鍚箟锛�
+     * 1. {@code currentStationId}锛氫换鍔″綋鍓嶆墍鍦ㄧ珯鐐广�傜敤浜庡垽鏂綋鍓嶆槸涓嶆槸鎺掑簭鍐崇瓥鐐广��
+     *    褰撳墠鏄笉鏄凡缁忓埌杈� watch-circle 鐨勪笅涓�鍐崇瓥绔欍��
+     * 2. {@code wrkMast}锛氬綋鍓嶄换鍔′富鐘舵�侊紝鑷冲皯瑕佹彁渚涗笟鍔$洰鏍囩珯銆佹潵婧愮珯銆佹壒娆°�佸簭鍙风瓑淇℃伅銆�
+     * 3. {@code outOrderStationIds}锛氬綋鍓嶈澶囬厤缃殑鎵�鏈夊嚭搴撴帓搴忕珯鐐瑰垪琛ㄣ��
+     * 4. {@code pathLenFactor}锛氱敱 {@link #resolveOutboundPathLenFactor(WrkMast)} 寰楀埌鐨勮矾寰勫亸濂界郴鏁帮紝
+     *    鐢ㄦ潵璁╁悓涓�鎵规浠诲姟鍦ㄩ�夋嫨 dispatch target 鏃朵繚鎸佺ǔ瀹氱殑璺緞鍊惧悜銆�
+     *
+     * <p>杩斿洖鍊艰涔夛細
+     * 1. 杩斿洖 {@code null}锛氬綋鍓嶆棤娉曞緱鍒板悎娉曠洰鏍囩珯锛岃皟鐢ㄦ柟搴旇烦杩囨湰娆℃淳鍙戙��
+     * 2. 杩斿洖 {@code targetStationId=wrkMast.staNo, circle=false}锛�
+     *    褰撳墠涓嶉渶瑕佸嚭搴撴帓搴忓共棰勶紝鎴栧凡鍏佽鐩存帴鍘讳笟鍔$洰鏍囩珯銆�
+     * 3. 杩斿洖 {@code targetStationId!=wrkMast.staNo}锛�
+     *    褰撳墠搴旇鍏堝幓涓�涓腑闂� dispatch 鐩爣绔欙紝鍚庣画鍐嶇敱鎺掑簭鐐�/缁曞湀鐐圭户缁喅绛栥��
+     * 4. 杩斿洖 {@code circle=true}锛�
+     *    褰撳墠灞炰簬缁曞湀鍐崇瓥缁撴灉锛屽悗缁� watch-circle 閫昏緫浼氭嵁姝ゆ帴绠°��
+     *
+     * <p>娉ㄦ剰锛�
+     * 杩欎釜鏂规硶鍙喅瀹氣�滅洰鏍囩珯鈥濓紝涓嶇洿鎺ョ敓鎴愯緭閫佸懡浠ゃ��
+     * 鐪熸鐨勮矾寰勭敱鏅�氱畻璺垨 run-block 涓撶敤绠楄矾鍦ㄥ悗缁楠ょ敓鎴愩��
+     */
     private OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId,
                                                                      WrkMast wrkMast,
                                                                      List<Integer> outOrderStationIds,
@@ -913,6 +1150,24 @@
         return new OutOrderDispatchDecision(dispatchStationId, false);
     }
 
+    /**
+     * 鍦ㄢ�滃綋鍓嶇珯鐐瑰氨鏄湰娆℃帓搴忓喅绛栫偣鈥濇椂锛岃绠楄繖閲屽埌搴曡鐩存帴鏀捐杩樻槸杩涘叆缁曞湀銆�
+     *
+     * <p>杩欐槸鍑哄簱鎺掑簭閲屾渶鏍稿績鐨勫眬閮ㄥ喅绛栨柟娉曘�傝繘鍏ヨ繖閲屼箣鍓嶏紝宸茬粡婊¤冻锛�
+     * 1. 褰撳墠浠诲姟閫傜敤 out-order锛�
+     * 2. 褰撳墠绔欑偣灏辨槸杩欑エ浠诲姟姝ゅ埢瀵瑰簲鐨� dispatch 鎺掑簭鐐广��
+     *
+     * <p>鍐呴儴鍐崇瓥椤哄簭锛�
+     * 1. 鍏堝彇鍑哄悓鎵规浠嶆湭瀹屾垚鐨勪换鍔★紝鎵惧嚭杩欐壒褰撳墠鈥滃簲褰撹浼樺厛鏀捐鈥濈殑搴忓彿浣嶇疆銆�
+     * 2. 鍐嶇粨鍚堝綋鍓嶄换鍔″湪鍒濆璺緞涓婄殑鎺掑簭绐楀彛浣嶇疆锛屽垽鏂嚜宸辨鍒昏兘鍚︾洿鎺ュ幓涓氬姟鐩爣绔欍��
+     * 3. 濡傛灉鐞嗚涓婅鑷繁鏀捐锛岃繕瑕侀澶栨鏌ョ洰鏍囨柟鍚戜笂鏄惁瀛樺湪鍙繘鍏ョ殑 release slot銆�
+     * 4. 濡傛灉涓嶈兘鐩磋揪锛屾垨鑰呯洿杈炬柟鍚戝綋鍓嶅叏閮ㄩ樆濉烇紝灏辫浆鎴� circle 鍐崇瓥锛屽鎵句笅涓�鎺掑簭妫�娴嬬偣銆�
+     *
+     * <p>杩斿洖鍊硷細
+     * 1. {@code circle=false} 琛ㄧず褰撳墠鎺掑簭鐐瑰凡缁忓厑璁哥洿鎺ュ幓涓氬姟鐩爣绔欍��
+     * 2. {@code circle=true} 琛ㄧず褰撳墠鍙兘鍏堝幓涓嬩竴缁曞湀鐩爣绔欙紝鍚庣画鐢� watch-circle/鎺掑簭鐐圭户缁帴鍔涘喅绛栥��
+     * 3. {@code null} 琛ㄧず褰撳墠鏃笉鑳界洿杈撅紝涔熸病鎵惧埌鍚堟硶鐨勪笅涓�缁曞湀鐐癸紝璋冪敤鏂瑰簲璺宠繃鏈娲惧彂銆�
+     */
     private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId,
                                                                             WrkMast wrkMast,
                                                                             List<Integer> outOrderStationIds,
@@ -998,6 +1253,12 @@
         return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true);
     }
 
+    /**
+     * 鍒ゆ柇杩欑エ浠诲姟鍦ㄥ綋鍓嶈澶囦笂鏄惁搴旇鍚敤鍑哄簱鎺掑簭閫昏緫銆�
+     *
+     * <p>鍙缂哄皯浠讳綍涓�涓帓搴忓墠鎻愶紝渚嬪涓嶆槸鍑哄簱浠诲姟銆佹病鏈夋壒娆°�佹病鏈夊簭鍙枫��
+     * 褰撳墠璁惧涔熸病鏈夐厤缃帓搴忕偣锛屽氨搴旇鐩存帴閫�鍥炩�滄櫘閫氱洰鏍囩珯鍐崇瓥鈥濄��
+     */
     private boolean shouldApplyOutOrder(WrkMast wrkMast, List<Integer> outOrderStationIds) {
         return wrkMast != null
                 && wrkMast.getStaNo() != null
@@ -1008,6 +1269,13 @@
                 && !outOrderStationIds.isEmpty();
     }
 
+    /**
+     * 鍒ゆ柇褰撳墠鎵�鍦ㄧ珯鐐规槸鍚﹀氨鏄�滆繖绁ㄤ换鍔℃鍒诲簲璇ヨЕ鍙戞帓搴忓喅绛栫殑 dispatch 绔欑偣鈥濄��
+     *
+     * <p>娉ㄦ剰瀹冧笉鏄畝鍗曞垽鏂�滃綋鍓嶇珯鐐规槸鍚﹀睘浜庢帓搴忕偣鍒楄〃鈥濓紝
+     * 鑰屾槸鍏堟牴鎹畬鏁磋矾寰勫弽鎺ㄥ嚭褰撳墠浠诲姟瀵瑰簲鐨� dispatch 鎺掑簭鐐癸紝
+     * 鍐嶅垽鏂綋鍓嶄綅缃槸鍚︽濂界瓑浜庤繖涓� dispatch 鐐广��
+     */
     private boolean isCurrentOutOrderDispatchStation(Integer currentStationId,
                                                      WrkMast wrkMast,
                                                      List<Integer> outOrderStationIds,
@@ -1027,6 +1295,12 @@
                 && Objects.equals(currentStationId, dispatchStationId);
     }
 
+    /**
+     * 鍒ゆ柇褰撳墠浣嶇疆鏄惁灞炰簬璁惧閰嶇疆閲岀殑浠绘剰涓�涓帓搴忕偣銆�
+     *
+     * <p>杩欎釜鍒ゆ柇姣� {@link #isCurrentOutOrderDispatchStation(Integer, WrkMast, List, Double)} 鏇村锛�
+     * 鍙洖绛斺�滃綋鍓嶄綅缃槸涓嶆槸鎺掑簭鐐光�濓紝涓嶅洖绛斺�滄槸涓嶆槸杩欑エ浠诲姟褰撳墠搴旇鍛戒腑鐨勬帓搴忕偣鈥濄��
+     */
     private boolean isCurrentOutOrderStation(Integer currentStationId,
                                              List<Integer> outOrderStationIds) {
         return currentStationId != null
@@ -1054,6 +1328,12 @@
         }
     }
 
+    /**
+     * 涓哄綋鍓嶆帓搴忕偣鍐崇瓥棰勫厛鍋氫竴娆$幆绾胯瘎浼般��
+     *
+     * <p>褰撴湰娆″喅绛栨渶缁堣繘鍏ョ粫鍦堟椂锛岃瘎浼扮粨鏋滀細琚甫杩� {@link OutOrderDispatchDecision}锛�
+     * 鍚庣画鐢ㄤ簬璁板綍 loop issue 缁熻锛岃�屼笉鏄湪鐪熸涓嬪彂鍚庡啀閲嶅璇勪及涓�娆°��
+     */
     private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo,
                                                                        Integer currentStationId,
                                                                        List<Integer> outOrderStationIds) {
@@ -1076,6 +1356,17 @@
         );
     }
 
+    /**
+     * 浠庘�滄簮绔欏埌涓氬姟鐩爣绔欌�濈殑瀹屾暣璺緞閲岋紝鍙嶆帹鍑哄綋鍓嶄换鍔″簲褰撳厛鍛戒腑鐨� dispatch 鎺掑簭鐐广��
+     *
+     * <p>鍋氭硶鏄細
+     * 1. 鍏堣绠椾粠 sourceStationId 鍒� finalTargetStationId 鐨勫畬鏁村鑸矾寰勶紱
+     * 2. 鍐嶄粠璺緞灏鹃儴鍚戝墠鎵弿锛�
+     * 3. 鎵惧埌绂绘渶缁堢洰鏍囨渶杩戠殑閭d釜鎺掑簭鐐癸紝浣滀负褰撳墠 dispatch 鐩爣銆�
+     *
+     * <p>濡傛灉璺緞涓婃牴鏈病鏈夋帓搴忕偣锛屾垨鑰呯己灏戞簮绔�/鎺掑簭鐐归厤缃紝
+     * 灏辩洿鎺ユ妸涓氬姟鐩爣绔欐湰韬綋鎴� dispatch 鐩爣銆�
+     */
     private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast,
                                                   Integer sourceStationId,
                                                   Integer finalTargetStationId,
@@ -1106,6 +1397,13 @@
         return finalTargetStationId;
     }
 
+    /**
+     * 鍒ゆ柇浠庡綋鍓嶆帓搴忕偣缁х画鍓嶅線鏈�缁堜笟鍔$洰鏍囩珯鏃讹紝璺緞涓婃槸鍚﹁嚦灏戝瓨鍦ㄤ竴涓彲杩涘叆鐨勫悗缁珯鐐广��
+     *
+     * <p>杩欓噷涓嶆槸瑕佹眰鏁存潯璺緞瀹屽叏鐣呴�氾紝鑰屾槸鍒ゆ柇鈥滃綋鍓嶆槸鍚︽湁閲婃斁鍙e彲璧扳�濄��
+     * 鍙鍚庣画璺緞涓婂瓨鍦ㄤ竴涓湭闃诲绔欑偣锛屽氨璁や负褰撳墠浠嶅彲灏濊瘯鐩磋揪锛�
+     * 鍙湁鏁存鍚庣画璺緞鐪嬭捣鏉ラ兘琚樆濉炴椂锛屾墠浼氬己鍒惰浆鍏ョ粫鍦堛��
+     */
     private boolean hasReachableOutReleaseSlot(WrkMast wrkMast,
                                                Integer currentStationId,
                                                Integer finalTargetStationId,
@@ -1161,6 +1459,28 @@
                 || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0);
     }
 
+    /**
+     * 涓衡�滄帓搴忕偣闇�瑕佺粫鍦堚�濈殑鍦烘櫙锛屼粠鍑哄簱鎺掑簭绔欑偣鍒楄〃閲屾寫鍑轰笅涓�璺崇粫鍦堢洰鏍囥��
+     * <p>
+     * 杩欓噷鐨勮緭鍏ヤ笉鏄暣寮犲湴鍥撅紝鑰屾槸宸茬粡鎸変笟鍔¢『搴忔帓濂界殑鍑哄簱绔欑偣搴忓垪銆傛柟娉曚細浠庡綋鍓嶇珯鐐瑰湪璇ュ簭鍒椾腑鐨勫悗缁у紑濮嬶紝
+     * 渚濇灏濊瘯姣忎釜鍊欓�夌珯鐐癸紝骞惰绠椻�滃綋鍓嶇珯鐐� -> 鍊欓�夌珯鐐光�濈殑鍙璺緞锛�
+     * <p>
+     * 1. 鍙鑳界畻鍑鸿矾寰勶紝灏辨妸鍊欓�夌珯鐐硅褰曚负涓�涓彲閫夌粫鍦堢洰鏍囷紱
+     * 2. 璁板綍涓ょ被鎺掑簭淇℃伅锛�
+     *    - pathLength锛氬埌璇ュ�欓�夌偣鐨勮矾寰勯暱搴︼紝瓒婄煭璇存槑瓒婇�傚悎鍏堟嬁鏉ュ仛涓存椂缂撳啿锛�
+     *    - offset锛氳绔欑偣鍦ㄦ帓搴忓簭鍒椾腑璺濈褰撳墠绔欑偣鏈夊杩滐紝鐢ㄦ潵鍦ㄨ矾寰勯暱搴︾浉鍚岀殑鏃跺�欎繚鐣欐棦鏈夐『搴忔劅锛�
+     * 3. 鏈�鍚庢妸鍊欓�夊垪琛ㄤ氦缁� {@link #resolveGradualCircleTargetByPathLength(Integer, List, Double)}锛�
+     *    鍐嶆牴鎹�滃綋鍓嶅凡缁忕粫鍦堝灏戞鈥濅笌鈥滆矾寰勯暱搴﹀亸濂界郴鏁扳�濅粠涓嶅悓闀垮害灞傜骇閲屾寫鏈�缁堢洰鏍囥��
+     * <p>
+     * 杩欎釜鏂规硶鏈韩涓嶅垽鏂�滄槸鍚﹀簲璇ョ粫鍦堚�濓紝鍙礋璐e湪宸茬粡鍐冲畾缁曞湀鍚庯紝浠庢帓搴忕珯鐐归摼璺噷鎵句竴涓笅涓�璺崇紦鍐茬偣銆�
+     *
+     * @param wrkMast 褰撳墠浠诲姟锛屼富瑕佺敤浜庣畻璺�
+     * @param currentStationId 褰撳墠绔欑偣锛屽嵆鏈鍑嗗浠庡摢閲屽彂鍑虹粫鍦堝懡浠�
+     * @param orderedOutStationList 宸叉寜涓氬姟椤哄簭鎺掑ソ鐨勫嚭搴撶珯鐐瑰垪琛�
+     * @param expectedLoopIssueCount 棰勮宸插彂鐢熺殑缁曞湀/鍫靛杞锛岀敤浜庡喅瀹氭槸鍚﹂�愭鏀惧ぇ缁曞湀鍗婂緞
+     * @param pathLenFactor 褰撳墠浠诲姟瀵瑰簲鐨勮矾寰勫亸濂界郴鏁帮紝褰卞搷 calcOutboundNavigatePath 鐨勯�夎矾缁撴灉
+     * @return 涓嬩竴璺崇粫鍦堢洰鏍囩珯锛涘鏋滄病鏈変换浣曞彲鍒拌揪鍊欓�夊垯杩斿洖 null
+     */
     private Integer resolveNextCircleOrderTarget(WrkMast wrkMast,
                                                  Integer currentStationId,
                                                  List<Integer> orderedOutStationList,
@@ -1211,6 +1531,30 @@
         return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor);
     }
 
+    /**
+     * 鍦ㄢ�滃凡鎸夎矾寰勯暱搴﹀崌搴忔帓濂解�濈殑缁曞湀鍊欓�夊垪琛ㄤ腑锛屾寜灞傜骇娓愯繘鍦版寫閫夌洰鏍囩珯銆�
+     * <p>
+     * candidateList 閲屽彲鑳藉瓨鍦ㄥ涓�欓�夌偣鎷ユ湁鐩稿悓鐨� pathLength銆傚缁曞湀鍐崇瓥鏉ヨ锛�
+     * 鍚屼竴闀垮害灞傞噷鐨勫�欓�夌偣閮藉睘浜庘�滃悓涓�缁曞湀鍗婂緞鈥濓紝鐪熸闇�瑕佹帶鍒剁殑鏄細
+     * <p>
+     * 1. 鍒濇/鍓嶅嚑娆″牭濉炴椂锛屼紭鍏堥�夋嫨鏈�鐭彲杈惧眰锛屽敖閲忕敤鏈�灏忕粫琛岃窛绂绘仮澶嶆祦杞紱
+     * 2. 濡傛灉浠诲姟宸茬粡杩炵画澶氭鍦ㄥ悓涓�鍖哄煙缁曞湀锛岃鏄庣煭鍗婂緞鍊欓�夊ぇ姒傜巼宸茬粡璇曡繃鎴栨仮澶嶆晥鏋滃樊锛�
+     *    灏遍渶瑕侀�愭鏀惧ぇ鍒版洿杩滀竴灞傦紱
+     * 3. pathLenFactor 浠h〃褰撳墠浠诲姟瀵光�滅煭璺緞/闀胯矾寰勨�濈殑鍋忓ソ锛屽厑璁稿湪鐩稿悓鍫靛杞涓嬮�傚害寰�鏇磋繙灞傚亸绉汇��
+     * <p>
+     * 鍥犳杩欓噷鍏堟妸 candidateList 鍘嬬缉鎴愨�滄寜 pathLength 鍘婚噸鍚庣殑 tierList鈥濓紝姣忎釜 tier 鍙繚鐣欒闀垮害灞傜殑棣栦釜鍊欓�夈��
+     * 鐒跺悗鍚屾椂璁$畻涓や釜灞傜骇绱㈠紩锛�
+     * <p>
+     * - defaultTierIndex锛氬熀浜� expectedLoopIssueCount 鐨勯粯璁ゆ斁澶у眰绾э紱
+     * - factorTierIndex锛氬熀浜� pathLenFactor 鐨勫亸濂藉眰绾э紱
+     * <p>
+     * 鏈�缁堝彇涓よ�呰緝澶у�硷紝鍚箟鏄�滆嚦灏戞弧瓒冲綋鍓嶅牭濉炶疆娆¢渶瑕佺殑鏀惧ぇ鍗婂緞锛屽悓鏃跺厑璁歌矾寰勫亸濂芥妸鐩爣鎺ㄥ悜鏇磋繙灞傜骇鈥濄��
+     *
+     * @param expectedLoopIssueCount 棰勮宸插彂鐢熺殑缁曞湀/鍫靛杞
+     * @param candidateList 宸叉寜 pathLength銆乷ffset 鎺掑簭鐨勫�欓�夊垪琛�
+     * @param pathLenFactor 褰撳墠浠诲姟鐨勮矾寰勫亸濂界郴鏁�
+     * @return 鏈�缁堥�変腑鐨勭粫鍦堢洰鏍囩珯锛涜嫢娌℃湁鍊欓�夊垯杩斿洖 null
+     */
     private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount,
                                                            List<CircleTargetCandidate> candidateList,
                                                            Double pathLenFactor) {
@@ -1253,9 +1597,61 @@
         return true;
     }
 
+    private boolean shouldSkipOutOrderDispatchForExistingRoute(Integer wrkNo, Integer stationId) {
+        if (stationMoveCoordinator == null || wrkNo == null || wrkNo <= 0 || stationId == null) {
+            return false;
+        }
+        StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo);
+        if (session == null) {
+            return false;
+        }
+        if (!session.isActive() || !session.containsStation(stationId)) {
+            return false;
+        }
+        // 缁曞湀璺嚎鍦ㄥ綋鍓嶇珯鐐瑰皻鏈蛋瀹屾椂锛屾帓搴忕偣涓嶅簲鍐嶆鎻掓墜銆�
+        if (StationMoveDispatchMode.CIRCLE == session.getDispatchMode()) {
+            return true;
+        }
+        // 鐩存帴璺嚎鍙湪鈥滃綋鍓嶇珯鐐瑰凡缁忚鍒殑娲诲姩璺嚎鍗犱綇涓旂洰鏍囦笉鍚屸�濇椂鎵嶆嫤鎴��
+        return !Objects.equals(stationId, session.getCurrentRouteTargetStationId());
+    }
+
     private boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) {
+        if (stationMoveCoordinator != null) {
+            StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo);
+            if (session != null && session.isActive() && stationId != null) {
+                // nextDecisionStationId 琛ㄧず杩欐潯璺嚎鐪熸绛夊緟閲嶆柊鍐崇瓥鐨勭珯鐐癸紝鍒扮珯鎵嶈Е鍙� watch-circle銆�
+                if (stationId.equals(session.getNextDecisionStationId())) {
+                    return true;
+                }
+                // 杩樺湪 session 璺緞涓棿绔欒繍琛屾椂涓嶅簲璇Е鍙戙��
+                if (session.containsStation(stationId)) {
+                    return false;
+                }
+            }
+        }
         StationCommand command = getWatchCircleCommand(wrkNo);
         return command != null && stationId != null && stationId.equals(command.getTargetStaNo());
+    }
+
+    private boolean isWatchingCircleTransit(Integer wrkNo, Integer stationId) {
+        if (stationMoveCoordinator != null) {
+            StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo);
+            if (session != null && session.isActive() && stationId != null) {
+                if (stationId.equals(session.getNextDecisionStationId())) {
+                    return false;
+                }
+                if (session.containsStation(stationId)) {
+                    return true;
+                }
+            }
+        }
+        StationCommand command = getWatchCircleCommand(wrkNo);
+        if (command == null || stationId == null || Objects.equals(stationId, command.getTargetStaNo())) {
+            return false;
+        }
+        List<Integer> navigatePath = command.getNavigatePath();
+        return navigatePath != null && navigatePath.contains(stationId);
     }
 
     private StationCommand getWatchCircleCommand(Integer wrkNo) {
@@ -1300,6 +1696,9 @@
         }
 
         StationTaskIdleTrack idleTrack = touchStationTaskIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId());
+        if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
+            return;
+        }
         if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) {
             return;
         }
@@ -1313,52 +1712,143 @@
         if (lock != null) {
             return;
         }
+        Double pathLenFactor = 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);
+    }
+
+    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);
+    }
+
+    private void executeRunBlockDirectReassign(BasDevp basDevp,
+                                               StationThread stationThread,
+                                               StationProtocol stationProtocol,
+                                               WrkMast wrkMast) {
+        if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) {
+            return;
+        }
         int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(
                 stationProtocol.getTaskBufferItems(),
                 stationProtocol.getTaskNo()
         );
         if (currentTaskBufferCommandCount > 0) {
-            News.info("杈撻�佺珯鐐逛换鍔″仠鐣欒秴鏃讹紝浣嗙紦瀛樺尯浠嶅瓨鍦ㄥ綋鍓嶄换鍔″懡浠わ紝宸茶烦杩囬噸绠椼�傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
+            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸鍒嗛厤宸茶烦杩囷紝缂撳瓨鍖轰粛瀛樺湪褰撳墠浠诲姟鍛戒护銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
                     stationProtocol.getStationId(),
                     stationProtocol.getTaskNo(),
                     currentTaskBufferCommandCount);
             return;
         }
-
-        Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-        OutOrderDispatchDecision dispatchDecision = null;
-        Integer moveStaNo;
-        if (Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) {
-            dispatchDecision = resolveOutboundDispatchDecision(stationProtocol.getStationId(), wrkMast, outOrderList, pathLenFactor);
-            moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-        } else {
-            moveStaNo = wrkMast.getStaNo();
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
         }
-        if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
+        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
+        if (Cools.isEmpty(response)) {
+            News.taskError(wrkMast.getWrkNo(), "璇锋眰WMS閲嶆柊鍒嗛厤搴撲綅鎺ュ彛澶辫触锛屾帴鍙f湭鍝嶅簲锛侊紒锛乺esponse锛歿}", response);
+            return;
+        }
+        JSONObject jsonObject = JSON.parseObject(response);
+        if (!jsonObject.getInteger("code").equals(200)) {
+            News.error("璇锋眰WMS鎺ュ彛澶辫触锛侊紒锛乺esponse锛歿}", response);
             return;
         }
 
-        redisUtil.set(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), "lock", STATION_IDLE_RECOVER_LIMIT_SECONDS);
-        resetSegmentMoveCommandsBeforeReroute(stationProtocol.getTaskNo());
-        int clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(idleTrack, stationProtocol.getTaskNo(), stationProtocol.getStationId());
+        StartupDto dto = jsonObject.getObject("data", StartupDto.class);
+        String sourceLocNo = wrkMast.getLocNo();
+        String locNo = dto.getLocNo();
 
-        StationCommand command = buildOutboundMoveCommand(
-                stationThread,
-                wrkMast,
-                stationProtocol.getStationId(),
-                moveStaNo,
-                pathLenFactor
-        );
+        LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo);
+        if (sourceLocMast == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶄俊鎭笉瀛樺湪", sourceLocNo);
+            return;
+        }
+        if (!sourceLocMast.getLocSts().equals("S")) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶇姸鎬佷笉澶勪簬鍏ュ簱棰勭害", sourceLocNo);
+            return;
+        }
+
+        LocMast locMast = locMastService.queryByLoc(locNo);
+        if (locMast == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅淇℃伅涓嶅瓨鍦�", locNo);
+            return;
+        }
+        if (!locMast.getLocSts().equals("O")) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅鐘舵�佷笉澶勪簬绌哄簱浣�", locNo);
+            return;
+        }
+
+        FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
+        if (findCrnNoResult == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
+            return;
+        }
+        Integer crnNo = findCrnNoResult.getCrnNo();
+
+        Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId());
+        if (targetStationId == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationProtocol.getStationId());
+            return;
+        }
+
+        StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0);
         if (command == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "绔欑偣浠诲姟鍋滅暀瓒呮椂鍚庨噸绠楄矾寰勫け璐ワ紝褰撳墠绔欑偣={}锛岀洰鏍囩珯鐐�={}", stationProtocol.getStationId(), moveStaNo);
+            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
             return;
         }
 
-        MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
-        syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderList, dispatchDecision, command);
-        saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis()));
-        News.info("杈撻�佺珯鐐逛换鍔″仠鐣檣}绉掓湭杩愯锛屽凡閲嶆柊璁$畻璺緞骞堕噸鍚繍琛岋紝绔欑偣鍙�={}锛岀洰鏍囩珯={}锛屽伐浣滃彿={}锛屾竻鐞嗘棫鍒嗘鍛戒护鏁�={}锛屽懡浠ゆ暟鎹�={}",
-                STATION_IDLE_RECOVER_SECONDS, stationProtocol.getStationId(), moveStaNo, wrkMast.getWrkNo(), clearedCommandCount, JSON.toJSONString(command));
+        sourceLocMast.setLocSts("O");
+        sourceLocMast.setModiTime(new Date());
+        locMastService.updateById(sourceLocMast);
+
+        locMast.setLocSts("S");
+        locMast.setModiTime(new Date());
+        locMastService.updateById(locMast);
+
+        wrkMast.setLocNo(locNo);
+        wrkMast.setStaNo(targetStationId);
+
+        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
+            wrkMast.setCrnNo(crnNo);
+        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
+            wrkMast.setDualCrnNo(crnNo);
+        } else {
+            throw new CoolException("鏈煡璁惧绫诲瀷");
+        }
+
+        if (!wrkMastService.updateById(wrkMast)) {
+            return;
+        }
+        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
+        if (!offered) {
+            return;
+        }
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.recordDispatch(
+                    wrkMast.getWrkNo(),
+                    stationProtocol.getStationId(),
+                    "checkStationRunBlock_direct",
+                    command,
+                    false
+            );
+        }
     }
 
     private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) {
@@ -1370,6 +1860,29 @@
         }
         return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)
                 || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts);
+    }
+
+    private boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) {
+        if (stationMoveCoordinator == null || taskNo == null || taskNo <= 0 || stationId == null) {
+            return false;
+        }
+        StationMoveSession session = stationMoveCoordinator.loadSession(taskNo);
+        if (session == null || !session.isActive() || session.getLastIssuedAt() == null) {
+            return false;
+        }
+        if (!Objects.equals(stationId, session.getCurrentStationId())
+                && !Objects.equals(stationId, session.getDispatchStationId())) {
+            return false;
+        }
+        long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt();
+        long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L;
+        if (elapsedMs >= thresholdMs) {
+            return false;
+        }
+        saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
+        News.info("杈撻�佺珯鐐逛换鍔″垰瀹屾垚鍛戒护涓嬪彂锛屽凡璺宠繃鍋滅暀閲嶇畻銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛岃窛涓婃涓嬪彂={}ms锛宺outeVersion={}",
+                stationId, taskNo, elapsedMs, session.getRouteVersion());
+        return true;
     }
 
     private void resetSegmentMoveCommandsBeforeReroute(Integer taskNo) {
@@ -1401,6 +1914,53 @@
             }
         }
         return count;
+    }
+
+    private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) {
+        if (deviceNo == null || command == null) {
+            return false;
+        }
+        String dedupKey = buildStationCommandDispatchDedupKey(deviceNo, command);
+        if (redisUtil != null) {
+            Object lock = redisUtil.get(dedupKey);
+            if (lock != null) {
+                News.info("杈撻�佺珯鐐瑰懡浠ょ煭鏃堕噸澶嶆淳鍙戯紝宸茶烦杩囥�俿cene={}锛宒eviceNo={}锛宼askNo={}锛宻tationId={}锛宼argetStaNo={}锛宑ommandType={}",
+                        scene,
+                        deviceNo,
+                        command.getTaskNo(),
+                        command.getStationId(),
+                        command.getTargetStaNo(),
+                        command.getCommandType());
+                return false;
+            }
+            redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS);
+        }
+        boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command));
+        if (!offered && redisUtil != null) {
+            redisUtil.del(dedupKey);
+        }
+        return offered;
+    }
+
+    private String buildStationCommandDispatchDedupKey(Integer deviceNo, StationCommand command) {
+        return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key
+                + deviceNo + "_"
+                + command.getTaskNo() + "_"
+                + command.getStationId() + "_"
+                + (stationMoveCoordinator == null ? Integer.toHexString(buildFallbackPathSignature(command).hashCode())
+                : stationMoveCoordinator.buildPathSignatureHash(command));
+    }
+
+    private String buildFallbackPathSignature(StationCommand command) {
+        if (command == null) {
+            return "";
+        }
+        return String.valueOf(command.getCommandType())
+                + "_" + command.getStationId()
+                + "_" + command.getTargetStaNo()
+                + "_" + command.getNavigatePath()
+                + "_" + command.getLiftTransferPath()
+                + "_" + command.getOriginalNavigatePath();
     }
 
     private int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack,
@@ -1514,10 +2074,33 @@
         );
     }
 
+    /**
+     * 娌库�滄簮绔� -> 鐩爣绔欌�濈殑鐞嗚璺緞锛屼粠褰撳墠绔欑偣寰�涓嬫父鍥炵湅锛屾壘鍑哄悓鎵规浠诲姟鍦ㄥ悗缁珯鐐逛笂鐨勫凡鐭ュ簭鍙枫��
+     * <p>
+     * 涓ユ牸绐楀彛鎺у埗瑕佸洖绛旂殑闂涓嶆槸鈥滃綋鍓嶄换鍔¤嚜宸辩殑 batchSeq 鏄灏戔�濓紝鑰屾槸锛�
+     * 鈥滃湪褰撳墠绔欑偣鍚庨潰锛屾部杩欐潯鍑哄簱閾捐矾宸茬粡鎺掔潃鐨勫悓鎵规浠诲姟锛屾渶闈犺繎鐩爣绔殑搴忓彿鏄灏戯紵鈥�
+     * 鍙湁鎷垮埌杩欎釜搴忓彿锛屾帓搴忕偣鎵嶈兘鍒ゆ柇褰撳墠浠诲姟鏄惁搴旇绱ц窡鍏跺悗鏀捐銆�
+     * <p>
+     * 鍏蜂綋鍋氭硶锛�
+     * <p>
+     * 1. 鎸� pathList 浠庡熬鍒板ご鍥炴壂锛屾埅鍑� searchStationId 涔嬪悗鐨勫叏閮ㄤ笅娓哥珯鐐癸紱
+     * 2. 璇诲彇杩欎簺绔欑偣褰撳墠姝e湪鎵ц鐨勪换鍔″彿锛�
+     * 3. 濡傛灉鏌愮珯鐐逛笂鐨勪换鍔″睘浜� searchBatch锛屽氨璁板綍瀹冪殑 batchSeq锛�
+     * 4. 杩斿洖璇ユ壒娆″湪涓嬫父宸茬煡鐨勫簭鍙枫��
+     * <p>
+     * 杩欓噷杩斿洖鐨勬槸鈥滆矾寰勪笅娓哥幇鍦哄凡鍑虹幇鐨勬壒娆″簭鍙封�濓紝涓嶆槸鎵规鐞嗚鏈�灏�/鏈�澶у�笺��
+     * 濡傛灉涓嬫父娌℃湁鍚屾壒娆′换鍔★紝杩斿洖 null锛岃皟鐢ㄦ柟闇�瑕侀��鍥炲埌鈥滃綋鍓嶆壒娆¢涓湭瀹屾垚浠诲姟鏄惁灏辨槸鑷繁鈥濈殑鍒ゅ畾銆�
+     *
+     * @param pathList 浠庝换鍔℃簮绔欏埌鐩爣绔欑殑鐞嗚鍑哄簱璺緞
+     * @param searchStationId 褰撳墠姝e湪鍋氭帓搴忓喅绛栫殑绔欑偣
+     * @param searchBatch 褰撳墠浠诲姟鎵�灞炴壒娆�
+     * @return 涓嬫父鍚屾壒娆′换鍔$殑宸茬煡搴忓彿锛涜嫢璺緞涓嬫父灏氭湭鍑虹幇璇ユ壒娆″垯杩斿洖 null
+     */
     public Integer getOutStationBatchSeq(List<NavigateNode> pathList, Integer searchStationId, String searchBatch) {
         if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) {
             return null;
         }
+        // 鍙叧蹇冨綋鍓嶇珯鐐逛箣鍚庣殑涓嬫父绔欑偣锛屽綋鍓嶇珯鐐逛箣鍓嶇殑鑺傜偣涓嶄細褰卞搷鈥滆皝搴旇鎺掑湪鎴戝墠闈⑩�濄��
         List<Integer> checkList = new ArrayList<>();
         for (int i = pathList.size() - 1; i >= 0; i--) {
             NavigateNode node = pathList.get(i);
@@ -1532,6 +2115,7 @@
             }
         }
 
+        // 涓嬫父绔欑偣鍙兘鍚屾椂鎸傜潃澶氫釜涓嶅悓鎵规浠诲姟锛涜繖閲屽彧鎶藉彇涓庡綋鍓嶆壒娆$浉鍏崇殑鐜板満搴忓彿蹇収銆�
         HashMap<String, Integer> batchMap = new HashMap<>();
         for (Integer station : checkList) {
             BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", station));
@@ -1718,6 +2302,412 @@
         return pathLenFactor;
     }
 
+    enum RerouteSceneType {
+        RUN_BLOCK_REROUTE,
+        IDLE_RECOVER,
+        OUT_ORDER,
+        WATCH_CIRCLE
+    }
+
+    static final class RerouteDecision {
+        private final boolean skip;
+        private final String skipReason;
+        private final Integer targetStationId;
+        private final OutOrderDispatchDecision dispatchDecision;
+
+        private RerouteDecision(boolean skip,
+                                String skipReason,
+                                Integer targetStationId,
+                                OutOrderDispatchDecision dispatchDecision) {
+            this.skip = skip;
+            this.skipReason = skipReason;
+            this.targetStationId = targetStationId;
+            this.dispatchDecision = dispatchDecision;
+        }
+
+        static RerouteDecision skip(String reason) {
+            return new RerouteDecision(true, reason, null, null);
+        }
+
+        static RerouteDecision proceed(Integer targetStationId) {
+            return new RerouteDecision(false, null, targetStationId, null);
+        }
+
+        static RerouteDecision proceed(Integer targetStationId,
+                                       OutOrderDispatchDecision dispatchDecision) {
+            return new RerouteDecision(false, null, targetStationId, dispatchDecision);
+        }
+
+        boolean skip() {
+            return skip;
+        }
+
+        String skipReason() {
+            return skipReason;
+        }
+
+        Integer targetStationId() {
+            return targetStationId;
+        }
+
+        OutOrderDispatchDecision dispatchDecision() {
+            return dispatchDecision;
+        }
+    }
+
+    /**
+     * 閲嶇畻/閲嶅彂閾捐矾鐨勪竴娆℃�ф墽琛屼笂涓嬫枃銆�
+     *
+     * <p>杩欎釜瀵硅薄鍙湪涓�娆� reroute 鎵ц杩囩▼涓瓨鍦紝涓嶄細钀藉簱锛屼篃涓嶄細闀挎湡缂撳瓨銆�
+     * 瀹冪殑鑱岃矗涓嶆槸琛ㄨ揪涓氬姟鐘舵�侊紝鑰屾槸鎶娾�滆繖娆′负浠�涔堣繘鏉ャ�佸綋鍓嶇敤鍝杩愯鏃跺璞°��
+     * 涓嬪彂鍓嶅悗瑕佷笉瑕佸惎鐢ㄩ澶栨帶鍒堕�昏緫鈥濋泦涓墦鍖咃紝渚涚粺涓�鎵ц閾捐矾浣跨敤銆�
+     *
+     * <p>缁熶竴鎵ц閾捐矾澶ц嚧鍒嗕笁娈碉細
+     * 1. {@code resolveSharedRerouteDecision} 鏍规嵁褰撳墠浠诲姟鍜屽満鏅厛鍐崇瓥鐩爣绔欍��
+     * 2. {@code buildRerouteCommandPlan} 鍐冲畾鐢ㄦ櫘閫氬嚭搴撶畻璺繕鏄� run-block 涓撶敤绠楄矾銆�
+     * 3. {@code executeReroutePlan} 鎸変笂涓嬫枃閲岀殑寮�鍏冲喅瀹氭槸鍚﹀仛 suppress銆佹槸鍚﹀姞閿併��
+     *    鏄惁鍏� cancel 鏃� session銆佹槸鍚﹀厛娓呮棫鍒嗘鍛戒护锛岀劧鍚庣湡姝d笅鍙戙��
+     *
+     * <p>瀛楁鍙互鍒嗘垚鍥涚粍鐞嗚В锛�
+     * 1. 鍦烘櫙涓庤繍琛屾椂瀵硅薄锛�
+     *    {@code sceneType} / {@code basDevp} / {@code stationThread} /
+     *    {@code stationProtocol} / {@code wrkMast}
+     *    琛ㄧず鈥滆皝鍦ㄤ粈涔堝満鏅笅瑙﹀彂浜嗚繖娆¢噸绠椻�濄��
+     * 2. 鐩爣鍐崇瓥杈撳叆锛�
+     *    {@code outOrderStationIds} / {@code pathLenFactor}
+     *    琛ㄧず鈥滅洰鏍囩珯濡備綍绠椼�佽矾寰勫�惧悜绯绘暟鏄灏戔�濄��
+     * 3. 涓嬪彂鐩爣淇℃伅锛�
+     *    {@code dispatchScene} / {@code dispatchDeviceNo}
+     *    琛ㄧず鈥滆繖娆″懡浠ゆ渶缁堝線鍝釜杈撻�佽澶囬槦鍒楀彂锛屼互鍙婃棩蹇�/鍘婚噸鍦烘櫙鍚嶆槸浠�涔堚�濄��
+     * 4. 鎵ц鎺у埗寮�鍏筹細
+     *    {@code useRunBlockCommand} / {@code checkSuppressDispatch} /
+     *    {@code requireOutOrderDispatchLock} / {@code cancelSessionBeforeDispatch} /
+     *    {@code resetSegmentCommandsBeforeDispatch} / {@code clearIdleIssuedCommands} /
+     *    {@code checkRecentDispatch} / {@code executionLockKey} / {@code executionLockSeconds}
+     *    琛ㄧず鈥滅湡姝f墽琛屽墠鍚庤鎵撳紑鍝簺淇濇姢鍔ㄤ綔鈥濄��
+     *
+     * <p>瀹冩湰璐ㄤ笂鏄竴涓弬鏁板璞″姞甯冨皵寮�鍏抽泦鍚堬細
+     * {@code create(...)} 鍏堟妸杩欐 reroute 鐨勫熀纭�鐜板満濉繘鏉ワ紝
+     * 鍐嶉�氳繃 {@code withXxx(...)} 閫愰」澹版槑杩欐鎵ц闇�瑕侀檮鍔犲摢浜涙帶鍒惰涔夈��
+     *
+     * <p>渚嬪锛�
+     * RUN_BLOCK_REROUTE 浼氭墦寮� {@code useRunBlockCommand}锛�
+     * 骞惰姹傚湪涓嬪彂鍓嶅厛 {@code cancelSession}銆佸厛娓呮棫鍒嗘鍛戒护锛�
+     * OUT_ORDER 浼氭墦寮� suppress guard 鍜� out-order lock锛�
+     * IDLE_RECOVER 鍒欎細鎵撳紑 recent dispatch guard锛屽苟璁板綍/娓呯悊鍋滅暀鏈熼棿宸蹭笅鍙戝懡浠ゃ��
+     */
+    static final class RerouteContext {
+        // 鏈 reroute 鐨勮Е鍙戞潵婧愩�傚喅瀹氬悗闈㈣蛋鍝被鐩爣瑁佸喅銆佹棩蹇楁枃妗堝拰鐗规畩淇濇姢鍒嗘敮銆�
+        private final RerouteSceneType sceneType;
+        // 褰撳墠杈撻�佽澶囬厤缃紝涓昏鐢ㄤ簬鎷块粯璁や笅鍙戣澶囧彿鍜岀浉鍏崇珯鐐归厤缃��
+        private final BasDevp basDevp;
+        // 褰撳墠绔欑偣绾跨▼锛屽悗闈㈡瀯閫犺緭閫佸懡浠ゆ椂鐩存帴渚濊禆瀹冨彇 command銆�
+        private final StationThread stationThread;
+        // 瑙﹀彂杩欐 reroute 鐨勭幇鍦虹珯鐐圭姸鎬佸揩鐓э紝鍖呭惈 stationId/taskNo/runBlock/targetStaNo 绛夎繍琛屾椂淇℃伅銆�
+        private final StationProtocol stationProtocol;
+        // 褰撳墠宸ヤ綔涓昏〃璁板綍锛岃〃绀鸿繖娆¢噸绠楀搴旂殑浠诲姟涓荤姸鎬併��
+        private final WrkMast wrkMast;
+        // 褰撳墠璁惧鐨勫嚭搴撴帓搴忕偣鍒楄〃銆傜洰鏍囩珯鍐崇瓥鏃堕渶瑕佺煡閬撳摢浜涚珯鐐瑰睘浜� out-order 鑺傜偣銆�
+        private final List<Integer> outOrderStationIds;
+        // 璺緞闀垮害鍊惧悜绯绘暟銆傛壒娆″嚭搴撴椂鐢ㄤ簬璁╀笉鍚屼换鍔″璺緞閫夋嫨鏈夌ǔ瀹氬亸濂姐��
+        private final Double pathLenFactor;
+        // 杩欐娲惧彂鍦ㄦ棩蹇椼�佸幓閲嶃�乻ession 璁板綍涓娇鐢ㄧ殑鍦烘櫙鍚嶏紝渚嬪 checkStationRunBlock_reroute銆�
+        private final String dispatchScene;
+        // 瀹為檯鎶曢�掑懡浠ょ殑璁惧鍙枫�傞粯璁ゅ彇 basDevp.getDevpNo()锛屾煇浜涘満鏅彲鏄惧紡瑕嗙洊銆�
+        private Integer dispatchDeviceNo;
+        // true 琛ㄧず鍛戒护鏋勯�犻樁娈垫敼璧� stationThread.getRunBlockRerouteCommand锛岃�屼笉鏄櫘閫氬嚭搴撶畻璺��
+        private boolean useRunBlockCommand;
+        // true 琛ㄧず鎵ц鍓嶈鍏堝仛 active-session suppress锛岄伩鍏嶆棫娲诲姩璺嚎琚噸澶嶆彃鍏ユ柊鍛戒护銆�
+        private boolean checkSuppressDispatch;
+        // true 琛ㄧず鎵ц鍓嶈鍔� out-order 涓撶敤鐭攣锛岄槻姝㈠悓涓�鎺掑簭鐐圭煭鏃堕棿閲嶅璁$畻/涓嬪彂銆�
+        private boolean requireOutOrderDispatchLock;
+        // true 琛ㄧず鐪熸涓嬪彂鍓嶅厛鍙栨秷鏃� session銆傞�氬父鐢ㄤ簬 reroute 鏇挎崲鏃ц矾绾裤��
+        private boolean cancelSessionBeforeDispatch;
+        // true 琛ㄧず鐪熸涓嬪彂鍓嶅厛娓呯悊鏃у垎娈佃緭閫佸懡浠わ紝閬垮厤 segment executor 杩樻寔鏈夋棫璺嚎銆�
+        private boolean resetSegmentCommandsBeforeDispatch;
+        // true 琛ㄧず瑕佹竻鐞� idle recover 鏈熼棿宸茬粡涓嬪彂杩囦絾鏈疄闄呯敓鏁堢殑鏃у懡浠ょ棔杩广��
+        private boolean clearIdleIssuedCommands;
+        // true 琛ㄧず鎵ц鍓嶈妫�鏌モ�滄渶杩戝垰涓嬪彂杩団�濓紝鐢ㄤ簬 idle recover 閬垮厤鍒氬彂瀹屽氨閲嶇畻銆�
+        private boolean checkRecentDispatch;
+        // 鍙�夋墽琛岄攣 key銆傜敤浜庣粰鏌愪釜 reroute 鍦烘櫙鍔犵煭鏃堕棿浜掓枼銆�
+        private String executionLockKey;
+        // executionLockKey 瀵瑰簲鐨勯攣绉掓暟銆�
+        private int executionLockSeconds;
+        // 浠� idle recover 闇�瑕侊紝璁板綍鍋滅暀璺熻釜涓婁笅鏂囷紝渚涙竻鐞嗘棫鍛戒护涓庢洿鏂版椂闂翠娇鐢ㄣ��
+        private StationTaskIdleTrack idleTrack;
+
+        private RerouteContext(RerouteSceneType sceneType,
+                               BasDevp basDevp,
+                               StationThread stationThread,
+                               StationProtocol stationProtocol,
+                               WrkMast wrkMast,
+                               List<Integer> outOrderStationIds,
+                               Double pathLenFactor,
+                               String dispatchScene) {
+            this.sceneType = sceneType;
+            this.basDevp = basDevp;
+            this.stationThread = stationThread;
+            this.stationProtocol = stationProtocol;
+            this.wrkMast = wrkMast;
+            this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds;
+            this.pathLenFactor = pathLenFactor;
+            this.dispatchScene = dispatchScene;
+            this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo();
+        }
+
+        static RerouteContext create(RerouteSceneType sceneType,
+                                     BasDevp basDevp,
+                                     StationThread stationThread,
+                                     StationProtocol stationProtocol,
+                                     WrkMast wrkMast,
+                                     List<Integer> outOrderStationIds,
+                                     Double pathLenFactor,
+                                     String dispatchScene) {
+            // create 鍙礋璐e~鍩虹鐜板満锛屼笉榛樿鎵撳紑浠讳綍鎵ц寮�鍏炽��
+            // 姣忎釜鍦烘櫙鍚庨潰閫氳繃 withXxx 鏄庣‘澹版槑鑷繁闇�瑕佸摢浜涢檮鍔犳帶鍒躲��
+            return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene);
+        }
+
+        RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) {
+            // 瑕嗙洊榛樿涓嬪彂璁惧鍙枫�傚吀鍨嬪満鏅槸 out-order 绔欑偣閰嶇疆鐨� deviceNo 涓� basDevp 榛樿鍊间笉鍚屻��
+            this.dispatchDeviceNo = dispatchDeviceNo;
+            return this;
+        }
+
+        RerouteContext withRunBlockCommand() {
+            // 鍛戒护鏋勯�犻樁娈靛垏鎹㈠埌 run-block 涓撶敤绠楄矾鍣ㄣ��
+            // 鐩爣绔欎粛鐢辩粺涓�鍐崇瓥閫昏緫鍐冲畾锛屽彧鏄�滃幓鐩爣绔欑殑璺緞鈥濇敼涓哄牭濉為噸瑙勫垝绠楁硶鐢熸垚銆�
+            this.useRunBlockCommand = true;
+            return this;
+        }
+
+        RerouteContext withSuppressDispatchGuard() {
+            // 鎵ц鍓嶅惎鐢� session suppress锛�
+            // 濡傛灉褰撳墠 task 鍦ㄥ綋鍓嶄綅缃凡缁忔湁涓�鏉℃椿鍔ㄤ腑鐨勫悓璺緞/鍚岃鐩栬寖鍥磋矾绾匡紝鍒欐湰娆′笉鍐嶉噸澶嶆淳鍙戙��
+            this.checkSuppressDispatch = true;
+            return this;
+        }
+
+        RerouteContext withOutOrderDispatchLock() {
+            // 鎵ц鍓嶅惎鐢ㄦ帓搴忕偣鐭攣銆�
+            // 涓昏闃叉鍚屼竴涓� out-order/watch-circle 瑙﹀彂鐐瑰湪鏋佺煭鏃堕棿鍐呰骞跺彂閲嶅閲嶇畻銆�
+            this.requireOutOrderDispatchLock = true;
+            return this;
+        }
+
+        RerouteContext withCancelSessionBeforeDispatch() {
+            // 鎵ц鍓嶆樉寮忓彇娑堟棫 session銆�
+            // 璇箟鏄�滄湰娆″懡浠ゅ噯澶囨浛鎹㈡棫璺嚎鈥濓紝鏃� routeVersion 涔嬪悗涓嶅簲鍐嶇户缁帹杩涖��
+            this.cancelSessionBeforeDispatch = true;
+            return this;
+        }
+
+        RerouteContext withResetSegmentCommandsBeforeDispatch() {
+            // 鎵ц鍓嶆竻鎺� segment executor 渚ф棫鍒嗘鍛戒护銆�
+            // 杩欏 run-block/idle recover 寰堝叧閿紝鍚﹀垯绯荤粺鍙兘杩樻嬁鐫�鏃� segment 鐘舵�侀樆鏂柊璺嚎銆�
+            this.resetSegmentCommandsBeforeDispatch = true;
+            return this;
+        }
+
+        RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) {
+            // 浠� idle recover 浣跨敤锛�
+            // 琛ㄧず閲嶅惎鍓嶈鎶娾�滃仠鐣欐湡闂村凡缁忓彂杩囦絾鍙兘鏈湡姝f墽琛岀殑鍛戒护鐥曡抗鈥濇竻鐞嗘帀銆�
+            this.clearIdleIssuedCommands = true;
+            this.idleTrack = idleTrack;
+            return this;
+        }
+
+        RerouteContext withRecentDispatchGuard() {
+            // 鎵ц鍓嶆鏌ユ渶杩戞槸鍚﹀垰涓嬪彂杩囥��
+            // 閬垮厤 idle recover 鍦ㄢ�滃垰閲嶅彂瀹屸�濈殑绐楀彛鍐呭張椹笂瑙﹀彂涓�娆°��
+            this.checkRecentDispatch = true;
+            return this;
+        }
+
+        RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) {
+            // 涓烘煇涓満鏅寕涓�涓嫭绔嬫墽琛岄攣銆�
+            // 鍜� out-order lock 涓嶅悓锛岃繖閲屾槸娉涘寲閿侊紝璋佷紶 key 璋佽礋璐e畾涔夐攣璇箟銆�
+            this.executionLockKey = executionLockKey;
+            this.executionLockSeconds = executionLockSeconds;
+            return this;
+        }
+
+        RerouteSceneType sceneType() {
+            return sceneType;
+        }
+
+        BasDevp basDevp() {
+            return basDevp;
+        }
+
+        StationThread stationThread() {
+            return stationThread;
+        }
+
+        StationProtocol stationProtocol() {
+            return stationProtocol;
+        }
+
+        WrkMast wrkMast() {
+            return wrkMast;
+        }
+
+        List<Integer> outOrderStationIds() {
+            return outOrderStationIds;
+        }
+
+        Double pathLenFactor() {
+            return pathLenFactor;
+        }
+
+        String dispatchScene() {
+            return dispatchScene;
+        }
+
+        Integer dispatchDeviceNo() {
+            return dispatchDeviceNo;
+        }
+
+        boolean useRunBlockCommand() {
+            return useRunBlockCommand;
+        }
+
+        boolean checkSuppressDispatch() {
+            return checkSuppressDispatch;
+        }
+
+        boolean requireOutOrderDispatchLock() {
+            return requireOutOrderDispatchLock;
+        }
+
+        boolean cancelSessionBeforeDispatch() {
+            return cancelSessionBeforeDispatch;
+        }
+
+        boolean resetSegmentCommandsBeforeDispatch() {
+            return resetSegmentCommandsBeforeDispatch;
+        }
+
+        boolean clearIdleIssuedCommands() {
+            return clearIdleIssuedCommands;
+        }
+
+        boolean checkRecentDispatch() {
+            return checkRecentDispatch;
+        }
+
+        String executionLockKey() {
+            return executionLockKey;
+        }
+
+        int executionLockSeconds() {
+            return executionLockSeconds;
+        }
+
+        StationTaskIdleTrack idleTrack() {
+            return idleTrack;
+        }
+    }
+
+    static final class RerouteCommandPlan {
+        private final boolean skip;
+        private final String skipReason;
+        private final StationCommand command;
+        private final RerouteDecision decision;
+        private final String dispatchScene;
+
+        private RerouteCommandPlan(boolean skip,
+                                   String skipReason,
+                                   StationCommand command,
+                                   RerouteDecision decision,
+                                   String dispatchScene) {
+            this.skip = skip;
+            this.skipReason = skipReason;
+            this.command = command;
+            this.decision = decision;
+            this.dispatchScene = dispatchScene;
+        }
+
+        static RerouteCommandPlan skip(String reason) {
+            return new RerouteCommandPlan(true, reason, null, null, null);
+        }
+
+        static RerouteCommandPlan dispatch(StationCommand command,
+                                           RerouteDecision decision,
+                                           String dispatchScene) {
+            return new RerouteCommandPlan(false, null, command, decision, dispatchScene);
+        }
+
+        boolean skip() {
+            return skip;
+        }
+
+        String skipReason() {
+            return skipReason;
+        }
+
+        StationCommand command() {
+            return command;
+        }
+
+        RerouteDecision decision() {
+            return decision;
+        }
+
+        String dispatchScene() {
+            return dispatchScene;
+        }
+    }
+
+    static final class RerouteExecutionResult {
+        private final boolean skipped;
+        private final String skipReason;
+        private final boolean dispatched;
+        private final StationCommand command;
+        private final int clearedCommandCount;
+
+        private RerouteExecutionResult(boolean skipped,
+                                       String skipReason,
+                                       boolean dispatched,
+                                       StationCommand command,
+                                       int clearedCommandCount) {
+            this.skipped = skipped;
+            this.skipReason = skipReason;
+            this.dispatched = dispatched;
+            this.command = command;
+            this.clearedCommandCount = clearedCommandCount;
+        }
+
+        static RerouteExecutionResult skip(String reason) {
+            return new RerouteExecutionResult(true, reason, false, null, 0);
+        }
+
+        static RerouteExecutionResult dispatched(StationCommand command,
+                                                 int clearedCommandCount) {
+            return new RerouteExecutionResult(false, null, true, command, clearedCommandCount);
+        }
+
+        boolean skipped() {
+            return skipped;
+        }
+
+        String skipReason() {
+            return skipReason;
+        }
+
+        boolean dispatched() {
+            return dispatched;
+        }
+
+        StationCommand command() {
+            return command;
+        }
+
+        int clearedCommandCount() {
+            return clearedCommandCount;
+        }
+    }
+
     private static class OutOrderDispatchDecision {
         private final Integer targetStationId;
         private final boolean circle;

--
Gitblit v1.9.1