Startup.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. using System.Diagnostics;
  2. using Admin.NET.Plugin.AiDOP.ChatBI;
  3. using Admin.NET.Plugin.AiDOP.Entity;
  4. using Admin.NET.Plugin.AiDOP.Entity.S0.Manufacturing;
  5. using Admin.NET.Plugin.AiDOP.Entity.S0.Quality;
  6. using Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
  7. using Admin.NET.Plugin.AiDOP.Entity.S0.Supply;
  8. using Admin.NET.Plugin.AiDOP.Entity.S8;
  9. using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
  10. using Admin.NET.Plugin.AiDOP.Order;
  11. using Admin.NET.Plugin.AiDOP.DataPlatform.Executors;
  12. using Admin.NET.Plugin.AiDOP.DataPlatform.HotWatch;
  13. using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
  14. using Admin.NET.Plugin.AiDOP.Infrastructure;
  15. using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  16. using Admin.NET.Plugin.AiDOP.WorkOrder;
  17. using Microsoft.AspNetCore.Builder;
  18. using Microsoft.AspNetCore.Hosting;
  19. using Microsoft.AspNetCore.Mvc;
  20. using Microsoft.Extensions.DependencyInjection;
  21. using Microsoft.Extensions.Hosting;
  22. using SqlSugar;
  23. namespace Admin.NET.Plugin.AiDOP;
  24. /// <summary>
  25. /// AiDOP 插件启动配置(按需扩展 DI)
  26. /// </summary>
  27. [AppStartup(900)]
  28. public class Startup : AppStartup
  29. {
  30. public void ConfigureServices(IServiceCollection services)
  31. {
  32. services.AddHttpClient();
  33. services.AddHttpClient("AidopChatBI.DeepSeek");
  34. services.AddScoped<DeepSeekChatClient>();
  35. services.AddScoped<ChatBIService>();
  36. services.AddConfigurableOptions<S8ActiveFlowWatchOptions>();
  37. services.AddConfigurableOptions<S8MasterDataOptions>();
  38. services.AddConfigurableOptions<AidopPickBillOptions>();
  39. services.AddConfigurableOptions<AidopStockOptions>();
  40. services.AddConfigurableOptions<AidopInventoryOptions>();
  41. services.AddTransient<IInventoryBalanceReader, InventoryBalanceReader>();
  42. services.AddTransient<IInventoryTransactionReader, StdInventoryTransactionReader>();
  43. // WP9:IPickBillCreator 唯一实现为 Facade(避免 Local/Mes 多实现竞注)
  44. services.AddTransient<IPickBillCreator, PickBillCreatorFacade>();
  45. services.AddScoped<AdoS0ExceptionFilter>();
  46. services.AddScoped<AdoS0ResultFilter>();
  47. services.AddScoped<AdoS0ReferenceChecker>();
  48. services.Configure<MvcOptions>(options =>
  49. {
  50. options.Filters.AddService<AdoS0ExceptionFilter>();
  51. options.Filters.AddService<AdoS0ResultFilter>();
  52. });
  53. // MDP 出站事件驱动:入队 Pulse → 立即推送;Period Job 仅兜底
  54. services.AddHostedService<MdpOutboxPushWorker>();
  55. services.AddHostedService<MdpHotWatchWorker>();
  56. services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.S1Refresh.S1DashboardRebuildWorker>();
  57. services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildWorker>();
  58. services.AddSingleton<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildCapability, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildCapability>();
  59. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildJobStore, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildJobStore>();
  60. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildLock, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildLock>();
  61. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S1ModuleRebuildHandler>();
  62. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S2ModuleRebuildHandler>();
  63. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S3ModuleRebuildHandler>();
  64. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S4ModuleRebuildHandler>();
  65. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S5ModuleRebuildHandler>();
  66. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S6ModuleRebuildHandler>();
  67. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S7ModuleRebuildHandler>();
  68. // S8-DATASET-FOUNDATION-HARDENING-2:S8 取数底座的接口映射显式注册
  69. // (与上方 IModuleRebuildLock 同一先例:本项目不依赖 ITransient 自动按接口注册)。
  70. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetCatalog, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.S8DatasetCatalog>();
  71. // S8-STANDARD-DATASET-HARD-CUTOVER-1:IS8LegacySqlDataProvider 及其实现已物理删除。
  72. // S8-RULE01-PURCHASE-DELIVERY-A:接入第一个真实业务数据集 PURCHASE_DELIVERY。
  73. // Catalog 有定义 + Registry 有实现,Enable Gate 才判 READY:
  74. // 缺定义 → 规则连保存都过不了(dataset_not_defined);
  75. // 缺实现 → 可存草稿但不可启用(dataset_provider_not_registered)。
  76. // 两个接口都必须显式注册(同上,不依赖 ITransient 自动按接口注册)。
  77. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8BusinessDatasetDefinitions>();
  78. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8MonitoringDataProvider, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8PurchaseDeliveryDataProvider>();
  79. // S8-RULE-GOVERNANCE-BATCH1:规则定义目录。业务语义从此只存在于代码里,
  80. // ado_s8_watch_rule 上的同名列降级为 provisioning 维护的只读投影。
  81. //
  82. // 注册为 **Singleton**(数据集目录是 Transient):目录构造期会做完整性校验,
  83. // 而 Transient 的失败形态是「首次解析该服务的那个请求 500」,启动日志干净但目录其实是坏的。
  84. // Singleton + Configure 里强制解析一次 = 定义写错就起不来,见下方 Configure。
  85. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8PurchaseDeliveryRuleDefinitions>();
  86. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleCatalog, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8RuleCatalog>();
  87. // S8-RULE-GOVERNANCE-BATCH2:代码定义 → 每租户运行策略的供给服务。
  88. // ITransient 在本仓不做按接口自动注册,且它没有接口,故显式登记具体类型。
  89. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.S8RuleProvisioningService>();
  90. }
  91. /// <summary>
  92. /// 开发环境下为 Demo 表执行 CodeFirst(表已存在则跳过变更,行为取决于 SqlSugar 配置)。
  93. /// </summary>
  94. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  95. {
  96. // S8-RULE-GOVERNANCE-BATCH1:强制解析一次规则目录,把「定义写错」变成启动失败。
  97. // 刻意**不 try/catch**:重复 rule_code、rule_type 不在词表、TIMEOUT 却没给判定语义,
  98. // 都是发布事故。吞掉它只会让服务带着一个坏目录起来,然后在某条规则跑到那一 tick 时
  99. // 才在后台日志里报错——那时页面上一切正常,没人会看见。
  100. {
  101. var ruleCatalog = app.ApplicationServices
  102. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleCatalog>();
  103. Trace.TraceInformation($"Ai-DOP S8RuleCatalog: {ruleCatalog.Definitions.Count} rule definition(s) loaded");
  104. }
  105. try
  106. {
  107. AidopMenuLinkSync.EnsureLinked(app.ApplicationServices);
  108. }
  109. catch (Exception ex)
  110. {
  111. Trace.TraceWarning("Ai-DOP AidopMenuLinkSync: " + ex);
  112. }
  113. // S8-RULE-GOVERNANCE-BATCH2:启动期对账 —— 确保每个启用租户都拥有全部代码定义规则的运行策略行。
  114. //
  115. // 失败策略沿用本方法内既有的 sync 惯例(AidopMenuLinkSync 等):**记录后放行,不阻断启动**。
  116. // 理由是这段 Configure 与 S0–S7 共用同一个宿主,供给失败不该把整个平台拖下线;
  117. // 且新 provision 的规则一律 disabled,缺行的后果是"该租户暂时看不到这条规则",
  118. // 不是数据错误。异常对象完整落日志,不做静默吞掉 —— 「以为同步成功」比同步失败更危险。
  119. //
  120. // 与之对照:规则目录自身构造失败(重复定义 / 定义写错)**不在此列**,
  121. // 它在上方 GetRequiredService 处直接抛出、阻断启动,因为那是代码包本身有问题。
  122. try
  123. {
  124. using var provisioningScope = app.ApplicationServices.CreateScope();
  125. var provisioning = provisioningScope.ServiceProvider
  126. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.S8RuleProvisioningService>();
  127. var summary = provisioning.SyncAsync().GetAwaiter().GetResult();
  128. Trace.TraceInformation(
  129. $"Ai-DOP S8RuleProvisioning: tenants={summary.TenantCount} definitions={summary.DefinitionCount} "
  130. + $"created={summary.CreatedCount} refreshed={summary.RefreshedCount} unchanged={summary.UnchangedCount} "
  131. + $"orphaned={summary.OrphanedCount} duplicate={summary.DuplicateCount}");
  132. }
  133. catch (Exception ex)
  134. {
  135. Trace.TraceError("Ai-DOP S8RuleProvisioning FAILED(本次启动未完成规则运行策略对账): " + ex);
  136. }
  137. try
  138. {
  139. using var scopeS4 = app.ApplicationServices.CreateScope();
  140. var dbS4 = scopeS4.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  141. dbS4.CodeFirst.InitTables(
  142. typeof(AdoSmartOpsLayoutItem),
  143. typeof(AdoSmartOpsHomeModule),
  144. typeof(AdoSmartOpsKpiMaster),
  145. typeof(AdoSmartOpsDashboardPageConfig),
  146. typeof(AdoSmartOpsDashboardWidget),
  147. typeof(AdoSmartOpsImprovementPlan),
  148. typeof(AdoMdpHotWatch),
  149. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoS1DashboardRebuildJob),
  150. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoModuleDashboardRebuildJob),
  151. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoMdpRebuildLock),
  152. typeof(AdoTenantMenuSuppression));
  153. AidopTenantMigration.MigrateOldTenantId(dbS4);
  154. AidopKpiMasterSeed.EnsureSeed(dbS4);
  155. AidopKpiMasterSeed.EnsureSeed(dbS4, 1300000000888);
  156. var kpiTenantIds = dbS4.Queryable<AdoSmartOpsKpiMaster>()
  157. .Select(x => x.TenantId)
  158. .Distinct()
  159. .ToList();
  160. foreach (var tenantId in kpiTenantIds.Where(x => x > 0))
  161. AidopKpiMasterSeed.EnsureSeed(dbS4, tenantId!.Value);
  162. AidopImprovementApprovalSeed.EnsureSeed(dbS4);
  163. Admin.NET.Plugin.AiDOP.Infrastructure.IqcInspBillFlowSeed.EnsureSeed(dbS4);
  164. Admin.NET.Plugin.AiDOP.Infrastructure.IpqcInspectionFlowSeed.EnsureSeed(dbS4);
  165. Admin.NET.Plugin.AiDOP.Infrastructure.S6ProcessInspectionFlowSeed.EnsureSeed(dbS4);
  166. Admin.NET.Plugin.AiDOP.Infrastructure.FqcInspBillFlowSeed.EnsureSeed(dbS4);
  167. }
  168. catch (Exception ex)
  169. {
  170. Trace.TraceWarning("Ai-DOP CodeFirst/S4 seed: " + ex);
  171. }
  172. if (!env.IsDevelopment())
  173. return;
  174. // 客户/物料/优先级等;远端若已是遗留表(已有主键),CodeFirst 会报 Multiple primary key,不可让启动失败。
  175. try
  176. {
  177. using var scope = app.ApplicationServices.CreateScope();
  178. var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  179. db.CodeFirst.InitTables(
  180. typeof(AdoS0CustMaster),
  181. typeof(AdoS0ItemMaster),
  182. typeof(AdoS0ItemSubstituteDetail),
  183. typeof(AdoS0PriorityCode),
  184. typeof(AdoS0ProductStructureOp),
  185. typeof(AdoS0StdOpMaster),
  186. typeof(AdoS0LineMaster),
  187. typeof(AdoS0MfgRoutingOpDetail),
  188. typeof(AdoS0MfgPersonSkillAssignment),
  189. typeof(AdoS0MfgWorkOrderControl),
  190. typeof(AdoS0MfgPersonSkill),
  191. typeof(AdoS0MfgLinePost),
  192. typeof(AdoS0MfgWorkCenter),
  193. typeof(AdoS0MfgLineMaterial),
  194. typeof(AdoS0ProcessFlowCard),
  195. typeof(AdoS0OrderScheduleCycle),
  196. typeof(AdoS0MfgElementParam),
  197. typeof(AdoS0MfgMaterialProcessElement),
  198. typeof(AdoS0MfgPreprocessElement),
  199. typeof(AdoS0MfgPreprocessElementParam),
  200. typeof(AdoS0ImageType),
  201. typeof(AdoS0QualitySegmentImage),
  202. typeof(AdoS0QmsQualityBaseType),
  203. typeof(AdoS0QmsFqcInspectionSpec),
  204. typeof(AdoS0QmsFqcInspectionSpecEntry),
  205. typeof(AdoS0QmsOqcInspectionSpec),
  206. typeof(AdoS0QmsOqcInspectionSpecEntry),
  207. typeof(AdoS0CategoryLeadTime),
  208. typeof(AdoS0MaterialPlanCycle),
  209. typeof(AdoS0SrmPurchase),
  210. typeof(AdoS8Exception),
  211. typeof(AdoS8ExceptionTimeline),
  212. typeof(AdoS8DecisionRecord),
  213. typeof(AdoS8Evidence),
  214. // S8-STANDARD-DATASET-HARD-CUTOVER-1:AdoS8AlertRule 已物理删除
  215. // (运行时权威一直是 AdoS8WatchRule;该表零 Runtime Consumer)。
  216. typeof(AdoS8SceneConfig),
  217. typeof(AdoS8NotificationLayer),
  218. typeof(AdoS8WatchRule),
  219. typeof(AdoS8RolePermissionConfig),
  220. typeof(AdoS8NotificationLog),
  221. typeof(AdoS8DashboardCellConfig),
  222. typeof(AdoS8ExceptionType),
  223. typeof(AdoS8DetectionLog),
  224. typeof(AdoS8RuleDetectionState),
  225. typeof(AdoS8Dimension),
  226. typeof(AdoS8DimensionNode),
  227. typeof(AdoS8ConfigDraft),
  228. typeof(AdoS8MonitorObject),
  229. typeof(AdoS8MonitorMetric),
  230. typeof(AdoS8OrderFlowOrder),
  231. typeof(AdoS8OrderFlowStage),
  232. typeof(AdoS8OrderFlowSnapshot),
  233. typeof(AdoS8OrderFlowSubstep),
  234. typeof(AdoS8OrderFlowSubstepUnit),
  235. typeof(AdoS8OrderFlowProcurementPivot),
  236. typeof(ContractReview),
  237. typeof(ContractReviewFlow),
  238. typeof(ProductDesign),
  239. typeof(ProductDesignBom),
  240. typeof(ProductDesignRouting)
  241. );
  242. AidopDimensionSeed.EnsureSeed(db);
  243. AidopMonitorDictionarySeed.EnsureSeed(db);
  244. }
  245. catch (Exception ex)
  246. {
  247. Trace.TraceWarning("Ai-DOP Development CodeFirst (S0/S8/Order demo tables): " + ex);
  248. }
  249. }
  250. }