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

---
 src/main/java/com/zy/core/utils/StationOperateProcessUtils.java             |  365 ++++++++++++++++++++++++++++++++++++++-
 src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java |  157 +++++++++++++++++
 src/main/java/com/zy/core/enums/RedisKeyType.java                           |    1 
 src/main/resources/application.yml                                          |    2 
 4 files changed, 508 insertions(+), 17 deletions(-)

diff --git a/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java b/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
index f79dda5..493238d 100644
--- a/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
+++ b/src/main/java/com/zy/asrs/service/impl/StationCycleCapacityServiceImpl.java
@@ -12,8 +12,10 @@
 import com.zy.asrs.service.DeviceConfigService;
 import com.zy.asrs.service.StationCycleCapacityService;
 import com.zy.common.model.NavigateNode;
+import com.zy.common.utils.RedisUtil;
 import com.zy.common.utils.NavigateSolution;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.model.StationObjModel;
 import com.zy.core.model.protocol.StationProtocol;
@@ -37,6 +39,7 @@
 @Service("stationCycleCapacityService")
 @Slf4j
 public class StationCycleCapacityServiceImpl implements StationCycleCapacityService {
+    private static final long LOOP_LOAD_RESERVE_EXPIRE_MILLIS = 120_000L;
 
     @Autowired
     private BasMapService basMapService;
@@ -44,6 +47,8 @@
     private DeviceConfigService deviceConfigService;
     @Autowired
     private BasDevpService basDevpService;
+    @Autowired
+    private RedisUtil redisUtil;
 
     private final AtomicReference<StationCycleCapacityVo> snapshotRef = new AtomicReference<>(new StationCycleCapacityVo());
 
@@ -92,6 +97,7 @@
         int loopNo = 1;
         int totalStationCount = 0;
         int taskStationCount = 0;
+        Set<Integer> actualWorkNoSet = new HashSet<>();
 
         for (Set<Integer> scc : sccList) {
             if (!isCycleScc(scc, filteredGraph)) {
@@ -111,6 +117,7 @@
                     if (workNo != null && workNo > 0) {
                         workNoList.add(workNo);
                         currentLoopTaskCount++;
+                        actualWorkNoSet.add(workNo);
                     }
                 }
 
@@ -128,6 +135,9 @@
             }
         }
 
+        int reserveTaskCount = mergeReserveTaskCount(loopList, actualWorkNoSet);
+        taskStationCount += reserveTaskCount;
+
         StationCycleCapacityVo vo = new StationCycleCapacityVo();
         vo.setLoopList(loopList);
         vo.setLoopCount(loopList.size());
@@ -138,6 +148,153 @@
         return vo;
     }
 
