AidopTenantMigration.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System.Diagnostics;
  2. using Admin.NET.Core;
  3. using SqlSugar;
  4. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  5. /// <summary>
  6. /// 一次性迁移:将历史 TenantId=1 的 AiDOP 业务数据更新为框架默认租户。
  7. /// 幂等——若已无 TenantId=1 的行则不做任何事。
  8. /// </summary>
  9. public static class AidopTenantMigration
  10. {
  11. private static readonly (string Table, string TenantColumn)[] Tables =
  12. {
  13. ("ado_smart_ops_kpi_master", "TenantId"),
  14. ("ado_smart_ops_layout_item", "TenantId"),
  15. ("ado_smart_ops_home_module", "TenantId"),
  16. ("ado_s9_kpi_value_l1_day", "tenant_id"),
  17. ("ado_s9_kpi_value_l2_day", "tenant_id"),
  18. ("ado_s9_kpi_value_l3_day", "tenant_id"),
  19. ("ado_s9_kpi_value_l4_day", "tenant_id"),
  20. ("ado_smart_ops_kpi_atomic_day", "tenant_id")
  21. };
  22. public static void MigrateOldTenantId(ISqlSugarClient db)
  23. {
  24. EnsureKpiValueL4Table(db);
  25. EnsureKpiAtomicTable(db);
  26. var target = SqlSugarConst.DefaultTenantId;
  27. if (target == 1) return;
  28. foreach (var (table, tenantColumn) in Tables)
  29. {
  30. try
  31. {
  32. db.Ado.ExecuteCommand(
  33. $"UPDATE `{table}` SET `{tenantColumn}` = @target WHERE `{tenantColumn}` = 1",
  34. new { target });
  35. }
  36. catch (Exception ex)
  37. {
  38. Trace.TraceWarning($"AidopTenantMigration [{table}]: {ex.Message}");
  39. }
  40. }
  41. DropOldUniqueIndex(db);
  42. BackfillThresholds(db);
  43. }
  44. public static void EnsureKpiValueL4Table(ISqlSugarClient db)
  45. {
  46. try
  47. {
  48. db.Ado.ExecuteCommand(
  49. "CREATE TABLE IF NOT EXISTS `ado_s9_kpi_value_l4_day` LIKE `ado_s9_kpi_value_l3_day`");
  50. }
  51. catch (Exception ex)
  52. {
  53. Trace.TraceWarning($"AidopTenantMigration ensure L4 value table: {ex.Message}");
  54. }
  55. }
  56. public static void EnsureKpiAtomicTable(ISqlSugarClient db)
  57. {
  58. RepairKpiAtomicTableSchemaIfNeeded(db);
  59. try
  60. {
  61. db.Ado.ExecuteCommand(
  62. """
  63. CREATE TABLE IF NOT EXISTS ado_smart_ops_kpi_atomic_day (
  64. id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
  65. tenant_id BIGINT NOT NULL COMMENT '租户ID',
  66. factory_id BIGINT NOT NULL DEFAULT 1 COMMENT '工厂ID',
  67. domain_code VARCHAR(50) NOT NULL COMMENT '业务对象域',
  68. metric_code VARCHAR(50) NOT NULL COMMENT '指标编码',
  69. metric_level TINYINT NOT NULL DEFAULT 1 COMMENT '指标层级',
  70. stat_date DATE NOT NULL COMMENT '统计日期',
  71. customer_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '客户编码',
  72. product_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '产品编码',
  73. order_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '订单号',
  74. production_line VARCHAR(100) NOT NULL DEFAULT '' COMMENT '产线',
  75. supplier_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '供应商编码',
  76. material_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '物料编码',
  77. po_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '采购单号',
  78. warehouse_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '仓库编码',
  79. work_order_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '工单号',
  80. equipment_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '设备编码',
  81. outbound_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '出库单号',
  82. sum_value DECIMAL(18, 6) NULL COMMENT '可累加值',
  83. numerator_value DECIMAL(18, 6) NULL COMMENT '比率分子',
  84. denominator_value DECIMAL(18, 6) NULL COMMENT '比率分母',
  85. sample_count INT NULL COMMENT '样本数',
  86. min_value DECIMAL(18, 6) NULL COMMENT '最小值',
  87. max_value DECIMAL(18, 6) NULL COMMENT '最大值',
  88. batch_id VARCHAR(100) NULL COMMENT 'MDP批次',
  89. calc_time DATETIME(3) NULL COMMENT '计算时间',
  90. grain VARCHAR(20) NOT NULL DEFAULT 'day' COMMENT '统计粒度',
  91. is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标识',
  92. PRIMARY KEY (id),
  93. UNIQUE KEY uk_atomic_grain (
  94. tenant_id, factory_id, domain_code, metric_code, stat_date,
  95. customer_code, product_code, order_no, production_line,
  96. supplier_code, material_code, po_no, warehouse_code,
  97. work_order_no, equipment_code, outbound_no
  98. ),
  99. KEY idx_tenant_domain_metric_date (tenant_id, domain_code, metric_code, stat_date),
  100. KEY idx_tenant_module_lookup (tenant_id, factory_id, metric_code, stat_date)
  101. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KPI原子聚合层(日)'
  102. """);
  103. EnsureKpiAtomicColumnDefaults(db);
  104. }
  105. catch (Exception ex)
  106. {
  107. Trace.TraceWarning($"AidopTenantMigration ensure KPI atomic table: {ex.Message}");
  108. }
  109. }
  110. /// <summary>
  111. /// 补齐维度列 DEFAULT '',避免 CodeFirst 误建表后 INSERT 缺列报错。
  112. /// </summary>
  113. private static void EnsureKpiAtomicColumnDefaults(ISqlSugarClient db)
  114. {
  115. try
  116. {
  117. db.Ado.ExecuteCommand(
  118. """
  119. ALTER TABLE ado_smart_ops_kpi_atomic_day
  120. MODIFY customer_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '客户编码',
  121. MODIFY product_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '产品编码',
  122. MODIFY order_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '订单号',
  123. MODIFY production_line VARCHAR(100) NOT NULL DEFAULT '' COMMENT '产线',
  124. MODIFY supplier_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '供应商编码',
  125. MODIFY material_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '物料编码',
  126. MODIFY po_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '采购单号',
  127. MODIFY warehouse_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '仓库编码',
  128. MODIFY work_order_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '工单号',
  129. MODIFY equipment_code VARCHAR(100) NOT NULL DEFAULT '' COMMENT '设备编码',
  130. MODIFY outbound_no VARCHAR(100) NOT NULL DEFAULT '' COMMENT '出库单号',
  131. MODIFY grain VARCHAR(20) NOT NULL DEFAULT 'day' COMMENT '统计粒度',
  132. MODIFY is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标识'
  133. """);
  134. }
  135. catch (Exception ex)
  136. {
  137. Trace.TraceWarning($"AidopTenantMigration ensure KPI atomic column defaults: {ex.Message}");
  138. }
  139. }
  140. /// <summary>
  141. /// 早期 SqlSugar 误建 PascalCase 列名时,DROP 后由 EnsureKpiAtomicTable 按 snake_case 重建。
  142. /// </summary>
  143. private static void RepairKpiAtomicTableSchemaIfNeeded(ISqlSugarClient db)
  144. {
  145. try
  146. {
  147. var columns = db.Ado.SqlQuery<string>(
  148. """
  149. SELECT COLUMN_NAME
  150. FROM information_schema.COLUMNS
  151. WHERE TABLE_SCHEMA = DATABASE()
  152. AND TABLE_NAME = 'ado_smart_ops_kpi_atomic_day'
  153. """);
  154. if (columns == null || columns.Count == 0)
  155. return;
  156. var hasSnakeDomain = columns.Any(c => string.Equals(c, "domain_code", StringComparison.OrdinalIgnoreCase));
  157. var hasPascalDomain = columns.Any(c => string.Equals(c, "DomainCode", StringComparison.OrdinalIgnoreCase));
  158. if (hasSnakeDomain || !hasPascalDomain)
  159. return;
  160. Trace.TraceWarning("AidopTenantMigration: ado_smart_ops_kpi_atomic_day 检测到 PascalCase 列名,将 DROP 后重建为 snake_case。");
  161. db.Ado.ExecuteCommand("DROP TABLE ado_smart_ops_kpi_atomic_day");
  162. }
  163. catch (Exception ex)
  164. {
  165. Trace.TraceWarning($"AidopTenantMigration repair KPI atomic schema: {ex.Message}");
  166. }
  167. }
  168. private static void BackfillThresholds(ISqlSugarClient db)
  169. {
  170. try
  171. {
  172. db.Ado.ExecuteCommand(
  173. "UPDATE ado_smart_ops_kpi_master SET YellowThreshold=95, RedThreshold=80 " +
  174. "WHERE YellowThreshold IS NULL AND Direction='higher_is_better'");
  175. db.Ado.ExecuteCommand(
  176. "UPDATE ado_smart_ops_kpi_master SET YellowThreshold=110, RedThreshold=120 " +
  177. "WHERE YellowThreshold IS NULL AND Direction='lower_is_better'");
  178. }
  179. catch (Exception ex)
  180. {
  181. Trace.TraceWarning($"AidopTenantMigration backfill thresholds: {ex.Message}");
  182. }
  183. }
  184. private static void DropOldUniqueIndex(ISqlSugarClient db)
  185. {
  186. try
  187. {
  188. var rows = db.Ado.SqlQuery<dynamic>(
  189. "SHOW INDEX FROM ado_smart_ops_kpi_master WHERE Key_name = 'uk_kpi_master_code'");
  190. if (rows != null && rows.Count > 0)
  191. db.Ado.ExecuteCommand("ALTER TABLE ado_smart_ops_kpi_master DROP INDEX uk_kpi_master_code");
  192. }
  193. catch (Exception ex)
  194. {
  195. Trace.TraceWarning($"AidopTenantMigration drop old index: {ex.Message}");
  196. }
  197. }
  198. }