Startup.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  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.DataPlatform.Inbound;
  14. using Microsoft.AspNetCore.Authentication;
  15. using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
  16. using Admin.NET.Plugin.AiDOP.Infrastructure;
  17. using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  18. using Admin.NET.Plugin.AiDOP.WorkOrder;
  19. using Microsoft.AspNetCore.Builder;
  20. using Microsoft.AspNetCore.Hosting;
  21. using Microsoft.AspNetCore.Mvc;
  22. using Microsoft.Extensions.DependencyInjection;
  23. using Microsoft.Extensions.Hosting;
  24. using SqlSugar;
  25. namespace Admin.NET.Plugin.AiDOP;
  26. /// <summary>
  27. /// AiDOP 插件启动配置(按需扩展 DI)
  28. /// </summary>
  29. [AppStartup(900)]
  30. public class Startup : AppStartup
  31. {
  32. public void ConfigureServices(IServiceCollection services)
  33. {
  34. services.AddHttpClient();
  35. services.AddHttpClient("AidopChatBI.DeepSeek");
  36. services.AddScoped<DeepSeekChatClient>();
  37. services.AddScoped<ChatBIService>();
  38. services.AddConfigurableOptions<S8ActiveFlowWatchOptions>();
  39. services.AddConfigurableOptions<S8MasterDataOptions>();
  40. services.AddConfigurableOptions<AidopPickBillOptions>();
  41. services.AddConfigurableOptions<AidopStockOptions>();
  42. services.AddConfigurableOptions<AidopInventoryOptions>();
  43. services.AddConfigurableOptions<AidopPoCompletionOptions>();
  44. services.AddConfigurableOptions<AidopOutboundOptions>();
  45. services.AddConfigurableOptions<AidopBomUploadOptions>();
  46. services.AddTransient<IInventoryBalanceReader, InventoryBalanceReader>();
  47. services.AddTransient<IInventoryTransactionReader, StdInventoryTransactionReader>();
  48. // WP9:IPickBillCreator 唯一实现为 Facade(避免 Local/Mes 多实现竞注)
  49. services.AddTransient<IPickBillCreator, PickBillCreatorFacade>();
  50. services.AddScoped<AdoS0ExceptionFilter>();
  51. services.AddScoped<AdoS0ResultFilter>();
  52. services.AddScoped<AdoS0ReferenceChecker>();
  53. services.Configure<MvcOptions>(options =>
  54. {
  55. options.Filters.AddService<AdoS0ExceptionFilter>();
  56. options.Filters.AddService<AdoS0ResultFilter>();
  57. });
  58. // ETL 执行机注册与心跳:AidopJobGate 的数据来源,必须常开且不受闸门管辖
  59. // (被闸门关掉会死锁:没人心跳 → 没人发布指派 → 永远不是执行机)
  60. services.AddHostedService<Admin.NET.Plugin.AiDOP.Infrastructure.EtlInstanceRegistrar>();
  61. // MDP 出站事件驱动:入队 Pulse → 立即推送;Period Job 仅兜底
  62. services.AddHostedService<MdpOutboxPushWorker>();
  63. services.AddHostedService<MdpHotWatchWorker>();
  64. services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.Schema.MdpSourceIdentityBackfillWorker>();
  65. services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.S1Refresh.S1DashboardRebuildWorker>();
  66. services.AddHostedService<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildWorker>();
  67. services.AddSingleton<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildCapability, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildCapability>();
  68. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildJobStore, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildJobStore>();
  69. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildLock, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.ModuleRebuildLock>();
  70. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S1ModuleRebuildHandler>();
  71. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S2ModuleRebuildHandler>();
  72. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S3ModuleRebuildHandler>();
  73. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S4ModuleRebuildHandler>();
  74. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S5ModuleRebuildHandler>();
  75. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S6ModuleRebuildHandler>();
  76. services.AddTransient<Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.IModuleRebuildHandler, Admin.NET.Plugin.AiDOP.DataPlatform.MdpRebuild.S7ModuleRebuildHandler>();
  77. // S8-DATASET-FOUNDATION-HARDENING-2:S8 取数底座的接口映射显式注册
  78. // (与上方 IModuleRebuildLock 同一先例:本项目不依赖 ITransient 自动按接口注册)。
  79. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetCatalog, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.S8DatasetCatalog>();
  80. // S8-STANDARD-DATASET-HARD-CUTOVER-1:IS8LegacySqlDataProvider 及其实现已物理删除。
  81. // S8-RULE01-PURCHASE-DELIVERY-A:接入第一个真实业务数据集 PURCHASE_DELIVERY。
  82. // Catalog 有定义 + Registry 有实现,Enable Gate 才判 READY:
  83. // 缺定义 → 规则连保存都过不了(dataset_not_defined);
  84. // 缺实现 → 可存草稿但不可启用(dataset_provider_not_registered)。
  85. // 两个接口都必须显式注册(同上,不依赖 ITransient 自动按接口注册)。
  86. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8BusinessDatasetDefinitions>();
  87. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8MonitoringDataProvider, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8PurchaseDeliveryDataProvider>();
  88. // S8-RULE02-ORDER-DELIVERY-WARNING-1:第二个真实业务数据集 ORDER_DELIVERY_RISK。
  89. // 与上面同理,定义与实现两个接口都要显式注册;两者的 DatasetCode 必须逐字一致,
  90. // 否则 Enable Gate 会以 dataset_provider_not_registered 挡住启用。
  91. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8OrderDeliveryDatasetDefinitions>();
  92. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8MonitoringDataProvider, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8OrderDeliveryDataProvider>();
  93. // S8-RULE03-MATERIAL-SHORTAGE:第三个真实业务数据集 MFG_MATERIAL_SHORTAGE。
  94. // 同上:定义与实现两个接口都要显式注册,DatasetCode 必须逐字一致
  95. // (规则定义侧已改为编译期引用本数据集常量,不再各写一份字面量)。
  96. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8DatasetDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8MaterialShortageDatasetDefinitions>();
  97. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.IS8MonitoringDataProvider, Admin.NET.Plugin.AiDOP.Service.S8.Rules.DataAccess.Providers.S8MaterialShortageDataProvider>();
  98. // S8-RULE-GOVERNANCE-BATCH1:规则定义目录。业务语义从此只存在于代码里,
  99. // ado_s8_watch_rule 上的同名列降级为 provisioning 维护的只读投影。
  100. //
  101. // 注册为 **Singleton**(数据集目录是 Transient):目录构造期会做完整性校验,
  102. // 而 Transient 的失败形态是「首次解析该服务的那个请求 500」,启动日志干净但目录其实是坏的。
  103. // Singleton + Configure 里强制解析一次 = 定义写错就起不来,见下方 Configure。
  104. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8PurchaseDeliveryRuleDefinitions>();
  105. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8OrderDeliveryRuleDefinitions>();
  106. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleDefinitionSource, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8MaterialShortageRuleDefinitions>();
  107. services.AddSingleton<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleCatalog, Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.S8RuleCatalog>();
  108. // S8-RULE-GOVERNANCE-BATCH2:代码定义 → 每租户运行策略的供给服务。
  109. // ITransient 在本仓不做按接口自动注册,且它没有接口,故显式登记具体类型。
  110. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.S8RuleProvisioningService>();
  111. // S8-ACTION-PERMISSION-1:异常动作授权矩阵的首次供给(同 S8RuleProvisioningService,无接口需显式登记)。
  112. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.S8ExceptionActionRoleProvisioningService>();
  113. // S8-UAT-APPROVAL-REPAIR-1:S8 审批流审批人配置一次性对账(只修解析为 0 人的节点)。
  114. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S8.S8ApprovalFlowRepairService>();
  115. // API_INBOUND:插件侧独立签名 scheme(严禁传入默认 scheme 参数,全局默认仍是 JWT)
  116. services.AddAuthentication()
  117. .AddScheme<InboundSignatureOptions, InboundSignatureHandler>(
  118. InboundSignatureDefaults.AuthenticationScheme, _ => { });
  119. // S6-LEGACY-SNAPSHOT-MIGRATION-1:S6 存量运行中实例的审批权威快照迁移(批次边界内一次性)。
  120. services.AddTransient<Admin.NET.Plugin.AiDOP.Service.S6.S6LegacyFlowAuthorityMigrationService>();
  121. }
  122. /// <summary>
  123. /// 开发环境下为 Demo 表执行 CodeFirst(表已存在则跳过变更,行为取决于 SqlSugar 配置)。
  124. /// </summary>
  125. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  126. {
  127. // S8-RULE-GOVERNANCE-BATCH1:强制解析一次规则目录,把「定义写错」变成启动失败。
  128. // 刻意**不 try/catch**:重复 rule_code、rule_type 不在词表、TIMEOUT 却没给判定语义,
  129. // 都是发布事故。吞掉它只会让服务带着一个坏目录起来,然后在某条规则跑到那一 tick 时
  130. // 才在后台日志里报错——那时页面上一切正常,没人会看见。
  131. {
  132. var ruleCatalog = app.ApplicationServices
  133. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.Rules.Definitions.IS8RuleCatalog>();
  134. Trace.TraceInformation($"Ai-DOP S8RuleCatalog: {ruleCatalog.Definitions.Count} rule definition(s) loaded");
  135. }
  136. // 与 Core 的 DOCKER_SKIP_DBINIT 语义保持一致:已有库运行模式下不再执行插件自己的
  137. // CodeFirst、种子、存量回填和一次性迁移。此前 Core 虽跳过 287 表初始化,插件仍继续
  138. // 扫描全部贴源表并执行 S1–S9 种子,导致端口迟迟不能监听。
  139. if (string.Equals(
  140. Environment.GetEnvironmentVariable("DOCKER_SKIP_DBINIT"),
  141. "1",
  142. StringComparison.Ordinal))
  143. {
  144. Trace.TraceInformation("Ai-DOP 数据库初始化已由 DOCKER_SKIP_DBINIT=1 跳过");
  145. return;
  146. }
  147. // 来源登记扫全部贴源表,同步执行会让端口晚开数秒。**不要改成 Task.Run 火忘**:
  148. // ISqlSugarClient 是单例 SqlSugarScope,其 AsyncLocal 上下文在启动期会被 Configure 管道
  149. // 与火忘任务共享,两个执行流拿到同一个 MySqlConnection;后执行的 ApprovalFlow(100) 插件
  150. // 同步 InitTables 时没有任何容错,撞上即未处理异常、进程退出。
  151. // 2026-09-26 的启动崩溃就是这么来的,见 doc/plan/天缘对接/20260926问题描述.md。
  152. try
  153. {
  154. using var sourceScope = app.ApplicationServices.CreateScope();
  155. var sourceDb = sourceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  156. Admin.NET.Plugin.AiDOP.DataPlatform.Schema.MdpSchemaAligner.EnsureSourceIdentityAsync(sourceDb)
  157. .GetAwaiter().GetResult();
  158. }
  159. catch (Exception ex)
  160. {
  161. Trace.TraceError("Ai-DOP 来源登记与 written_by 对齐失败: " + ex);
  162. }
  163. try
  164. {
  165. AidopMenuLinkSync.EnsureLinked(app.ApplicationServices);
  166. }
  167. catch (Exception ex)
  168. {
  169. Trace.TraceWarning("Ai-DOP AidopMenuLinkSync: " + ex);
  170. }
  171. // S8-RULE-GOVERNANCE-BATCH2:启动期对账 —— 确保每个启用租户都拥有全部代码定义规则的运行策略行。
  172. //
  173. // 失败策略沿用本方法内既有的 sync 惯例(AidopMenuLinkSync 等):**记录后放行,不阻断启动**。
  174. // 理由是这段 Configure 与 S0–S7 共用同一个宿主,供给失败不该把整个平台拖下线;
  175. // 且新 provision 的规则一律 disabled,缺行的后果是"该租户暂时看不到这条规则",
  176. // 不是数据错误。异常对象完整落日志,不做静默吞掉 —— 「以为同步成功」比同步失败更危险。
  177. //
  178. // 与之对照:规则目录自身构造失败(重复定义 / 定义写错)**不在此列**,
  179. // 它在上方 GetRequiredService 处直接抛出、阻断启动,因为那是代码包本身有问题。
  180. try
  181. {
  182. using var provisioningScope = app.ApplicationServices.CreateScope();
  183. var provisioning = provisioningScope.ServiceProvider
  184. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.S8RuleProvisioningService>();
  185. var summary = provisioning.SyncAsync().GetAwaiter().GetResult();
  186. Trace.TraceInformation(
  187. $"Ai-DOP S8RuleProvisioning: tenants={summary.TenantCount} definitions={summary.DefinitionCount} "
  188. + $"created={summary.CreatedCount} refreshed={summary.RefreshedCount} unchanged={summary.UnchangedCount} "
  189. + $"orphaned={summary.OrphanedCount} duplicate={summary.DuplicateCount}");
  190. }
  191. catch (Exception ex)
  192. {
  193. Trace.TraceError("Ai-DOP S8RuleProvisioning FAILED(本次启动未完成规则运行策略对账): " + ex);
  194. }
  195. // S8-ACTION-PERMISSION-1:动作授权矩阵首次供给。
  196. // 失败策略与上方规则供给一致:记录后放行,不阻断启动(本宿主与 S0–S7 共用)。
  197. // 后果是"该租户的异常动作暂时全部关闭"——fail closed,不是数据错误,且日志可见。
  198. try
  199. {
  200. using var actionScope = app.ApplicationServices.CreateScope();
  201. var actionProvisioning = actionScope.ServiceProvider
  202. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.S8ExceptionActionRoleProvisioningService>();
  203. var actionSummary = actionProvisioning.SyncAsync().GetAwaiter().GetResult();
  204. Trace.TraceInformation(
  205. $"Ai-DOP S8ActionRoleProvisioning: tenants={actionSummary.TenantCount} actions={actionSummary.ActionCount} "
  206. + $"created={actionSummary.CreatedCount} skippedTenants={actionSummary.SkippedTenantCount} "
  207. + $"unconfigured={actionSummary.UnconfiguredCount}");
  208. }
  209. catch (Exception ex)
  210. {
  211. Trace.TraceError("Ai-DOP S8ActionRoleProvisioning FAILED(本次启动未完成动作授权供给): " + ex);
  212. }
  213. // S8-UAT-APPROVAL-REPAIR-1:修复 S8 审批流里解析不出本租户审批人的节点。
  214. //
  215. // 必须排在动作授权供给**之后** —— 它按「该租户被授予 s8:verification:approve 的角色」
  216. // 派生审批人,而那份授权正是上一步供给出来的。
  217. //
  218. // 失败策略同上:记录后放行。审批人配置坏了的后果是流程悬挂(已经在发生),
  219. // 不该再因为修复失败把整个平台拖下线。
  220. try
  221. {
  222. using var repairScope = app.ApplicationServices.CreateScope();
  223. var repair = repairScope.ServiceProvider
  224. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S8.S8ApprovalFlowRepairService>();
  225. var repairSummary = repair.RepairAsync().GetAwaiter().GetResult();
  226. Trace.TraceInformation(
  227. $"Ai-DOP S8ApprovalFlowRepair: flows={repairSummary.FlowsInspected} nodes={repairSummary.NodesInspected} "
  228. + $"repaired={repairSummary.RepairedCount} restored={repairSummary.RestoredCount} "
  229. + $"alreadyOk={repairSummary.AlreadyOkCount} blocked={repairSummary.BlockedCount}");
  230. }
  231. catch (Exception ex)
  232. {
  233. Trace.TraceError("Ai-DOP S8ApprovalFlowRepair FAILED(本次启动未完成审批人配置对账): " + ex);
  234. }
  235. // S6-LEGACY-SNAPSHOT-MIGRATION-1:把批次边界内、仍在运行的 S6 实例快照里冻结的
  236. // 跨租户物理 RoleId 迁成 RoleCode。批次边界之后发起的实例一律不碰 —— 那之后再出现
  237. // 跨租户引用属于新的 authority regression,必须响亮失败而不是被这里静默治好。
  238. // 迁完即幂等:判据是快照自身内容,第二次启动选不出候选、不发 UPDATE。
  239. try
  240. {
  241. using var migScope = app.ApplicationServices.CreateScope();
  242. var migDb = migScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  243. migDb.CodeFirst.InitTables(typeof(Admin.NET.Plugin.AiDOP.Entity.S6.AdoS6FlowAuthorityMigrationLog));
  244. var migration = migScope.ServiceProvider
  245. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S6.S6LegacyFlowAuthorityMigrationService>();
  246. var migSummary = migration.MigrateAsync().GetAwaiter().GetResult();
  247. Trace.TraceInformation(
  248. $"Ai-DOP S6LegacyFlowAuthorityMigration: selected={migSummary.CandidatesSelected} "
  249. + $"applied={migSummary.Applied} blockedTargetAuthority={migSummary.BlockedByTargetAuthority} "
  250. + $"blockedRollbackSource={migSummary.BlockedByRollbackSource} "
  251. + $"blockedConcurrent={migSummary.BlockedByConcurrentModification}");
  252. }
  253. catch (Exception ex)
  254. {
  255. Trace.TraceError("Ai-DOP S6LegacyFlowAuthorityMigration FAILED(本次启动未完成存量快照迁移): " + ex);
  256. }
  257. // S7-LEGACY-SNAPSHOT-MIGRATION-1:把批次边界内、仍在运行的 S7 实例快照里冻结的
  258. // 默认租户物理 RoleId 迁成 RoleCode。批次边界之后发起的实例一律不碰 —— 那之后再出现
  259. // 默认租户 RoleId 属于新的 authority regression,必须响亮失败而不是被这里静默治好。
  260. // 迁完即幂等:判据是快照自身内容,第二次启动选不出候选、不发 UPDATE。
  261. //
  262. // 先 DryRun 再 Migrate:两者共用同一套谓词与门禁(EvaluateAsync),
  263. // DryRun 只读不写,其逐条结论会落到应用日志,便于事后核对「实际改的就是预演过的」。
  264. try
  265. {
  266. using var s7MigScope = app.ApplicationServices.CreateScope();
  267. var s7MigDb = s7MigScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  268. s7MigDb.CodeFirst.InitTables(typeof(Admin.NET.Plugin.AiDOP.Entity.S7.AdoS7FlowAuthorityMigrationLog));
  269. var s7Mig = s7MigScope.ServiceProvider
  270. .GetRequiredService<Admin.NET.Plugin.AiDOP.Service.S7.S7LegacyFlowAuthorityMigrationService>();
  271. s7Mig.DryRunAsync().GetAwaiter().GetResult();
  272. s7Mig.MigrateAsync().GetAwaiter().GetResult(); // 汇总与逐条结论由服务内 ILogger 输出
  273. }
  274. catch (Exception ex)
  275. {
  276. Trace.TraceError("Ai-DOP S7LegacyFlowAuthorityMigration FAILED(本次启动未完成存量快照迁移): " + ex);
  277. }
  278. try
  279. {
  280. using var scopeS4 = app.ApplicationServices.CreateScope();
  281. var dbS4 = scopeS4.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  282. dbS4.CodeFirst.InitTables(
  283. typeof(AdoSmartOpsLayoutItem),
  284. typeof(AdoSmartOpsHomeModule),
  285. typeof(AdoSmartOpsKpiMaster),
  286. typeof(AdoSmartOpsDashboardPageConfig),
  287. typeof(AdoSmartOpsDashboardWidget),
  288. typeof(AdoSmartOpsImprovementPlan),
  289. typeof(AdoMdpHotWatch),
  290. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoS1DashboardRebuildJob),
  291. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoModuleDashboardRebuildJob),
  292. typeof(Admin.NET.Plugin.AiDOP.Entity.SmartOps.AdoMdpRebuildLock),
  293. typeof(AdoTenantMenuSuppression));
  294. AidopTenantMigration.MigrateOldTenantId(dbS4);
  295. AidopKpiMasterSeed.EnsureSeed(dbS4);
  296. AidopKpiMasterSeed.EnsureSeed(dbS4, 1300000000888);
  297. var kpiTenantIds = dbS4.Queryable<AdoSmartOpsKpiMaster>()
  298. .Select(x => x.TenantId)
  299. .Distinct()
  300. .ToList();
  301. foreach (var tenantId in kpiTenantIds.Where(x => x > 0))
  302. AidopKpiMasterSeed.EnsureSeed(dbS4, tenantId!.Value);
  303. AidopImprovementApprovalSeed.EnsureSeed(dbS4);
  304. Admin.NET.Plugin.AiDOP.Infrastructure.IqcInspBillFlowSeed.EnsureSeed(dbS4);
  305. Admin.NET.Plugin.AiDOP.Infrastructure.IpqcInspectionFlowSeed.EnsureSeed(dbS4);
  306. Admin.NET.Plugin.AiDOP.Infrastructure.S6ProcessInspectionFlowSeed.EnsureSeed(dbS4);
  307. Admin.NET.Plugin.AiDOP.Infrastructure.FqcInspBillFlowSeed.EnsureSeed(dbS4);
  308. }
  309. catch (Exception ex)
  310. {
  311. Trace.TraceWarning("Ai-DOP CodeFirst/S4 seed: " + ex);
  312. }
  313. if (!env.IsDevelopment())
  314. return;
  315. // 客户/物料/优先级等;远端若已是遗留表(已有主键),CodeFirst 会报 Multiple primary key,不可让启动失败。
  316. try
  317. {
  318. using var scope = app.ApplicationServices.CreateScope();
  319. var db = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
  320. db.CodeFirst.InitTables(
  321. typeof(AdoS0CustMaster),
  322. typeof(AdoS0ItemMaster),
  323. typeof(AdoS0ItemSubstituteDetail),
  324. typeof(AdoS0PriorityCode),
  325. typeof(AdoS0ProductStructureOp),
  326. typeof(AdoS0StdOpMaster),
  327. typeof(AdoS0LineMaster),
  328. typeof(AdoS0MfgRoutingOpDetail),
  329. typeof(AdoS0MfgPersonSkillAssignment),
  330. typeof(AdoS0MfgWorkOrderControl),
  331. typeof(AdoS0MfgPersonSkill),
  332. typeof(AdoS0MfgLinePost),
  333. typeof(AdoS0MfgWorkCenter),
  334. typeof(AdoS0MfgLineMaterial),
  335. typeof(AdoS0ProcessFlowCard),
  336. typeof(AdoS0OrderScheduleCycle),
  337. typeof(AdoS0MfgElementParam),
  338. typeof(AdoS0MfgMaterialProcessElement),
  339. typeof(AdoS0MfgPreprocessElement),
  340. typeof(AdoS0MfgPreprocessElementParam),
  341. typeof(AdoS0ImageType),
  342. typeof(AdoS0QualitySegmentImage),
  343. typeof(AdoS0QmsQualityBaseType),
  344. typeof(AdoS0QmsFqcInspectionSpec),
  345. typeof(AdoS0QmsFqcInspectionSpecEntry),
  346. typeof(AdoS0QmsOqcInspectionSpec),
  347. typeof(AdoS0QmsOqcInspectionSpecEntry),
  348. typeof(AdoS0CategoryLeadTime),
  349. typeof(AdoS0MaterialPlanCycle),
  350. typeof(AdoS0SrmPurchase),
  351. typeof(AdoS8Exception),
  352. typeof(AdoS8ExceptionTimeline),
  353. typeof(AdoS8DecisionRecord),
  354. typeof(AdoS8Evidence),
  355. // S8-STANDARD-DATASET-HARD-CUTOVER-1:AdoS8AlertRule 已物理删除
  356. // (运行时权威一直是 AdoS8WatchRule;该表零 Runtime Consumer)。
  357. typeof(AdoS8SceneConfig),
  358. typeof(AdoS8NotificationLayer),
  359. typeof(AdoS8WatchRule),
  360. typeof(AdoS8RolePermissionConfig),
  361. // S8-ACTION-PERMISSION-1:异常动作 ↔ 角色授权矩阵。
  362. typeof(AdoS8ExceptionActionRole),
  363. // S8-HANDLER-POOL-1:规则 ↔ 处理账号池。
  364. typeof(AdoS8RuleHandlerUser),
  365. // S8-NOTIFY-RECIPIENT-1:事件 ↔ 收件人配置。
  366. typeof(AdoS8NotificationRecipient),
  367. // S8-UAT-APPROVAL-REPAIR-1:审批人配置修复留证。
  368. typeof(AdoS8ApprovalFlowRepairLog),
  369. typeof(AdoS8NotificationLog),
  370. typeof(AdoS8DashboardCellConfig),
  371. typeof(AdoS8ExceptionType),
  372. typeof(AdoS8DetectionLog),
  373. typeof(AdoS8RuleDetectionState),
  374. typeof(AdoS8Dimension),
  375. typeof(AdoS8DimensionNode),
  376. typeof(AdoS8ConfigDraft),
  377. typeof(AdoS8MonitorObject),
  378. typeof(AdoS8MonitorMetric),
  379. typeof(AdoS8OrderFlowOrder),
  380. typeof(AdoS8OrderFlowStage),
  381. typeof(AdoS8OrderFlowSnapshot),
  382. typeof(AdoS8OrderFlowSubstep),
  383. typeof(AdoS8OrderFlowSubstepUnit),
  384. typeof(AdoS8OrderFlowProcurementPivot),
  385. typeof(ContractReview),
  386. typeof(ContractReviewFlow),
  387. typeof(ProductDesign),
  388. typeof(ProductDesignBom),
  389. typeof(ProductDesignRouting)
  390. );
  391. AidopDimensionSeed.EnsureSeed(db);
  392. AidopMonitorDictionarySeed.EnsureSeed(db);
  393. }
  394. catch (Exception ex)
  395. {
  396. Trace.TraceWarning("Ai-DOP Development CodeFirst (S0/S8/Order demo tables): " + ex);
  397. }
  398. }
  399. }