From 53332ecb0edc651bae91f1e7a74a795d76d87cb2 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 04 三月 2026 17:18:07 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/core/utils/StationOperateProcessUtils.java |  350 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 334 insertions(+), 16 deletions(-)

diff --git a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
index 1e8058c..72d192f 100644
--- a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -7,6 +7,8 @@
 import com.core.common.Cools;
 import com.core.exception.CoolException;
 import com.zy.asrs.domain.enums.NotifyMsgType;
+import com.zy.asrs.domain.vo.StationCycleCapacityVo;
+import com.zy.asrs.domain.vo.StationCycleLoopVo;
 import com.zy.asrs.entity.*;
 import com.zy.asrs.service.*;
 import com.zy.asrs.utils.NotifyUtils;
@@ -32,6 +34,7 @@
 
 @Component
 public class StationOperateProcessUtils {
+    private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
 
     @Autowired
     private BasDevpService basDevpService;
@@ -51,10 +54,16 @@
     private NavigateUtils navigateUtils;
     @Autowired
     private BasStationService basStationService;
+    @Autowired
+    private StationCycleCapacityService stationCycleCapacityService;
 
     //鎵ц杈撻�佺珯鐐瑰叆搴撲换鍔�
     public synchronized void stationInExecute() {
         try {
+            DispatchLimitConfig limitConfig = getDispatchLimitConfig();
+            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
+            LoadGuardState loadGuardState = buildLoadGuardState(limitConfig);
+
             List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<>());
             for (BasDevp basDevp : basDevps) {
                 StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
@@ -109,6 +118,12 @@
                             continue;
                         }
 
+                        LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState);
+
+                        if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
+                            return;
+                        }
+
                         StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0);
                         if (command == null) {
                             News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
@@ -124,6 +139,8 @@
                             MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
                             News.info("杈撻�佺珯鐐瑰叆搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command));
                             redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5);
+                            loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                            saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
                         }
                     }
                 }
@@ -136,6 +153,10 @@
     //鎵ц鍫嗗灈鏈鸿緭閫佺珯鐐瑰嚭搴撲换鍔�
     public synchronized void crnStationOutExecute() {
         try {
+            DispatchLimitConfig limitConfig = getDispatchLimitConfig();
+            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
+            LoadGuardState loadGuardState = buildLoadGuardState(limitConfig);
+
             List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
                     .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
                     .isNotNull("crn_no")
@@ -188,6 +209,12 @@
                         }
                     }
 
+                    LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState);
+
+                    if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
+                        return;
+                    }
+
                     StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), moveStaNo, 0);
                     if (command == null) {
                         News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
@@ -202,6 +229,9 @@
                         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());
+                        currentStationTaskCountRef[0]++;
+                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                        saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
                     }
                 }
             }
@@ -302,6 +332,7 @@
                     wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts);
                     wrkMast.setIoTime(new Date());
                     wrkMastService.updateById(wrkMast);
+                    notifyUtils.notify(String.valueOf(SlaveType.Devp), basStation.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null);
                     redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
                 }
             }
@@ -494,22 +525,7 @@
 
     //鑾峰彇杈撻�佺嚎浠诲姟鏁伴噺
     public synchronized int getCurrentStationTaskCount() {
-        int currentStationTaskCount = 0;
-        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getId());
-            if (stationThread == null) {
-                continue;
-            }
-
-            for (StationProtocol stationProtocol : stationThread.getStatus()) {
-                if (stationProtocol.getTaskNo() > 0) {
-                    currentStationTaskCount++;
-                }
-            }
-        }
-
-        return currentStationTaskCount;
+        return countCurrentStationTask();
     }
 
     // 妫�娴嬪嚭搴撴帓搴�
@@ -755,4 +771,306 @@
         return seq;
     }
 
