Merge branch 'feature/multi-outbound-address-roundrobin' - 实现多出库口轮询功能
| | |
| | | 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" } } |
| | | }; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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]; |
| | | } |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | private readonly ILocationInfoService _locationInfoService; |
| | | private readonly HttpClientHelper _httpClientHelper; |
| | | private readonly IConfiguration _configuration; |
| | | private readonly RoundRobinService _roundRobinService; |
| | | |
| | | public IRepository<Dt_Task> Repository => BaseDal; |
| | | |
| | |
| | | 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é»è¾å¤ç |
| | |
| | | } |
| | | |
| | | /// <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> |
| | |
| | | |
| | | // 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>(); |
| | |
| | | // 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();//���� |
| | |
| | | { |
| | | "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" |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | ## 宿½æ¸
å |
| | | |
| | | - [ ] ä¿®æ¹ `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 |
| | | **ç¶æ**: 代ç å®ç°å®æï¼å¾
æå¨æµè¯ |