From 2df3b287a5c868f987b99ff00dc1d2339747b8f1 Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期一, 09 三月 2026 09:48:13 +0800
Subject: [PATCH] Merge branch 'feature/multi-outbound-address-roundrobin' - 实现多出库口轮询功能

---
 Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs                      |    8 +-
 Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/RoundRobinService.cs                            |   42 ++++++++++
 Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs                            |   37 +++++++--
 Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs                                      |    1 
 Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json                                |   80 ++++++++++----------
 Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md |   20 +++--
 6 files changed, 128 insertions(+), 60 deletions(-)

diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs
index 3822f2e..a619a6a 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs
@@ -18,13 +18,13 @@
         public int CheckIntervalSeconds { get; set; } = 300;
 
         /// <summary>
-        /// 鎸夊贩閬撳墠缂�閰嶇疆鐩爣鍦板潃
+        /// 鎸夊贩閬撳墠缂�閰嶇疆鐩爣鍦板潃锛堟敮鎸佸鍑哄簱鍙o級
         /// </summary>
-        public Dictionary<string, string> TargetAddresses { get; set; }
+        public Dictionary<string, List<string>> TargetAddresses { get; set; }
             = new()
             {
-                { "GW", "10081" },
-                { "CW", "10080" }
+                { "GW", new List<string> { "10081" } },
+                { "CW", new List<string> { "10080" } }
             };
     }
 }
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/RoundRobinService.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/RoundRobinService.cs
new file mode 100644
index 0000000..24d8c58
--- /dev/null
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/RoundRobinService.cs
@@ -0,0 +1,42 @@
+using System.Collections.Concurrent;
+
+namespace WIDESEA_Core.Core
+{
+    /// <summary>
+    /// 杞鏈嶅姟 - 绾跨▼瀹夊叏鐨勫湴鍧�杞閫夋嫨
+    /// </summary>
+    public class RoundRobinService
+    {
+        /// <summary>
+        /// 杞璁℃暟鍣� - key: 宸烽亾鍓嶇紑, value: 褰撳墠绱㈠紩
+        /// </summary>
+        private readonly ConcurrentDictionary<string, int> _counters = new();
+
+        /// <summary>
+        /// 鑾峰彇涓嬩竴涓湴鍧�锛堣疆璇級
+        /// </summary>
+        /// <param name="key">宸烽亾鍓嶇紑鏍囪瘑</param>
+        /// <param name="addresses">鍊欓�夊湴鍧�鍒楄〃</param>
+        /// <returns>閫変腑鐨勭洰鏍囧湴鍧�</returns>
+        public string GetNextAddress(string key, List<string> addresses)
+        {
+            // 绌哄垪琛ㄦ鏌�
+            if (addresses == null || addresses.Count == 0)
+                return "10080";
+
+            // 鍗曚釜鍦板潃锛岀洿鎺ヨ繑鍥�
+            if (addresses.Count == 1)
+                return addresses[0];
+
+            // 澶氫釜鍦板潃锛屼娇鐢ㄨ疆璇㈤�夋嫨
+            // AddOrUpdate 鏄師瀛愭搷浣滐紝绾跨▼瀹夊叏
+            int index = _counters.AddOrUpdate(
+                key,
+                0,                                      // 棣栨浣跨敤锛屼粠 0 寮�濮�
+                (k, oldValue) => (oldValue + 1) % addresses.Count  // 杞锛氶�掑鍚庡彇妯�
+            );
+
+            return addresses[index];
+        }
+    }
+}
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
index 4b23b23..d49b8e8 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -8,6 +8,7 @@
 using WIDESEA_Core;
 using WIDESEA_Core.BaseRepository;
 using WIDESEA_Core.BaseServices;
+using WIDESEA_Core.Core;
 using WIDESEA_DTO;
 using WIDESEA_DTO.Task;
 using WIDESEA_IBasicService;
@@ -24,6 +25,7 @@
         private readonly ILocationInfoService _locationInfoService;
         private readonly HttpClientHelper _httpClientHelper;
         private readonly IConfiguration _configuration;
+        private readonly RoundRobinService _roundRobinService;
 
         public IRepository<Dt_Task> Repository => BaseDal;
 
@@ -42,13 +44,15 @@
             IStockInfoService stockInfoService,
             ILocationInfoService locationInfoService,
             HttpClientHelper httpClientHelper,
