chore(clean): 测试数据三本账清完(库 / 磁盘 / 七牛桶)+ 100 条问题全部核销

一、dev-clean-e2e.py:加「在用组织保护」后执行
  · 先算「仍被存活任务引用的组织」并保留 —— 当前演示态就挂在其中一个
    「E2E 24数控1班」上,删了它演示态就坏(保留 3 个,演示态完好:
    任务 1449 / 名册 3 人 / 组织 43 都在)
  · 已执行 --apply --demo-tasks --orphan-files:
    49 个 e2e 租户 + 147 个测试组织 + 84 个【演示·旧】任务,
    共 3714 行 / 27 个文件(业务表按 information_schema 动态枚举,不写死清单)

二、dev-clean-qiniu.py(新增):清真实桶里本项目前缀的测试对象
  · 只动 xiaotutongxue/ 前缀;默认 dry-run;删前核实库中零引用、
    数量异常(>5000)直接停
  · 已执行:2036 个对象 / 116 MB 全部删除,删后复验 0 个(F-93「删库不删桶」)
  · 签名按官方文档逐字核对 —— 踩了两个 401 才对:旧 QBox 格式(一个换行 +
    "QBox " 前缀)不对,是「两个换行 + Qiniu 前缀」;batch 的 Body 必须进
    签名串(列举无 Body 能过、批删全 401 —— 同一签名函数一好一坏,线索在 Body)
  · 列举在 rsf.qiniuapi.com,不在 rs(打错 host 报「Bad method: list」)

三、xt-minio 容器已删(F-2)—— 代码早已不用 S3,deploy 里也没有它

四、文档核销与记录
  · docs/08:F-3/4/7/10(小节)与 F-14/26/29/30/31/38/51/57/64/65/71/72/93
    (表格行)全部标「已闭环」;F-5 补执行记录;F-28 备案维持两级(→D-11);
    变更记录补两行 —— **F-1~F-100 至此无未决项**,剩余全部等外部输入
  · docs/01:新增 E4「国标职业库数据来源待客户确认」(F-34,字典页如实显示 0 项)
  · 交接摘要:新增 §一·三十一(17 项决策执行记录)
  · AGENTS.md:清理一节写清三个工具与「在用组织保护」

验证:python scripts/verify-all.py --with-e2e →
      通过 8 / 失败 0 / 跳过 0(37 个 E2E 脚本 / 1577 条断言 / 0 失败,291s;
      prodguard 9/9;test profile 能起)
This commit is contained in:
2026-09-30 12:38:55 +08:00
parent f89cd998ef
commit 4a5790d900
6 changed files with 301 additions and 30 deletions
+48 -7
View File
@@ -117,6 +117,27 @@ def db_file_paths(db: Db) -> set[str]:
return known
def orgs_referenced_by_live_tasks(db: Db) -> set[int]:
"""
仍被**存活任务**引用的组织 id —— 这些组织绝不能删。
⚠️ 为什么必须有它:当前演示态就挂在其中一个 "E2E 24数控1班" 上
(历史原因:演示种子当时用了 E2E 建的班)。删了组织 = 演示态的审核范围、
导出目录全部失去归属,而**界面上一句话都不会提示**。
引用判断用任务表的 deleted(collect_task_org 没有 deleted 列,不能用它判活)。
"""
keep: set[int] = set()
for r in db.rows(
"SELECT DISTINCT cto.org_id FROM collect_task_org cto "
"JOIN collect_task t ON t.id = cto.task_id AND t.deleted = 0 "
"WHERE cto.org_id IS NOT NULL"):
try:
keep.add(int(r.split("|")[0]))
except ValueError:
pass
return keep
def scan_orphans(db: Db) -> tuple[list[str], int]:
"""磁盘上有、而库里没有任何一行指向的文件。"""
known = db_file_paths(db)
@@ -155,9 +176,16 @@ def main() -> int:
tenants = [] if args.only_orphan_files else db.rows(
f"SELECT id || '|' || tenant_code || '|' || tenant_name "
f"FROM sys_tenant WHERE {TENANT_FILTER} ORDER BY id")
demo_orgs = [] if args.only_orphan_files else db.rows(
f"SELECT id || '|' || org_name FROM sys_org "
f"WHERE deleted = 0 AND ({DEMO_ORG_FILTER}) ORDER BY id")
# 在用组织保护:先算好(任何删除发生之前),删除阶段只动"没被引用的"
keep_orgs = orgs_referenced_by_live_tasks(db) if not args.only_orphan_files else set()
demo_orgs = [] if args.only_orphan_files else [
r for r in db.rows(
f"SELECT id || '|' || org_name FROM sys_org "
f"WHERE deleted = 0 AND ({DEMO_ORG_FILTER}) ORDER BY id")
if int(r.split("|")[0]) not in keep_orgs
]
kept_note = ("(另有 %d 个测试组织被存活任务引用,**保留**:其中就有当前演示态挂着的班)"
% len(keep_orgs)) if keep_orgs else ""
demo_tasks = (db.rows(f"SELECT id || '|' || task_name FROM collect_task "
f"WHERE deleted = 0 AND {DEMO_TASK_FILTER} ORDER BY id")
if (args.demo_tasks and not args.only_orphan_files) else [])
@@ -176,7 +204,7 @@ def main() -> int:
print(f" ... 另有 {len(tenants) - 15} 个")
if demo_orgs:
print(f"\n② 其它租户下的 e2e 测试组织 {len(demo_orgs)} 个:")
print(f"\n② 其它租户下的 e2e 测试组织 {len(demo_orgs)} 个{kept_note}:")
for r in demo_orgs[:15]:
p = r.split("|")
print(" id=%-5s %s" % (p[0], p[1] if len(p) > 1 else ""))
@@ -228,9 +256,22 @@ def main() -> int:
total += n
print(" %-10s %-22s 删除 %d 行" % ("租户", "sys_tenant", n))
n = len(db.rows(f"DELETE FROM sys_org WHERE {DEMO_ORG_FILTER} RETURNING id"))
total += n
print(" %-10s %-22s 删除 %d 行" % ("测试组织", "sys_org", n))
# 测试组织:按 id 删(keep_orgs 已在预览阶段剔除;这里再校验一次,
# 因为上面可能刚删过任务,引用集合应该只减不增 —— 双保险,宁可少删)
keep_orgs = orgs_referenced_by_live_tasks(db) if not args.only_orphan_files else set()
org_ids = [int(r.split("|")[0]) for r in db.rows(
f"SELECT id FROM sys_org WHERE deleted = 0 AND ({DEMO_ORG_FILTER}) ORDER BY id")
if int(r.split("|")[0]) not in keep_orgs]
if org_ids:
# 一次 IN 里塞太多 id 会撑爆语句,分批(144 个一批也行,但分批无成本)
for i in range(0, len(org_ids), 200):
batch = org_ids[i:i + 200]
n = len(db.rows("DELETE FROM sys_org WHERE id IN (%s) RETURNING id"
% ",".join(map(str, batch))))
total += n
print(" %-10s %-22s 删除 %d 行" % ("测试组织", "sys_org", n))
else:
print(" %-10s %-22s 没有可删的(都在用或已清)" % ("测试组织", "sys_org"))
if args.demo_tasks:
# 先按任务 id 删子表(同样动态枚举"带 task_id 的表")