chore: add legacy binding fallback audit logs
This commit is contained in:
@@ -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 重建表结构后再回灌数据
|
||||||
|
|
||||||
|
因此删表动作必须放在数据库备份之后执行。
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
from fastapi_common.fastapi_common_sqlalchemy.database import GetAsyncSession
|
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.domain.responses import StatusCodeEnum
|
||||||
from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException
|
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.ossServiceImpl import OssServiceImpl
|
||||||
from fastapi_modules.fastapi_leaudit.services.impl.ruleGroupSupport import sync_doc_type_bindings_from_group
|
from fastapi_modules.fastapi_leaudit.services.impl.ruleGroupSupport import sync_doc_type_bindings_from_group
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RuleServiceImpl(IRuleService):
|
class RuleServiceImpl(IRuleService):
|
||||||
"""规则服务实现。"""
|
"""规则服务实现。"""
|
||||||
@@ -756,6 +759,13 @@ class RuleServiceImpl(IRuleService):
|
|||||||
if not Row:
|
if not Row:
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "绑定记录不存在")
|
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] = []
|
SetClauses: list[str] = []
|
||||||
Params: dict[str, object] = {"bid": BindingId}
|
Params: dict[str, object] = {"bid": BindingId}
|
||||||
|
|
||||||
@@ -838,6 +848,11 @@ class RuleServiceImpl(IRuleService):
|
|||||||
text("DELETE FROM leaudit_rule_type_bindings WHERE id = :bid"),
|
text("DELETE FROM leaudit_rule_type_bindings WHERE id = :bid"),
|
||||||
{"bid": BindingId},
|
{"bid": BindingId},
|
||||||
)
|
)
|
||||||
|
if Result.rowcount:
|
||||||
|
LOGGER.warning(
|
||||||
|
"rule binding legacy fallback hit on delete: binding_id=%s",
|
||||||
|
BindingId,
|
||||||
|
)
|
||||||
await Session.commit()
|
await Session.commit()
|
||||||
if Result.rowcount == 0:
|
if Result.rowcount == 0:
|
||||||
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "绑定记录不存在")
|
raise LeauditException(StatusCodeEnum.HTTP_404_NOT_FOUND, "绑定记录不存在")
|
||||||
|
|||||||
@@ -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'
|
||||||
Reference in New Issue
Block a user