+    private int mergeReserveTaskCount(List<StationCycleLoopVo> loopList, Set<Integer> actualWorkNoSet) {
+        if (loopList == null || loopList.isEmpty()) {
+            return 0;
+        }
+
+        Map<Object, Object> reserveMap = redisUtil.hmget(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key);
+        if (reserveMap == null || reserveMap.isEmpty()) {
+            return 0;
+        }
+
+        Map<Integer, StationCycleLoopVo> loopMap = new HashMap<>();
+        Map<Integer, StationCycleLoopVo> stationLoopMap = new HashMap<>();
+        for (StationCycleLoopVo loopVo : loopList) {
+            if (loopVo != null && loopVo.getLoopNo() != null) {
+                loopMap.put(loopVo.getLoopNo(), loopVo);
+            }
+            if (loopVo == null || loopVo.getStationIdList() == null) {
+                continue;
+            }
+            for (Integer stationId : loopVo.getStationIdList()) {
+                if (stationId != null) {
+                    stationLoopMap.put(stationId, loopVo);
+                }
+            }
+        }
+
+        long now = System.currentTimeMillis();
+        int mergedCount = 0;
+        List<Object> removeFieldList = new ArrayList<>();
+
+        for (Map.Entry<Object, Object> entry : reserveMap.entrySet()) {
+            ReserveRecord record = parseReserveRecord(entry.getKey(), entry.getValue());
+            if (record == null) {
+                removeFieldList.add(entry.getKey());
+                continue;
+            }
+
+            if (actualWorkNoSet.contains(record.wrkNo)) {
+                removeFieldList.add(entry.getKey());
+                continue;
+            }
+
+            if (record.createTime <= 0 || now - record.createTime > LOOP_LOAD_RESERVE_EXPIRE_MILLIS) {
+                removeFieldList.add(entry.getKey());
+                continue;
+            }
+
+            StationCycleLoopVo loopVo = loopMap.get(record.loopNo);
+            if (loopVo == null && record.hitStationId != null) {
+                loopVo = stationLoopMap.get(record.hitStationId);
+            }
+            if (loopVo == null) {
+                removeFieldList.add(entry.getKey());
+                continue;
+            }
+
+            List<Integer> workNoList = loopVo.getWorkNoList();
+            if (workNoList == null) {
+                workNoList = new ArrayList<>();
+                loopVo.setWorkNoList(workNoList);
+            }
+            if (workNoList.contains(record.wrkNo)) {
+                continue;
+            }
+
+            workNoList.add(record.wrkNo);
+            Collections.sort(workNoList);
+
+            int mergedTaskCount = toNonNegative(loopVo.getTaskCount()) + 1;
+            loopVo.setTaskCount(mergedTaskCount);
+            loopVo.setCurrentLoad(calcCurrentLoad(mergedTaskCount, toNonNegative(loopVo.getStationCount())));
+            mergedCount++;
+        }
+
+        if (!removeFieldList.isEmpty()) {
+            redisUtil.hdel(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, removeFieldList.toArray());
+        }
+        return mergedCount;
+    }
+
+    private ReserveRecord parseReserveRecord(Object fieldObj, Object valueObj) {
+        if (fieldObj == null || valueObj == null) {
+            return null;
+        }
+
+        Integer fieldWrkNo = parseInteger(String.valueOf(fieldObj));
+        if (fieldWrkNo == null || fieldWrkNo <= 0) {
+            return null;
+        }
+
+        JSONObject jsonObject;
+        try {
+            jsonObject = JSON.parseObject(String.valueOf(valueObj));
+        } catch (Exception e) {
+            return null;
+        }
+        if (jsonObject == null) {
+            return null;
+        }
+
+        Integer wrkNo = jsonObject.getInteger("wrkNo");
+        Integer loopNo = jsonObject.getInteger("loopNo");
+        Integer hitStationId = jsonObject.getInteger("hitStationId");
+        Long createTime = jsonObject.getLong("createTime");
+        if (wrkNo == null || wrkNo <= 0) {
+            wrkNo = fieldWrkNo;
+        }
+        if ((loopNo == null || loopNo <= 0) && (hitStationId == null || hitStationId <= 0)) {
+            return null;
+        }
+        if (createTime == null || createTime <= 0) {
+            return null;
+        }
+
+        ReserveRecord record = new ReserveRecord();
+        record.wrkNo = wrkNo;
+        record.loopNo = loopNo;
+        record.hitStationId = hitStationId;
+        record.createTime = createTime;
+        return record;
+    }
+
+    private Integer parseInteger(String value) {
+        if (value == null || value.trim().isEmpty()) {
+            return null;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private int toNonNegative(Integer value) {
+        if (value == null || value < 0) {
+            return 0;
+        }
+        return value;
+    }
+
+    private static class ReserveRecord {
+        private Integer wrkNo;
+        private Integer loopNo;
+        private Integer hitStationId;
+        private Long createTime;
+    }
+
     private double calcCurrentLoad(int taskCount, int stationCount) {
         if (stationCount <= 0 || taskCount <= 0) {
             return 0.0;
diff --git a/src/main/java/com/zy/core/enums/RedisKeyType.java b/src/main/java/com/zy/core/enums/RedisKeyType.java
index 755e0d5..516fbc9 100644
--- a/src/main/java/com/zy/core/enums/RedisKeyType.java
+++ b/src/main/java/com/zy/core/enums/RedisKeyType.java
@@ -60,6 +60,7 @@
     CRN_OUT_TASK_COMPLETE_STATION_INFO("crn_out_task_complete_station_info_"),
 
     WATCH_CIRCLE_STATION_("watch_circle_station_"),
+    STATION_CYCLE_LOAD_RESERVE("station_cycle_load_reserve"),
 
     CURRENT_CIRCLE_TASK_CRN_NO("current_circle_task_crn_no_"),
     ASYNC_WMS_IN_TASK_REQUEST("async_wms_in_task_request_"),
diff --git a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
index dffc379..c486e32 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);
                     }
                 }
             }
@@ -495,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();
     }
 
     // 妫�娴嬪嚭搴撴帓搴�
@@ -756,4 +771,322 @@
         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;
+                }
+            }
+        }
+
+        if (config.stationMaxTaskCount > 0 && currentStationTaskCount >= config.stationMaxTaskCount) {
+            News.warn("杈撻�佺珯鐐逛换鍔℃暟閲忚揪鍒颁笂闄愶紝宸插仠姝㈢珯鐐逛换鍔′笅鍙戙�傚綋鍓嶄换鍔℃暟={}锛屼笂闄�={}", currentStationTaskCount, config.stationMaxTaskCount);
+            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);
+
+        String stationMaxTaskCountValue = getConfigValue(systemConfigMap, "stationMaxTaskCountLimit");
+        if (isBlank(stationMaxTaskCountValue)) {
+            stationMaxTaskCountValue = getConfigValue(systemConfigMap, "stationMaxTaskCount");
+        }
+        if (isBlank(stationMaxTaskCountValue)) {
+            stationMaxTaskCountValue = getConfigValue(systemConfigMap, "conveyorStationTaskLimit");
+        }
+        config.stationMaxTaskCount = parseInt(stationMaxTaskCountValue, config.stationMaxTaskCount);
+
+        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;
+        // 绔欑偣鏈�澶т换鍔℃暟閲忎笂闄愶紝0琛ㄧず涓嶉檺鍒�
+        private int stationMaxTaskCount = 30;
+    }
+
+    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;
+        }
+    }
+
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 4504d87..1c25968 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,6 @@
 # 绯荤粺鐗堟湰淇℃伅
 app:
-  version: 1.0.4.5
+  version: 1.0.4.6
   version-type: dev  # prd 鎴� dev
 
 server:

--
Gitblit v1.9.1