-            IConfiguration configuration) : base(BaseDal)
+            IConfiguration configuration,
+            RoundRobinService roundRobinService) : base(BaseDal)
         {
             _mapper = mapper;
             _stockInfoService = stockInfoService;
             _locationInfoService = locationInfoService;
             _httpClientHelper = httpClientHelper;
             _configuration = configuration;
+            _roundRobinService = roundRobinService;
         }
 
         #region WCS閫昏緫澶勭悊
@@ -381,20 +385,37 @@
         }
 
         /// <summary>
-        /// 鏍规嵁宸烽亾纭畾鐩爣鍦板潃
+        /// 鏍规嵁宸烽亾纭畾鐩爣鍦板潃锛堟敮鎸佸鍑哄簱鍙h疆璇級
         /// </summary>
-        private string DetermineTargetAddress(string roadway, Dictionary<string, string> addressMap)
+        private string DetermineTargetAddress(string roadway, Dictionary<string, List<string>> addressMap)
         {
             if (string.IsNullOrWhiteSpace(roadway))
-                return "10080"; // 榛樿鍦板潃
+                return "10080";
 
+            // 鏌ユ壘鍖归厤鐨勫贩閬撳墠缂�
+            string matchedPrefix = null;
             foreach (var kvp in addressMap)
             {
                 if (roadway.Contains(kvp.Key))
-                    return kvp.Value;
+                {
+                    matchedPrefix = kvp.Key;
+                    break;
+                }
             }
 
-            return "10080"; // 榛樿鍦板潃
+            if (matchedPrefix == null)
+                return "10080";
+
+            var addresses = addressMap[matchedPrefix];
+            if (addresses == null || addresses.Count == 0)
+                return "10080";
+
+            // 鍗曚釜鍦板潃锛岀洿鎺ヨ繑鍥�
+            if (addresses.Count == 1)
+                return addresses[0];
+
+            // 澶氫釜鍦板潃锛屼娇鐢ㄨ疆璇㈡湇鍔�
+            return _roundRobinService.GetNextAddress(matchedPrefix, addresses);
         }
 
         /// <summary>
@@ -473,8 +494,8 @@
 
                 // 4. 鑾峰彇閰嶇疆鐨勭洰鏍囧湴鍧�鏄犲皠
                 var targetAddressMap = _configuration.GetSection("AutoOutboundTask:TargetAddresses")
-                    .Get<Dictionary<string, string>>()
-                    ?? new Dictionary<string, string>();
+                    .Get<Dictionary<string, List<string>>>()
+                    ?? new Dictionary<string, List<string>>();
 
                 // 5. 鎵归噺鍒涘缓浠诲姟
                 var taskList = new List<Dt_Task>();
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
index 32e4349..815fd7b 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
@@ -42,6 +42,7 @@
 // 2锟斤拷锟斤拷锟矫凤拷锟斤拷
 builder.Services.AddSingleton(new AppSettings(builder.Configuration));//注锟斤拷
 builder.Services.AddAllOptionRegister();//锟斤拷取锟斤拷锟斤拷锟侥硷拷
