diff --git a/docs/接口/旧规则绑定表下线观察与删表草案.md b/docs/接口/旧规则绑定表下线观察与删表草案.md new file mode 100644 index 0000000..192d21c --- /dev/null +++ b/docs/接口/旧规则绑定表下线观察与删表草案.md @@ -0,0 +1,73 @@ +# 旧规则绑定表下线观察与删表草案 + +## 当前策略 + +- 新主链路:`leaudit_rule_group_bindings` +- 旧兼容表:`leaudit_rule_type_bindings` +- 当前仅保留 `UpdateBinding / DeleteBinding` 对历史 `BindingId` 的 fallback + +## 已完成 + +- 文档类型保存不再双写旧表 +- `CreateBinding` 不再 fallback 写旧表 +- 规则绑定主读链路优先读取新分组绑定 + +## 观察期要看什么 + +### 1. 后端日志 + +当前已经在以下场景增加明确日志: + +- `rule binding legacy fallback hit on update` +- `rule binding legacy fallback hit on delete` + +如果观察期内不再出现以上日志,说明已经基本没有历史旧 `BindingId` 命中。 + +### 2. 数据检查 + +执行: + +```bash +psql ... -f scripts/precheck_drop_legacy_rule_type_bindings.sql +``` + +重点看: + +- `doc_types_legacy_only = 0` +- 新链路绑定明细完整 +- 旧表活动绑定仅剩历史冗余数据 + +## 建议收口顺序 + +1. 保持现状进入观察期 +2. 连续一段时间无 fallback 日志 +3. 删除 `UpdateBinding / DeleteBinding` 的旧表 fallback +4. 再执行删表 SQL + +## 删表前条件 + +- 前端主链路全部走评查点分组绑定接口 +- 不再有外部脚本调用旧 `/api/rule-sets/bindings/{bindingId}` 历史 ID +- 观察期日志为 0 +- `doc_types_legacy_only = 0` + +## 删表 SQL 草案 + +```sql +BEGIN; + +DROP TABLE IF EXISTS leaudit_rule_type_bindings; + +COMMIT; +``` + +## 回滚草案 + +如果只是删 fallback 代码但还没删表,直接回滚代码即可。 + +如果已经删表,必须依赖: + +- 事先数据库备份 +- 或按历史 DDL 重建表结构后再回灌数据 + +因此删表动作必须放在数据库备份之后执行。 diff --git a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py index 4268301..361f69e 100644 --- a/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py +++ b/fastapi_modules/fastapi_leaudit/services/impl/ruleServiceImpl.py @@ -3,6 +3,7 @@ from __future__ import annotations import hashlib +import logging from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException @@ -21,6 +22,8 @@ from fastapi_modules.fastapi_leaudit.services import IOssService, IRuleService from fastapi_modules.fastapi_leaudit.services.impl.ossServiceImpl import OssServiceImpl from fastapi_modules.fastapi_leaudit.services.impl.ruleGroupSupport import sync_doc_type_bindings_from_group +LOGGER = logging.getLogger(__name__) + class RuleServiceImpl(IRuleService): """规则服务实现。""" @@ -756,6 +759,13 @@ class RuleServiceImpl(IRuleService): if not Row: raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "绑定记录不存在") + LOGGER.warning( + "rule binding legacy fallback hit on update: binding_id=%s doc_type_id=%s rule_set_id=%s", + BindingId, + Row.get("doc_type_id"), + Row.get("rule_set_id"), + ) + SetClauses: list[str] = [] Params: dict[str, object] = {"bid": BindingId} @@ -838,6 +848,11 @@ class RuleServiceImpl(IRuleService): text("DELETE FROM leaudit_rule_type_bindings WHERE id = :bid"), {"bid": BindingId}, ) + if Result.rowcount: + LOGGER.warning( + "rule binding legacy fallback hit on delete: binding_id=%s", + BindingId, + ) await Session.commit() if Result.rowcount == 0: raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "绑定记录不存在") diff --git a/scripts/precheck_drop_legacy_rule_type_bindings.sql b/scripts/precheck_drop_legacy_rule_type_bindings.sql new file mode 100644 index 0000000..cb99588 --- /dev/null +++ b/scripts/precheck_drop_legacy_rule_type_bindings.sql @@ -0,0 +1,78 @@ +-- 删除 leaudit_rule_type_bindings 前的检查 SQL(仅检查,不执行删除) +-- 使用方式: +-- psql ... -f scripts/precheck_drop_legacy_rule_type_bindings.sql + +\echo '=== 1. 文档类型总量 / 新旧绑定覆盖情况 ===' +WITH doc_types AS ( + SELECT id + FROM leaudit_document_types + WHERE deleted_at IS NULL +), +new_bindings AS ( + SELECT DISTINCT child.document_type_id + FROM leaudit_evaluation_point_groups child + JOIN leaudit_rule_group_bindings rgb + ON rgb.group_id = child.id + AND rgb.deleted_at IS NULL + AND rgb.is_active = TRUE + WHERE child.deleted_at IS NULL + AND COALESCE(child.pid, 0) <> 0 + AND child.document_type_id IS NOT NULL +), +legacy_bindings AS ( + SELECT DISTINCT doc_type_id + FROM leaudit_rule_type_bindings + WHERE deleted_at IS NULL + AND is_active = TRUE +) +SELECT + (SELECT COUNT(*) FROM doc_types) AS doc_types_total, + (SELECT COUNT(*) FROM new_bindings) AS doc_types_with_new_binding, + (SELECT COUNT(*) FROM legacy_bindings) AS doc_types_with_legacy_binding, + ( + SELECT COUNT(*) + FROM legacy_bindings lb + WHERE NOT EXISTS ( + SELECT 1 + FROM new_bindings nb + WHERE nb.document_type_id = lb.doc_type_id + ) + ) AS doc_types_legacy_only; + +\echo '=== 2. 仍可能依赖旧表的活动绑定明细 ===' +SELECT + b.id, + b.doc_type_id, + b.doc_type_code, + b.rule_set_id, + b.binding_mode, + b.priority, + b.region, + b.note +FROM leaudit_rule_type_bindings b +WHERE b.deleted_at IS NULL + AND b.is_active = TRUE +ORDER BY b.doc_type_id ASC, b.priority DESC, b.id ASC; + +\echo '=== 3. 新链路活动绑定明细 ===' +SELECT + rgb.id AS group_binding_id, + child.id AS child_group_id, + child.document_type_id, + rgb.rule_set_id, + rgb.priority, + rgb.is_active, + rgb.note +FROM leaudit_rule_group_bindings rgb +JOIN leaudit_evaluation_point_groups child + ON child.id = rgb.group_id +WHERE rgb.deleted_at IS NULL + AND rgb.is_active = TRUE + AND child.deleted_at IS NULL + AND COALESCE(child.pid, 0) <> 0 +ORDER BY child.document_type_id ASC, child.sort_order ASC, rgb.priority DESC, rgb.id ASC; + +\echo '=== 4. 删除前必须人工确认 ===' +\echo '1) 观察期内未再出现 legacy fallback update/delete 日志' +\echo '2) doc_types_legacy_only = 0' +\echo '3) 前端/脚本/外部调用不再使用 /api/rule-sets/bindings 历史 BindingId'