1.0.496.sql 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. -- =====================================================================================
  2. -- 1.0.496 S8-SYSUSER-ONLY-1 · 人员身份统一为系统账号 + 旧运行数据 CLEAN RESET
  3. --
  4. -- 背景(真库取证,只读):
  5. -- · ado_s8_exception.assignee_id 34 个非空值里只有 20 个能解析到 EmployeeMaster.RecID,
  6. -- 0 个能解析到 SysUser.Id,另有 14 行是 assignee_id = 1(两边都不存在的脏值)。
  7. -- · ado_s8_exception_timeline.operator_id 同一列混装两套 ID 空间:
  8. -- CLAIM 写 EmployeeMaster.RecID(19 行),START_PROGRESS / VERIFY_* / CREATE 写 SysUser.Id。
  9. -- · EmployeeMaster 1105 行只有 8 行绑定了 sys_user_id(0.7%),
  10. -- 所有「经 EmployeeMaster.SysUserId 换算」的通知与审批流转办路径实际上长期静默失败。
  11. --
  12. -- 本次动作:物理列改名为显式 *_user_id,并清空旧运行数据(不做逐条身份迁移 —— 业务已确认
  13. -- 旧 S8 历史数据无保留要求;混装两套 ID 空间的历史值也没有可靠的迁移依据)。
  14. --
  15. -- 幂等:六个阶段全部可重复执行。第二次运行时 PHASE 3 清 0 行、PHASE 4 全部跳过。
  16. --
  17. -- 安全边界:
  18. -- · RESET 表为**硬编码白名单**,绝不使用 LIKE 'ado_s8_%'(否则将来新增的表会被无声删掉)。
  19. -- · 只清 S8 自己的运行数据。SysUser / SysRole / SysUserRole / SysOrg /
  20. -- DepartmentMaster / EmployeeMaster 一行不动。
  21. -- · 配置态表(exception_type / scene_config / notification_layer / role_permission_config /
  22. -- dashboard_cell_config / watch_rule 的参数列)一行不动。
  23. -- · 审批流表按 **BizType 白名单**定点删除 S8 自己发起的实例与任务,
  24. -- 绝不整表清空(ApprovalFlowInstance / Task 由 S5/S6/S7 共用)。
  25. -- =====================================================================================
  26. -- ─────────────────────────────────────────────────────────────────────────────────────
  27. -- PHASE 1 · PREFLIGHT
  28. -- 目的:把「执行前长什么样」原样记下来。出问题时这张表是唯一的现场。
  29. -- ─────────────────────────────────────────────────────────────────────────────────────
  30. CREATE TABLE IF NOT EXISTS `ado_s8_sysuser_only_reset_1_0_496` (
  31. `id` BIGINT NOT NULL AUTO_INCREMENT,
  32. `phase` VARCHAR(16) NOT NULL COMMENT 'BEFORE / AFTER',
  33. `table_name` VARCHAR(96) NOT NULL,
  34. `table_class` VARCHAR(32) NOT NULL COMMENT 'RUNTIME / FLOW_RUNTIME / POLICY_RUNTIME_STATE',
  35. `row_count` BIGINT NOT NULL,
  36. `noted_at` DATETIME NOT NULL,
  37. PRIMARY KEY (`id`),
  38. KEY `idx_reset_1_0_496_phase` (`phase`, `table_name`)
  39. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='1.0.496 CLEAN RESET 前后行数留证';
  40. -- 重复执行时先清掉上一轮的留证,避免同一 phase 出现多份互相矛盾的计数。
  41. DELETE FROM `ado_s8_sysuser_only_reset_1_0_496`;
  42. -- ── RESET 白名单(就是下面这 7 张,没有第 8 张)──
  43. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  44. SELECT 'BEFORE','ado_s8_exception','RUNTIME',COUNT(*),NOW() FROM `ado_s8_exception`
  45. UNION ALL SELECT 'BEFORE','ado_s8_exception_timeline','RUNTIME',COUNT(*),NOW() FROM `ado_s8_exception_timeline`
  46. UNION ALL SELECT 'BEFORE','ado_s8_detection_log','RUNTIME',COUNT(*),NOW() FROM `ado_s8_detection_log`
  47. UNION ALL SELECT 'BEFORE','ado_s8_rule_detection_state','RUNTIME',COUNT(*),NOW() FROM `ado_s8_rule_detection_state`
  48. UNION ALL SELECT 'BEFORE','ado_s8_notification_log','RUNTIME',COUNT(*),NOW() FROM `ado_s8_notification_log`
  49. UNION ALL SELECT 'BEFORE','ado_s8_evidence','RUNTIME',COUNT(*),NOW() FROM `ado_s8_evidence`
  50. UNION ALL SELECT 'BEFORE','ado_s8_decision_record','RUNTIME',COUNT(*),NOW() FROM `ado_s8_decision_record`;
  51. -- 审批流:只统计 S8 自己的三种 BizType,别的模块的行不在本次视野里。
  52. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  53. SELECT 'BEFORE','ApprovalFlowInstance(S8 BizType)','FLOW_RUNTIME',COUNT(*),NOW()
  54. FROM `ApprovalFlowInstance`
  55. WHERE `BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE')
  56. UNION ALL
  57. SELECT 'BEFORE','ApprovalFlowTask(S8 BizType)','FLOW_RUNTIME',COUNT(*),NOW()
  58. FROM `ApprovalFlowTask` t
  59. JOIN `ApprovalFlowInstance` i ON i.`Id` = t.`InstanceId`
  60. WHERE i.`BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE');
  61. -- 规则:只清运行态列,参数列(含租户已配的默认部门)保持不动,见 PHASE 3 说明。
  62. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  63. SELECT 'BEFORE','ado_s8_watch_rule(last_run_at NOT NULL)','POLICY_RUNTIME_STATE',COUNT(*),NOW()
  64. FROM `ado_s8_watch_rule` WHERE `last_run_at` IS NOT NULL;
  65. -- ─────────────────────────────────────────────────────────────────────────────────────
  66. -- PHASE 2 · ARCHIVE / DIAGNOSTICS
  67. -- 不做整表备份(业务已确认旧数据无保留要求,全量备份只会把混装 ID 的脏数据换个地方留着)。
  68. -- 只留一份**证据摘要**:这次到底删掉了什么形状的身份数据。
  69. -- ─────────────────────────────────────────────────────────────────────────────────────
  70. CREATE TABLE IF NOT EXISTS `ado_s8_idspace_evidence_1_0_496` (
  71. `id` BIGINT NOT NULL AUTO_INCREMENT,
  72. `metric` VARCHAR(96) NOT NULL,
  73. `metric_value` BIGINT NOT NULL,
  74. `noted_at` DATETIME NOT NULL,
  75. PRIMARY KEY (`id`)
  76. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='1.0.496 清理前的 ID 空间混用取证';
  77. DELETE FROM `ado_s8_idspace_evidence_1_0_496`;
  78. -- 只在旧列还在时取证;改过名之后这些指标已无处可取,跳过即可(幂等)。
  79. SET @has_old_assignee := (SELECT COUNT(*) FROM information_schema.COLUMNS
  80. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'ado_s8_exception' AND COLUMN_NAME = 'assignee_id');
  81. SET @sql := IF(@has_old_assignee = 1, '
  82. INSERT INTO `ado_s8_idspace_evidence_1_0_496` (`metric`,`metric_value`,`noted_at`)
  83. SELECT ''assignee_notnull'', COUNT(*), NOW() FROM `ado_s8_exception` WHERE `assignee_id` IS NOT NULL
  84. UNION ALL SELECT ''assignee_resolves_employee'', COUNT(*), NOW()
  85. FROM `ado_s8_exception` e JOIN `EmployeeMaster` m ON m.`RecID` = e.`assignee_id`
  86. UNION ALL SELECT ''assignee_resolves_sysuser'', COUNT(*), NOW()
  87. FROM `ado_s8_exception` e JOIN `SysUser` u ON u.`Id` = e.`assignee_id`
  88. UNION ALL SELECT ''assignee_dangling'', COUNT(*), NOW()
  89. FROM `ado_s8_exception` e
  90. WHERE e.`assignee_id` IS NOT NULL
  91. AND NOT EXISTS (SELECT 1 FROM `EmployeeMaster` m WHERE m.`RecID` = e.`assignee_id`)
  92. AND NOT EXISTS (SELECT 1 FROM `SysUser` u WHERE u.`Id` = e.`assignee_id`)
  93. ', 'DO 0');
  94. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  95. -- ─────────────────────────────────────────────────────────────────────────────────────
  96. -- PHASE 3 · RUNTIME DATA RESET
  97. --
  98. -- 删除顺序:明细 → 主表 → 审批流。全部按白名单点名,没有一条通配。
  99. -- ─────────────────────────────────────────────────────────────────────────────────────
  100. -- ① S8 异常运行数据
  101. DELETE FROM `ado_s8_exception_timeline`;
  102. DELETE FROM `ado_s8_evidence`;
  103. DELETE FROM `ado_s8_decision_record`;
  104. DELETE FROM `ado_s8_exception`;
  105. -- ② 规则检测运行数据(不清会让规则以为"这些对象我已经报过了",重启后一条都建不出来)
  106. DELETE FROM `ado_s8_detection_log`;
  107. DELETE FROM `ado_s8_rule_detection_state`;
  108. -- ③ 通知投递日志(全部指向已删除的异常)
  109. DELETE FROM `ado_s8_notification_log`;
  110. -- ④ 审批流:**只删 S8 自己的三种 BizType**。
  111. -- ApprovalFlowInstance / ApprovalFlowTask 由 S5/S6/S7 共用,整表清空会毁掉别的模块。
  112. -- 先删子表(Task)再删父表(Instance),否则 Task 会成为查不到来源的孤儿。
  113. DELETE t FROM `ApprovalFlowTask` t
  114. JOIN `ApprovalFlowInstance` i ON i.`Id` = t.`InstanceId`
  115. WHERE i.`BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE');
  116. DELETE FROM `ApprovalFlowInstance`
  117. WHERE `BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE');
  118. -- ⑤ 规则的**运行态列**清零。
  119. -- 刻意只清运行态、不动参数列,也不删行:
  120. -- · watch_rule 上没有任何人员身份列,本次改造不要求重建它;
  121. -- · 删行会连带删掉租户已配的默认部门(实测 2 个租户已配 245 / 253),
  122. -- 而 Provisioning 只能把它们恢复成 NULL —— 那是纯粹的损失,换不来任何东西;
  123. -- · 也会删掉无代码定义的孤儿行,与 S8RuleProvisioningService「孤儿不删、不改、不启用」相矛盾。
  124. -- enabled 一列同样不碰:真库 11 行当前全部为 0,Provisioning 新建行也恒为 false,
  125. -- 因此 reset 之后 Rule 01 仍然是停用状态 —— §十八 要的就是这个结果。
  126. UPDATE `ado_s8_watch_rule`
  127. SET `last_run_at` = NULL,
  128. `last_status` = NULL,
  129. `last_error` = NULL,
  130. `last_duration_ms` = NULL,
  131. `last_run_id` = NULL,
  132. `next_run_at` = NULL,
  133. `lock_token` = NULL,
  134. `locked_by` = NULL,
  135. `lock_until` = NULL,
  136. `running_started_at` = NULL,
  137. `consecutive_failure_count` = 0
  138. WHERE `last_run_at` IS NOT NULL
  139. OR `last_status` IS NOT NULL
  140. OR `last_run_id` IS NOT NULL
  141. OR `lock_token` IS NOT NULL
  142. OR `running_started_at` IS NOT NULL
  143. OR `consecutive_failure_count` <> 0;
  144. -- ─────────────────────────────────────────────────────────────────────────────────────
  145. -- PHASE 4 · SCHEMA IDENTITY CHANGE
  146. --
  147. -- 三种到场状态都要能收敛(幂等):
  148. -- (a) 只有旧列 → CHANGE COLUMN 改名
  149. -- (b) 新旧列同时存在 → CodeFirst 已经把新列加进来了;此时数据已在 PHASE 3 清空,
  150. -- 直接丢掉旧列即可,不需要也不应该做数据搬运
  151. -- (c) 只有新列 → 已经改过,跳过
  152. -- ─────────────────────────────────────────────────────────────────────────────────────
  153. -- ── ado_s8_exception.assignee_id → assignee_user_id ──
  154. SET @old := (SELECT COUNT(*) FROM information_schema.COLUMNS
  155. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='assignee_id');
  156. SET @new := (SELECT COUNT(*) FROM information_schema.COLUMNS
  157. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='assignee_user_id');
  158. SET @sql := CASE
  159. WHEN @old = 1 AND @new = 0 THEN 'ALTER TABLE `ado_s8_exception` CHANGE COLUMN `assignee_id` `assignee_user_id` BIGINT NULL COMMENT ''处理人账号Id(SysUser.Id)'''
  160. WHEN @old = 1 AND @new = 1 THEN 'ALTER TABLE `ado_s8_exception` DROP COLUMN `assignee_id`'
  161. ELSE 'DO 0' END;
  162. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  163. -- ── ado_s8_exception.verifier_id → verifier_user_id ──
  164. SET @old := (SELECT COUNT(*) FROM information_schema.COLUMNS
  165. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='verifier_id');
  166. SET @new := (SELECT COUNT(*) FROM information_schema.COLUMNS
  167. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='verifier_user_id');
  168. SET @sql := CASE
  169. WHEN @old = 1 AND @new = 0 THEN 'ALTER TABLE `ado_s8_exception` CHANGE COLUMN `verifier_id` `verifier_user_id` BIGINT NULL COMMENT ''检验人账号Id(SysUser.Id)'''
  170. WHEN @old = 1 AND @new = 1 THEN 'ALTER TABLE `ado_s8_exception` DROP COLUMN `verifier_id`'
  171. ELSE 'DO 0' END;
  172. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  173. -- ── ado_s8_exception.reporter_id → reporter_user_id ──
  174. SET @old := (SELECT COUNT(*) FROM information_schema.COLUMNS
  175. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='reporter_id');
  176. SET @new := (SELECT COUNT(*) FROM information_schema.COLUMNS
  177. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception' AND COLUMN_NAME='reporter_user_id');
  178. SET @sql := CASE
  179. WHEN @old = 1 AND @new = 0 THEN 'ALTER TABLE `ado_s8_exception` CHANGE COLUMN `reporter_id` `reporter_user_id` BIGINT NULL COMMENT ''提报人账号Id(SysUser.Id)'''
  180. WHEN @old = 1 AND @new = 1 THEN 'ALTER TABLE `ado_s8_exception` DROP COLUMN `reporter_id`'
  181. ELSE 'DO 0' END;
  182. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  183. -- ── ado_s8_exception_timeline.operator_id → operator_user_id ──
  184. SET @old := (SELECT COUNT(*) FROM information_schema.COLUMNS
  185. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception_timeline' AND COLUMN_NAME='operator_id');
  186. SET @new := (SELECT COUNT(*) FROM information_schema.COLUMNS
  187. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception_timeline' AND COLUMN_NAME='operator_user_id');
  188. SET @sql := CASE
  189. WHEN @old = 1 AND @new = 0 THEN 'ALTER TABLE `ado_s8_exception_timeline` CHANGE COLUMN `operator_id` `operator_user_id` BIGINT NULL COMMENT ''操作人账号Id(SysUser.Id);系统动作为 NULL'''
  190. WHEN @old = 1 AND @new = 1 THEN 'ALTER TABLE `ado_s8_exception_timeline` DROP COLUMN `operator_id`'
  191. ELSE 'DO 0' END;
  192. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  193. -- ── 索引重建 ──
  194. --
  195. -- 这里有一个**实测踩到过的坑**,不要简化成「索引名不存在才建」:
  196. -- CodeFirst 若先于本脚本把 assignee_user_id 建了出来,PHASE 4 会走 (b) DROP COLUMN 分支,
  197. -- 而 MySQL 在 DROP COLUMN 时会把该列**从复合索引里摘掉、索引本身保留**。
  198. -- 结果是 idx_s8_exception_assignee_status 退化成 (tenant_id, factory_id, status, is_deleted) ——
  199. -- 索引名还在,覆盖的列却少了最关键的那一个,「我的待办」从此走不到索引,
  200. -- 而且没有任何报错。因此判据必须是「索引里有没有那一列」,不是「索引名在不在」。
  201. SET @has_col := (SELECT COUNT(*) FROM information_schema.STATISTICS
  202. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  203. AND INDEX_NAME='idx_s8_exception_assignee_status' AND COLUMN_NAME='assignee_user_id');
  204. SET @has_idx := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  205. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  206. AND INDEX_NAME='idx_s8_exception_assignee_status');
  207. SET @sql := IF(@has_idx = 1 AND @has_col = 0,
  208. 'ALTER TABLE `ado_s8_exception` DROP INDEX `idx_s8_exception_assignee_status`', 'DO 0');
  209. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  210. SET @has_idx := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  211. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  212. AND INDEX_NAME='idx_s8_exception_assignee_status');
  213. SET @sql := IF(@has_idx = 0,
  214. 'ALTER TABLE `ado_s8_exception` ADD INDEX `idx_s8_exception_assignee_status` (`tenant_id`,`factory_id`,`assignee_user_id`,`status`,`is_deleted`)',
  215. 'DO 0');
  216. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  217. SET @has_col := (SELECT COUNT(*) FROM information_schema.STATISTICS
  218. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  219. AND INDEX_NAME='idx_s8_exception_verifier_status' AND COLUMN_NAME='verifier_user_id');
  220. SET @has_idx := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  221. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  222. AND INDEX_NAME='idx_s8_exception_verifier_status');
  223. SET @sql := IF(@has_idx = 1 AND @has_col = 0,
  224. 'ALTER TABLE `ado_s8_exception` DROP INDEX `idx_s8_exception_verifier_status`', 'DO 0');
  225. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  226. SET @has_idx := (SELECT COUNT(DISTINCT INDEX_NAME) FROM information_schema.STATISTICS
  227. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  228. AND INDEX_NAME='idx_s8_exception_verifier_status');
  229. SET @sql := IF(@has_idx = 0,
  230. 'ALTER TABLE `ado_s8_exception` ADD INDEX `idx_s8_exception_verifier_status` (`tenant_id`,`factory_id`,`verifier_user_id`,`status`,`is_deleted`)',
  231. 'DO 0');
  232. PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
  233. -- ─────────────────────────────────────────────────────────────────────────────────────
  234. -- PHASE 5 · POSTCHECK
  235. -- 任何一条不成立就抛错中止,不让"半成功"通过。
  236. -- ─────────────────────────────────────────────────────────────────────────────────────
  237. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  238. SELECT 'AFTER','ado_s8_exception','RUNTIME',COUNT(*),NOW() FROM `ado_s8_exception`
  239. UNION ALL SELECT 'AFTER','ado_s8_exception_timeline','RUNTIME',COUNT(*),NOW() FROM `ado_s8_exception_timeline`
  240. UNION ALL SELECT 'AFTER','ado_s8_detection_log','RUNTIME',COUNT(*),NOW() FROM `ado_s8_detection_log`
  241. UNION ALL SELECT 'AFTER','ado_s8_rule_detection_state','RUNTIME',COUNT(*),NOW() FROM `ado_s8_rule_detection_state`
  242. UNION ALL SELECT 'AFTER','ado_s8_notification_log','RUNTIME',COUNT(*),NOW() FROM `ado_s8_notification_log`
  243. UNION ALL SELECT 'AFTER','ado_s8_evidence','RUNTIME',COUNT(*),NOW() FROM `ado_s8_evidence`
  244. UNION ALL SELECT 'AFTER','ado_s8_decision_record','RUNTIME',COUNT(*),NOW() FROM `ado_s8_decision_record`;
  245. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  246. SELECT 'AFTER','ApprovalFlowInstance(S8 BizType)','FLOW_RUNTIME',COUNT(*),NOW()
  247. FROM `ApprovalFlowInstance`
  248. WHERE `BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE')
  249. UNION ALL
  250. SELECT 'AFTER','ApprovalFlowTask(S8 BizType)','FLOW_RUNTIME',COUNT(*),NOW()
  251. FROM `ApprovalFlowTask` t
  252. JOIN `ApprovalFlowInstance` i ON i.`Id` = t.`InstanceId`
  253. WHERE i.`BizType` IN ('EXCEPTION_REPORT','EXCEPTION_ESCALATION','EXCEPTION_CLOSURE')
  254. UNION ALL
  255. SELECT 'AFTER','ado_s8_watch_rule(last_run_at NOT NULL)','POLICY_RUNTIME_STATE',COUNT(*),NOW()
  256. FROM `ado_s8_watch_rule` WHERE `last_run_at` IS NOT NULL;
  257. -- ① 白名单表必须为 0
  258. SET @left := (SELECT COALESCE(SUM(`row_count`),0) FROM `ado_s8_sysuser_only_reset_1_0_496` WHERE `phase`='AFTER');
  259. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  260. CREATE TABLE `tmp_s8_496_guard` (
  261. probe BIGINT NOT NULL,
  262. CONSTRAINT s8_496_BLOCKED_reset_tables_not_empty CHECK (probe = 0)
  263. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  264. INSERT INTO `tmp_s8_496_guard` (probe) VALUES (@left);
  265. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  266. -- ② 四个身份列必须已改名到位(新列在、旧列不在)
  267. SET @bad := (SELECT COUNT(*) FROM information_schema.COLUMNS
  268. WHERE TABLE_SCHEMA = DATABASE()
  269. AND ((TABLE_NAME='ado_s8_exception' AND COLUMN_NAME IN ('assignee_id','verifier_id','reporter_id'))
  270. OR (TABLE_NAME='ado_s8_exception_timeline' AND COLUMN_NAME='operator_id')));
  271. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  272. CREATE TABLE `tmp_s8_496_guard` (
  273. probe BIGINT NOT NULL,
  274. CONSTRAINT s8_496_BLOCKED_old_identity_column_still_present CHECK (probe = 0)
  275. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  276. INSERT INTO `tmp_s8_496_guard` (probe) VALUES (@bad);
  277. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  278. SET @ok := (SELECT COUNT(*) FROM information_schema.COLUMNS
  279. WHERE TABLE_SCHEMA = DATABASE()
  280. AND ((TABLE_NAME='ado_s8_exception' AND COLUMN_NAME IN ('assignee_user_id','verifier_user_id','reporter_user_id'))
  281. OR (TABLE_NAME='ado_s8_exception_timeline' AND COLUMN_NAME='operator_user_id')));
  282. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  283. CREATE TABLE `tmp_s8_496_guard` (
  284. probe BIGINT NOT NULL,
  285. CONSTRAINT s8_496_BLOCKED_new_identity_column_missing CHECK (probe = 0)
  286. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  287. INSERT INTO `tmp_s8_496_guard` (probe) VALUES ((4 - @ok));
  288. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  289. -- ③ 两个身份索引必须真的覆盖身份列(不是「索引名还在」就算数,见 PHASE 4 的坑)
  290. SET @idx_ok := (SELECT COUNT(*) FROM information_schema.STATISTICS
  291. WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='ado_s8_exception'
  292. AND ((INDEX_NAME='idx_s8_exception_assignee_status' AND COLUMN_NAME='assignee_user_id')
  293. OR (INDEX_NAME='idx_s8_exception_verifier_status' AND COLUMN_NAME='verifier_user_id')));
  294. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  295. CREATE TABLE `tmp_s8_496_guard` (
  296. probe BIGINT NOT NULL,
  297. CONSTRAINT s8_496_BLOCKED_identity_index_lost_its_column CHECK (probe = 0)
  298. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  299. INSERT INTO `tmp_s8_496_guard` (probe) VALUES ((2 - @idx_ok));
  300. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  301. -- ④ 平台主数据与配置态一行都不能少 —— 这是本脚本最重要的一条断言。
  302. -- RESET 允许清运行数据,但只要下面任何一张表被清空,就说明白名单被写错了。
  303. SET @masters := (SELECT (SELECT COUNT(*) FROM `SysUser`)
  304. + (SELECT COUNT(*) FROM `SysRole`)
  305. + (SELECT COUNT(*) FROM `EmployeeMaster`)
  306. + (SELECT COUNT(*) FROM `DepartmentMaster`));
  307. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  308. CREATE TABLE `tmp_s8_496_guard` (
  309. probe BIGINT NOT NULL,
  310. CONSTRAINT s8_496_BLOCKED_platform_master_wiped CHECK (probe = 0)
  311. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  312. INSERT INTO `tmp_s8_496_guard` (probe) VALUES (IF(@masters = 0, 1, 0));
  313. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  314. SET @configs := (SELECT (SELECT COUNT(*) FROM `ado_s8_exception_type`)
  315. + (SELECT COUNT(*) FROM `ado_s8_scene_config`)
  316. + (SELECT COUNT(*) FROM `ado_s8_notification_layer`)
  317. + (SELECT COUNT(*) FROM `ado_s8_role_permission_config`)
  318. + (SELECT COUNT(*) FROM `ado_s8_watch_rule`));
  319. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  320. CREATE TABLE `tmp_s8_496_guard` (
  321. probe BIGINT NOT NULL,
  322. CONSTRAINT s8_496_BLOCKED_product_config_wiped CHECK (probe = 0)
  323. ) COMMENT '1.0.496 断言门禁;通过后自动删除';
  324. INSERT INTO `tmp_s8_496_guard` (probe) VALUES (IF(@configs = 0, 1, 0));
  325. DROP TABLE IF EXISTS `tmp_s8_496_guard`;
  326. -- ─────────────────────────────────────────────────────────────────────────────────────
  327. -- PHASE 6 · IDEMPOTENT RERUN GUARD
  328. -- 第二次执行时:PHASE 3 的 DELETE 命中 0 行、PHASE 4 全部落到 'DO 0'、
  329. -- PHASE 5 的断言依旧成立。下面这条只是把「本脚本已收敛」这件事也记进留证表。
  330. -- ─────────────────────────────────────────────────────────────────────────────────────
  331. INSERT INTO `ado_s8_sysuser_only_reset_1_0_496` (`phase`,`table_name`,`table_class`,`row_count`,`noted_at`)
  332. VALUES ('AFTER','__migration_converged__','GUARD',1,NOW());