+builder.Services.AddSingleton<RoundRobinService>();
 builder.Services.Configure<AutoOutboundTaskOptions>(
     builder.Configuration.GetSection("AutoOutboundTask"));
 builder.Services.AddMemoryCacheSetup();//锟斤拷锟斤拷
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
index db224ba..e334059 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -1,42 +1,42 @@
 {
-  "urls": "http://*:9291", //web鏈嶅姟绔彛锛屽鏋滅敤IIS閮ㄧ讲锛屾妸杩欎釜鍘绘帀
-  "Logging": {
-    "LogLevel": {
-      "Default": "Information",
-      "Microsoft.AspNetCore": "Warning"
+    "urls": "http://*:9291", //web鏈嶅姟绔彛锛屽鏋滅敤IIS閮ㄧ讲锛屾妸杩欎釜鍘绘帀
+    "Logging": {
+        "LogLevel": {
+            "Default": "Information",
+            "Microsoft.AspNetCore": "Warning"
+        }
+    },
+    "dics": "inOrderType,outOrderType,inboundState,createType,enableEnum,enableStatusEnum,locationStatusEnum,locationTypeEnum,taskTypeEnum,taskStatusEnum,outboundStatusEnum,orderDetailStatusEnum,stockStatusEmun,stockChangeType,outStockStatus,receiveOrderTypeEnum,authorityScope,authorityScopes,locationChangeType,warehouses,suppliers,taskType,receiveStatus,purchaseType,purchaseOrderStatus,printStatus",
+    "AllowedHosts": "*",
+    "ConnectionStringsEncryption": false,
+    "MainDB": "DB_WIDESEA", //褰撳墠椤圭洰鐨勪富搴擄紝鎵�瀵瑰簲鐨勮繛鎺ュ瓧绗︿覆鐨凟nabled蹇呴』涓簍rue
+    //杩炴帴瀛楃涓�
+    //"ConnectionString": "HTI6FB1H05Krd07mNm9yBCNhofW6edA5zLs9TY~MNthRYW3kn0qKbMIsGp~3yyPDF1YZUCPBQx8U0Jfk4PH~ajNFXVIwlH85M3F~v_qKYQ3CeAz3q1mLVDn8O5uWt1~3Ut2V3KRkEwYHvW2oMDN~QIDXPxDgXN0R2oTIhc9dNu7QNaLEknblqmHhjaNSSpERdDVZIgHnMKejU_SL49tralBkZmDNi0hmkbL~837j1NWe37u9fJKmv91QPb~16JsuI9uu0EvNZ06g6PuZfOSAeFH9GMMIZiketdcJG3tHelo=",
+    "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
+    //"ConnectionString": "Data Source=10.30.4.92;Initial Catalog=WMS_TC;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
+    //鏃MS鏁版嵁搴撹繛鎺�
+    //"TeConnectionString": "Data Source=10.30.4.92;Initial Catalog=TeChuang;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
+    //璺ㄥ煙
+    "Cors": {
+        "PolicyName": "CorsIpAccess", //绛栫暐鍚嶇О
+        "EnableAllIPs": true, //褰撲负true鏃讹紝寮�鏀炬墍鏈塈P鍧囧彲璁块棶銆�
+        // 鏀寔澶氫釜鍩熷悕绔彛锛屾敞鎰忕鍙e彿鍚庝笉瑕佸甫/鏂滄潌锛氭瘮濡俵ocalhost:8000/锛屾槸閿欑殑
+        // 娉ㄦ剰锛宧ttp://127.0.0.1:1818 鍜� http://localhost:1818 鏄笉涓�鏍风殑
+        "IPs": "http://127.0.0.1:8080,http://localhost:8080,http://127.0.0.1:8081,http://localhost:8081"
+    },
+    "LogAopEnable": false,
+    "PrintSql": false, //鎵撳嵃SQL璇彞
+    "ApiName": "WIDESEA",
+    "ExpMinutes": 120,
+    "DBSeedEnable": false,
+    "PDAVersion": "4",
+    "WebSocketPort": 9296,
+    "AutoOutboundTask": {
+        "Enable": true, /// 鏄惁鍚敤鑷姩鍑哄簱浠诲姟
+        "CheckIntervalSeconds": 300, /// 妫�鏌ラ棿闅旓紙绉掞級
+        "TargetAddresses": {    /// 鎸夊贩閬撳墠缂�閰嶇疆鐩爣鍦板潃锛堟敮鎸佸鍑哄簱鍙o級
+            "GW": ["10081"],
+            "CW": ["10080"]
+        }
     }
-  },
-  "dics": "inOrderType,outOrderType,inboundState,createType,enableEnum,enableStatusEnum,locationStatusEnum,locationTypeEnum,taskTypeEnum,taskStatusEnum,outboundStatusEnum,orderDetailStatusEnum,stockStatusEmun,stockChangeType,outStockStatus,receiveOrderTypeEnum,authorityScope,authorityScopes,locationChangeType,warehouses,suppliers,taskType,receiveStatus,purchaseType,purchaseOrderStatus,printStatus",
-  "AllowedHosts": "*",
-  "ConnectionStringsEncryption": false,
-  "MainDB": "DB_WIDESEA", //褰撳墠椤圭洰鐨勪富搴擄紝鎵�瀵瑰簲鐨勮繛鎺ュ瓧绗︿覆鐨凟nabled蹇呴』涓簍rue
-  //杩炴帴瀛楃涓�
-  //"ConnectionString": "HTI6FB1H05Krd07mNm9yBCNhofW6edA5zLs9TY~MNthRYW3kn0qKbMIsGp~3yyPDF1YZUCPBQx8U0Jfk4PH~ajNFXVIwlH85M3F~v_qKYQ3CeAz3q1mLVDn8O5uWt1~3Ut2V3KRkEwYHvW2oMDN~QIDXPxDgXN0R2oTIhc9dNu7QNaLEknblqmHhjaNSSpERdDVZIgHnMKejU_SL49tralBkZmDNi0hmkbL~837j1NWe37u9fJKmv91QPb~16JsuI9uu0EvNZ06g6PuZfOSAeFH9GMMIZiketdcJG3tHelo=",
-  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
-  //"ConnectionString": "Data Source=10.30.4.92;Initial Catalog=WMS_TC;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
-  //鏃MS鏁版嵁搴撹繛鎺�
-  //"TeConnectionString": "Data Source=10.30.4.92;Initial Catalog=TeChuang;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
-  //璺ㄥ煙
-  "Cors": {
-    "PolicyName": "CorsIpAccess", //绛栫暐鍚嶇О
-    "EnableAllIPs": true, //褰撲负true鏃讹紝寮�鏀炬墍鏈塈P鍧囧彲璁块棶銆�
-    // 鏀寔澶氫釜鍩熷悕绔彛锛屾敞鎰忕鍙e彿鍚庝笉瑕佸甫/鏂滄潌锛氭瘮濡俵ocalhost:8000/锛屾槸閿欑殑
-    // 娉ㄦ剰锛宧ttp://127.0.0.1:1818 鍜� http://localhost:1818 鏄笉涓�鏍风殑
-    "IPs": "http://127.0.0.1:8080,http://localhost:8080,http://127.0.0.1:8081,http://localhost:8081"
-  },
-  "LogAopEnable": false,
-  "PrintSql": false, //鎵撳嵃SQL璇彞
-  "ApiName": "WIDESEA",
-  "ExpMinutes": 120,
-  "DBSeedEnable": false,
-  "PDAVersion": "4",
-  "WebSocketPort": 9296,
-  "AutoOutboundTask": {
-    "Enable": true, /// 鏄惁鍚敤鑷姩鍑哄簱浠诲姟
-    "CheckIntervalSeconds": 300, /// 妫�鏌ラ棿闅旓紙绉掞級
-    "TargetAddresses": {    /// 鎸夊贩閬撳墠缂�閰嶇疆鐩爣鍦板潃
-      "GW": "10081",
-      "CW": "10080"
-    }
-  }
-}
\ No newline at end of file
+}
diff --git a/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md b/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md
index d0dfc6c..0e6e2af 100644
--- a/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md
+++ b/Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md
@@ -313,11 +313,15 @@
 
 ## 瀹炴柦娓呭崟
 