+    private int countCurrentStationTask() {
+        int currentStationTaskCount = 0;
+        List<BasDevp> basDevps = basDevpService.selectList(new EntityWrapper<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.getTaskNo() > 0) {
+                    currentStationTaskCount++;
+                }
+            }
+        }
+        return currentStationTaskCount;
+    }
+
+    private boolean isDispatchBlocked(DispatchLimitConfig config,
+                                      int currentStationTaskCount,
+                                      LoadGuardState loadGuardState,
+                                      boolean needReserveLoopLoad) {
+        if (config.loopModeEnable) {
+            double currentLoad = loadGuardState.currentLoad();
+            if (currentLoad >= config.circleMaxLoadLimit) {
+                News.warn("褰撳墠鎵胯浇閲忚揪鍒颁笂闄愶紝宸插仠姝㈢珯鐐逛换鍔′笅鍙戙�傚綋鍓嶆壙杞介噺={}锛屼笂闄�={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit));
+                return true;
+            }
+
+            if (needReserveLoopLoad) {
+                double reserveLoad = loadGuardState.loadAfterReserve();
+                if (reserveLoad >= config.circleMaxLoadLimit) {
+                    News.warn("棰勫崰鍚庢壙杞介噺杈惧埌涓婇檺锛屽凡鍋滄绔欑偣浠诲姟涓嬪彂銆傞鍗犲悗鎵胯浇閲�={}锛屼笂闄�={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit));
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) {
+        LoadGuardState state = new LoadGuardState();
+        if (!config.loopModeEnable) {
+            return state;
+        }
+
+        StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
+        if (capacityVo == null) {
+            return state;
+        }
+
+        state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount());
+        state.projectedTaskStationCount = toNonNegative(capacityVo.getTaskStationCount());
+
+        List<StationCycleLoopVo> loopList = capacityVo.getLoopList();
+        if (loopList != null) {
+            for (StationCycleLoopVo loopVo : loopList) {
+                if (loopVo == null || loopVo.getStationIdList() == null) {
+                    continue;
+                }
+                Integer loopNo = loopVo.getLoopNo();
+                for (Integer stationId : loopVo.getStationIdList()) {
+                    if (stationId != null) {
+                        if (loopNo != null) {
+                            state.stationLoopNoMap.put(stationId, loopNo);
+                        }
+                    }
+                }
+            }
+        }
+        return state;
+    }
+
+    private LoopHitResult findPathLoopHit(DispatchLimitConfig config,
+                                          Integer sourceStationId,
+                                          Integer targetStationId,
+                                          LoadGuardState loadGuardState) {
+        if (!config.loopModeEnable) {
+            return LoopHitResult.NO_HIT;
+        }
+        if (sourceStationId == null || targetStationId == null) {
+            return LoopHitResult.NO_HIT;
+        }
+        if (loadGuardState.stationLoopNoMap.isEmpty()) {
+            return LoopHitResult.NO_HIT;
+        }
+
+        try {
+            List<NavigateNode> nodes = navigateUtils.calcByStationId(sourceStationId, targetStationId);
+            if (nodes == null || nodes.isEmpty()) {
+                return LoopHitResult.NO_HIT;
+            }
+
+            for (NavigateNode node : nodes) {
+                Integer stationId = getStationIdFromNode(node);
+                if (stationId == null) {
+                    continue;
+                }
+                Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId);
+                if (loopNo != null) {
+                    return new LoopHitResult(true, loopNo, stationId);
+                }
+            }
+        } catch (Exception e) {
+            return LoopHitResult.NO_HIT;
+        }
+
+        return LoopHitResult.NO_HIT;
+    }
+
+    private Integer getStationIdFromNode(NavigateNode node) {
+        if (node == null || isBlank(node.getNodeValue())) {
+            return null;
+        }
+        try {
+            JSONObject v = JSONObject.parseObject(node.getNodeValue());
+            if (v == null) {
+                return null;
+            }
+            return v.getInteger("stationId");
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private int toNonNegative(Integer value) {
+        if (value == null || value < 0) {
+            return 0;
+        }
+        return value;
+    }
+
+    private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) {
+        if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) {
+            return;
+        }
+        JSONObject reserveJson = new JSONObject();
+        reserveJson.put("wrkNo", wrkNo);
+        reserveJson.put("loopNo", loopHitResult.getLoopNo());
+        reserveJson.put("hitStationId", loopHitResult.getHitStationId());
+        reserveJson.put("createTime", System.currentTimeMillis());
+        redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString());
+        redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS);
+    }
+
+    private DispatchLimitConfig getDispatchLimitConfig() {
+        DispatchLimitConfig config = new DispatchLimitConfig();
+        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        if (!(systemConfigMapObj instanceof Map)) {
+            return config;
+        }
+        Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
+
+        config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit);
+        String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable");
+        if (isBlank(loopModeValue)) {
+            loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable");
+        }
+        if (isBlank(loopModeValue)) {
+            loopModeValue = getConfigValue(systemConfigMap, "isCircleMode");
+        }
+        config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable);
+
+        return config;
+    }
+
+    private String getConfigValue(Map<?, ?> configMap, String key) {
+        Object value = configMap.get(key);
+        if (value == null) {
+            return null;
+        }
+        return String.valueOf(value).trim();
+    }
+
+    private boolean parseBoolean(String value, boolean defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        String lowValue = value.toLowerCase(Locale.ROOT);
+        if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue)
+                || "1".equals(lowValue) || "on".equals(lowValue)) {
+            return true;
+        }
+        if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue)
+                || "0".equals(lowValue) || "off".equals(lowValue)) {
+            return false;
+        }
+        return defaultValue;
+    }
+
+    private double parseLoadLimit(String value, double defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            String normalized = value.replace("%", "").trim();
+            double parsed = Double.parseDouble(normalized);
+            if (parsed > 1.0) {
+                parsed = parsed / 100.0;
+            }
+            if (parsed < 0.0) {
+                return 0.0;
+            }
+            if (parsed > 1.0) {
+                return 1.0;
+            }
+            return parsed;
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    private int parseInt(String value, int defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            int parsed = Integer.parseInt(value.trim());
+            return parsed < 0 ? defaultValue : parsed;
+        } catch (Exception e) {
+            return defaultValue;
+        }
+    }
+
+    private String formatPercent(double value) {
+        return String.format(Locale.ROOT, "%.1f%%", value * 100.0);
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+    private static class DispatchLimitConfig {
+        // 鍦堟渶澶ф壙杞借兘鍔涳紝榛樿80%
+        private double circleMaxLoadLimit = 0.8d;
+        // 鏄惁鍚敤缁曞湀妯″紡锛堜粎鍚敤鏃舵墠鐢熸晥鎵胯浇闄愬埗锛�
+        private boolean loopModeEnable = false;
+    }
+
+    private static class LoadGuardState {
+        private int totalStationCount = 0;
+        private int projectedTaskStationCount = 0;
+        private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>();
+
+        private double currentLoad() {
+            return calcLoad(this.projectedTaskStationCount, this.totalStationCount);
+        }
+
+        private double loadAfterReserve() {
+            return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount);
+        }
+
+        private void reserveLoopTask(Integer loopNo) {
+            if (loopNo == null || loopNo <= 0) {
+                return;
+            }
+            if (this.totalStationCount <= 0) {
+                return;
+            }
+            this.projectedTaskStationCount++;
+        }
+
+        private double calcLoad(int taskCount, int stationCount) {
+            if (stationCount <= 0 || taskCount <= 0) {
+                return 0.0;
+            }
+            double load = (double) taskCount / (double) stationCount;
+            if (load < 0.0) {
+                return 0.0;
+            }
+            if (load > 1.0) {
+                return 1.0;
+            }
+            return load;
+        }
+    }
+
+    private static class LoopHitResult {
+        private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null);
+        private final boolean throughLoop;
+        private final Integer loopNo;
+        private final Integer hitStationId;
+
+        private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) {
+            this.throughLoop = throughLoop;
+            this.loopNo = loopNo;
+            this.hitStationId = hitStationId;
+        }
+
+        private boolean isThroughLoop() {
+            return throughLoop;
+        }
+
+        private Integer getLoopNo() {
+            return loopNo;
+        }
+
+        private Integer getHitStationId() {
+            return hitStationId;
+        }
+    }
+
 }

--
Gitblit v1.9.1