wanshenmean
2026-03-09 2df3b287a5c868f987b99ff00dc1d2339747b8f1
Merge branch 'feature/multi-outbound-address-roundrobin' - 实现多出库口轮询功能
已添加1个文件
已修改5个文件
188 ■■■■■ 文件已修改
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/RoundRobinService.cs 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/docs/plans/2026-03-09-multi-outbound-address-roundrobin-design.md 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/AutoOutboundTaskOptions.cs
@@ -18,13 +18,13 @@
        public int CheckIntervalSeconds { get; set; } = 300;
        /// <summary>
        /// æŒ‰å··é“前缀配置目标地址
        /// æŒ‰å··é“前缀配置目标地址(支持多出库口)
        /// </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" } }
            };
    }
}
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];
        }
    }
}
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>
        /// æ ¹æ®å··é“确定目标地址
        /// æ ¹æ®å··é“确定目标地址(支持多出库口轮询)
        /// </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>();
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();//����
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", //当前项目的主库,所对应的连接字符串的Enabled必须为true
    //连接字符串
    //"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",
    //旧WMS数据库连接
    //"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时,开放所有IP均可访问。
        // æ”¯æŒå¤šä¸ªåŸŸåç«¯å£ï¼Œæ³¨æ„ç«¯å£å·åŽä¸è¦å¸¦/斜杆:比如localhost:8000/,是错的
        // æ³¨æ„ï¼Œhttp://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"]
        }
    }
  },
  "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", //当前项目的主库,所对应的连接字符串的Enabled必须为true
  //连接字符串
  //"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",
  //旧WMS数据库连接
  //"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时,开放所有IP均可访问。
    // æ”¯æŒå¤šä¸ªåŸŸåç«¯å£ï¼Œæ³¨æ„ç«¯å£å·åŽä¸è¦å¸¦/斜杆:比如localhost:8000/,是错的
    // æ³¨æ„ï¼Œhttp://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"
    }
  }
}
}
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
**状态**: ä»£ç å®žçŽ°å®Œæˆï¼Œå¾…æ‰‹åŠ¨æµ‹è¯•