R45:C8 只清了存量(1003 个 / 9.62 GB),「孤儿为何持续产生」的三条根因仍在,本次一并治理。
顺序铁律「先库后盘」:文件删除不可回滚,若先删盘而 DB 写/事务失败 ⇒ DB 指向已删文件
⇒ 下载 404 = 数据丢失(比留一个孤儿严重)。故全部删除点一律「DB 侧确定成功 → 才删物理文件」。
- app/service/upload.js
· saveSubmissionRecord:覆盖前记住旧物理文件路径,existing.update() 成功之后才删旧件
(原先 upsert 只改 DB 指向 ⇒ 重传 N 次 = 1 条 DB + N-1 个孤儿)
· deleteSubmission:裸 fs.unlinkSync → 复用统一入口
· 新增 removePublicFile(relativePath, label):唯一删除入口。仅允许 works/ 或 tmp/ 前缀
(挡住脏 file_path 指向 templates/、carousel/ 等静态资源被连带删除)+ 拒含 `..`
+ best-effort 永不抛错,返回 removed|missing|skipped(复用 helper.safeCleanup)
- app/controller/upload.js
· upload() / mergeChunks():新增 savedFileInfo|mergedRelPath + recordPersisted 两个标志,
catch 里仅当「已落盘 && 未入库」才回滚删除 ⇒ 已入库还删会删掉正被 DB 引用的文件
- app/controller/admin.js
· resetTestData:事务内先只读收集六列路径(exam/work/zhongzhi/gaozhi/benke/jiaoshi),
tx.commit() 之后才删物理文件 + 去重 + 上报 orphanFileCandidates/orphanFilesRemoved
验证:verify-c9.mjs 24/24 · verify-c9-reset.mjs 12/12 · verify-c9-rollback.mjs 13/13
check_upload_import_gate.py 14 PASS/0 FAIL · 后端重启(node dev.js)后真实接口联调通过
配套:scripts/scan_orphan_files.py(兜底双向扫描,仓库级工具)
16 KiB
16 KiB