-- [ ] 淇敼 `AutoOutboundTaskOptions.TargetAddresses` 绫诲瀷
-- [ ] 鍒涘缓 `RoundRobinService` 绫�
-- [ ] 淇敼 `TaskService.DetermineTargetAddress` 鏂规硶
-- [ ] 鍦� `Program.cs` 娉ㄥ唽 `RoundRobinService`
-- [ ] 鏇存柊 `appsettings.json` 閰嶇疆绀轰緥
-- [ ] 缂栬瘧楠岃瘉
-- [ ] 鎵嬪姩娴嬭瘯
-- [ ] 鏇存柊璁捐鏂囨。
+- [x] 淇敼 `AutoOutboundTaskOptions.TargetAddresses` 绫诲瀷
+- [x] 鍒涘缓 `RoundRobinService` 绫�
+- [x] 淇敼 `TaskService.DetermineTargetAddress` 鏂规硶
+- [x] 鍦� `Program.cs` 娉ㄥ唽 `RoundRobinService`
+- [x] 鏇存柊 `appsettings.json` 閰嶇疆绀轰緥
+- [x] 缂栬瘧楠岃瘉
+- [ ] 鎵嬪姩娴嬭瘯锛堥渶瑕佽繍琛岀幆澧冨拰鏁版嵁搴擄級
+- [x] 鏇存柊璁捐鏂囨。
+
+**瀹炴柦鏃ユ湡**: 2026-03-09
+**瀹炴柦浜�**: Claude Code
+**鐘舵��**: 浠g爜瀹炵幇瀹屾垚锛屽緟鎵嬪姩娴嬭瘯

--
Gitblit v1.9.3