using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Configuration; using Polly; using Polly.Extensions.Http; using Prism.Ioc; using Prism.Modularity; using System.Net.Http; using System.Text; using YY.Admin.Core.Services; using YY.Admin.Core.Session; using YY.Admin.EventBus; using YY.Admin.Infrastructure.Hubs; using YY.Admin.Infrastructure.Network; using YY.Admin.Infrastructure.Storage; using YY.Admin.Infrastructure.Sync; using YY.Admin.Services.Service; using YY.Admin.Services.Service.Category; using YY.Admin.Services.Service.Customer; using YY.Admin.Services.Service.Dict; using YY.Admin.Services.Service.MixerMaterialTareStrategy; using YY.Admin.Services.Service.MixerMaterial; using YY.Admin.Services.Service.Supplier; using YY.Admin.Services.Service.RawMaterialCard; using YY.Admin.Services.Service.RawMaterialEntry; using YY.Admin.Services.Service.Vehicle; using YY.Admin.Services.Service.Warehouse; using YY.Admin.Services.Service.WarehouseArea; using YY.Admin.Services.Service.WeightRecord; using YY.Admin.Services.Service.Print; namespace YY.Admin.Module; public class SyncModule : IModule { public void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 本地用户事件订阅器:将增删改操作入 Outbox 回传后端 containerRegistry.RegisterSingleton(); // 车辆管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 客户管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 供应商管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 磅单管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 密炼物料信息:API直连 + STOMP实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 原料入场记录:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 原材料卡片:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 仓库数据只读缓存(供库区等模块筛选使用,不含 CRUD 页面) containerRegistry.RegisterSingleton(); // 库区管理:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 密炼物料皮重策略:免密 API 直连 + STOMP 实时通知 containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); // 分类字典:启动同步 + 断线重连补刷 containerRegistry.RegisterSingleton(); // 数据字典:启动同步 + 断线重连补刷 containerRegistry.RegisterSingleton(); // 统一轮询管理器(修改 SyncPollManager.PollInterval 即可调整所有模块的轮询间隔) containerRegistry.RegisterSingleton(); // 打印服务:PrintDot 桥接器 + 打印模板(含 STOMP 实时同步 + 本地缓存) containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); containerRegistry.RegisterSingleton(); var serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); serviceCollection.AddHttpClient("JeecgApi", (sp, client) => { var config = containerRegistry.GetContainer().Resolve(); var baseUrl = config.GetValue("JeecgIntegration:BaseUrl")?.TrimEnd('/'); if (!string.IsNullOrWhiteSpace(baseUrl)) { client.BaseAddress = new Uri(baseUrl); } var tokenStore = containerRegistry.GetContainer().Resolve(); var token = tokenStore.GetTokenAsync(default).ConfigureAwait(false).GetAwaiter().GetResult(); if (!string.IsNullOrWhiteSpace(token)) { client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); } client.DefaultRequestHeaders.TryAddWithoutValidation("X-Platform", "desktop"); // .NET HttpClient 要求请求头值为 ASCII;主机名/中文姓名否则会报 Request headers must contain only ASCII characters client.DefaultRequestHeaders.TryAddWithoutValidation("X-Host-Name", ToAsciiHttpHeaderValue(Environment.MachineName)); var currentUser = AppSession.CurrentUser; client.DefaultRequestHeaders.TryAddWithoutValidation("X-User-Name", ToAsciiHttpHeaderValue(currentUser?.Account ?? "unknown")); client.DefaultRequestHeaders.TryAddWithoutValidation("X-Real-Name", ToAsciiHttpHeaderValue(currentUser?.RealName ?? "")); }).AddPolicyHandler(GetRetryPolicy()) .AddHttpMessageHandler(); var provider = serviceCollection.BuildServiceProvider(); var httpClientFactory = provider.GetRequiredService(); containerRegistry.RegisterInstance(httpClientFactory); } public void OnInitialized(IContainerProvider containerProvider) { var networkMonitor = containerProvider.Resolve(); var outboxProcessor = containerProvider.Resolve(); var signalService = containerProvider.Resolve(); _ = networkMonitor.StartAsync(CancellationToken.None); _ = outboxProcessor.StartConsumerAsync(CancellationToken.None); // 用户镜像 + 设备指令:统一 STOMP(/ws/device),免密与设备 Token 模式均启动 _ = Task.Run(() => signalService.ConnectUnifiedDeviceChannelAsync(CancellationToken.None)); // 强制实例化事件订阅器(单例,构造函数内完成订阅注册) _ = containerProvider.Resolve(); // 统一轮询管理器必须在各协调器之前实例化,确保 Register() 能正常接收注册 _ = containerProvider.Resolve(); // 强制实例化车辆同步协调器(构造函数内订阅 STOMP 车辆变更事件) _ = containerProvider.Resolve(); // 强制实例化客户同步协调器 _ = containerProvider.Resolve(); // 强制实例化供应商同步协调器 _ = containerProvider.Resolve(); // 强制实例化磅单同步协调器 _ = containerProvider.Resolve(); // 强制实例化密炼物料同步协调器 _ = containerProvider.Resolve(); // 强制实例化原料入场记录同步协调器 _ = containerProvider.Resolve(); // 强制实例化原材料卡片同步协调器 _ = containerProvider.Resolve(); // 强制实例化库区同步协调器 _ = containerProvider.Resolve(); // 强制实例化密炼物料皮重策略同步协调器 _ = containerProvider.Resolve(); // 强制实例化分类字典同步协调器 _ = containerProvider.Resolve(); // 强制实例化数据字典同步协调器 _ = containerProvider.Resolve(); // 强制实例化打印模板同步协调器 _ = containerProvider.Resolve(); _ = containerProvider.Resolve(); } private static IAsyncPolicy GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); } /// /// 将自定义请求头转为 ASCII(含非 ASCII 时整体做 UTF-8 Base64,前缀 B64. 便于后端按需解码)。 /// private static string ToAsciiHttpHeaderValue(string? value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } foreach (var c in value) { if (c > 127) { return "B64." + Convert.ToBase64String(Encoding.UTF8.GetBytes(value)); } } return value; } }