data-download-code-review-2026-09-19.md 7.1 KB

DataDownload 代码审核复核与修复报告

初审日期:2026-09-19
复核及修复日期:2026-09-19
审核范围:当前 git 未提交改动,并结合当时的集成设计归档进行权限、FileRequest、生命周期和存储约束复核。当前文档入口见 README.md。 关联台账:data-download-open-issues.md

1. 结论

原报告 11 条结论中:CR-02、CR-05、CR-06、CR-08 是实际问题;CR-04、CR-07、CR-10、CR-11 是低风险一致性或完整性问题;CR-01、CR-03、CR-09 不成立或没有足够证据。

本轮已经修复确认成立且适合在当前范围内处理的问题,并补充修复复核时发现的任务计数错误。CR-08 的 HTTP 下载内存放大已经解决,但 ZIP 构建、Renderer 首次生成和上游二进制读取仍属于 DD-010,不能标记为整体解决。

2. 逐条复核与处理状态

ID 原严重度 复核结论 当前状态 处理结果
CR-01 高 不成立 rejected 当前没有同 key 嵌套加锁;binary 与 rendered 分支互斥。不可重入只属于未来维护约束。
CR-02 高 成立 resolved 上游请求改用独立、自动续租的 FileRequest lease;content 锁仅覆盖提交阶段;等待者通过 Asynq 延迟重试。
CR-03 高 证据不足 rejected 短数组补 nil 与旧 PowerFlow 导出语义一致;新增测试固定该兼容行为。
CR-04 中 低风险类型不一致 resolved run_status 改用 *int32,超出 PostgreSQL integer 范围时显式报错。
CR-05 中 成立 resolved item 写入改为 (taskId,itemKey) upsert 并回填已有 _id;稳定队列 TaskID 冲突视为已调度。
CR-06 中 成立 resolved 清理时统一使用 ExpiresAt + 30天。
CR-07 中 字段未维护属实,影响被夸大 resolved Recount 统计空 TaskDocument,前端任务进度显示空文档数。
CR-08 中 成立 partially resolved Package、二进制文档、缓存 RenderArtifact 已流式响应;生成和打包阶段的内存问题继续由 DD-010 跟踪。
CR-09 中 不成立 rejected 设计明确允许其他登录用户查看、预览和下载;所有 /api 路由已有统一认证。
CR-10 低 防御性建议 resolved 删除弱类型 mapToBSON,改用显式 bson.M。
CR-11 低 文档建议 resolved 在 PowerFlow 持久化模型中明确本地 wall-clock 与 UTC instant 的语义。

3. 关键修复说明

3.1 FileRequest 协调与 EMS 长轮询

  • 新增 data-download:request:{DocumentID} 请求租约,RequestID 使用唯一 lease token。
  • 租约每 40 秒续期,TTL 为 2 分钟;续租失败后提交校验会拒绝继续发布。
  • Mutable 等待者携带 AwaitRequestID,按 15、30、60 秒退避重新入队;等待期间 item 保持 pending,不占用 worker。
  • 同轮 Mutable 请求完成后,等待者只复用对应 RequestID 的结果;之后创建的新任务仍会刷新 Mutable 文档。
  • Dynamic/UpdateLog 等待前一轮完成后重新竞争租约,不复用上一轮结果。
  • 上游 EMS 轮询和文件下载不再持有 content 锁;content 锁只在上传及元数据切换阶段持有。
  • 管理员删除文档时同时竞争 request lease,活跃请求期间禁止物理删除。

这解决了 CR-02,并落实了设计文档 3.2、8.2、9.1、9.2 的主要行为。普通 content/Renderer 锁的完整 fencing/CAS 仍由 DD-001、DD-007 跟踪。

3.2 规划幂等

InsertDataDownloadItems 已由 InsertMany 改为无序 BulkWrite upsert。重复规划会取得已存在 item 的 _id,不会因唯一索引冲突失败;重复提交稳定 Asynq TaskID 时也不会把 item 错误标记为失败。该修复同时覆盖 plan handler 和 site_validation 重规划路径。

3.3 任务计数

除原报告指出的 Empty 未写入外,复核还发现两个遗漏:

  • 某种状态消失时,旧 succeeded/failed 值不会归零;
  • aggregate 将 skipped 视为成功,但 Recount 未计入 succeeded。

当前 Recount 会先清零计数、将 success + skipped 汇总为 succeeded,并从 TaskDocument 统计空文档数。

3.4 流式下载和 Package 生命周期

  • Package、二进制文档和 RenderArtifact 下载改为 SendStream。
  • 流式读取期间持有并续租对应锁,响应关闭后释放。
  • Package 下载、人工删除和定时清理使用同一个 Package 锁,避免流式下载期间删除对象。
  • Package 清理不再重算为 清理时间+30天,而是固定使用 ExpiresAt+30天。

ZIP 仍完整构建在内存中,Renderer 首次生成仍产生完整 []byte,因此 DD-010 保持 open。

4. 被否决的原结论

CR-01

BuildDataDownloadPackage 的 binary 分支自行取得 content 锁,structured 分支则直接进入 ReadRenderedDataDownloadDocument;两者不会在同一调用路径对同一 key 二次加锁。原报告描述的是假设未来修改后的风险,不是当前缺陷。

CR-03

旧 PowerFlow 导出和前端转换均以时间轴为基准,并允许较短的并行数组以空值补齐。当前实现拒绝长于时间轴的数组、保留短数组中的“缺测值”语义,没有证据证明应强制全部等长。新增单元测试防止未来把兼容行为误改为报错。

CR-09

设计文档 12.7 明确规定:其他用户可以查看任务和文件、预览、下载及批量下载,但不能修改、取消、重试或删除。归档读取属于文件查看/下载,且 /api 已由统一认证中间件保护,因此不能增加 owner/admin 限制。

5. REPO-006 更正说明

原报告建议把 REPO-006 更正为 GetAlarmSnapshotData,该建议不正确。

REPO-006 描述的是旧测试 service/test/getSnapTest/getAlarmSnapshotTest.go 仍调用不存在的 jobs.GetAlarmSnapshot;DataDownload 新代码调用存在的 jobs.GetAlarmSnapshotData。两项事实同时成立,问题台账无需更正。

6. 验证结果

  • go test ./services ./controllers ./repositories/mongo ./repositories/pgsql ./repositories/minio ./task/queue/dataDownloadQueue ./cache/redisx ./database:通过。
  • 新增 PowerFlow 回归测试:短并行数组补空;越界 run_status 被拒绝。
  • pnpm type-check:通过。
  • pnpm exec eslint src/service/dataDownload src/views/data-download:通过。
  • git diff --check:通过。
  • go test ./...:仍被 REPO-001~007 的既有源码问题阻断;DataDownload 相关包未出现新增失败。

7. 仍需继续处理的风险

  • DD-001:普通 content/Renderer 锁尚未全部统一为带 fencing 的租约。
  • DD-002、DD-007、DD-008:PostgreSQL、Mongo Document、RenderSlot/Artifact 仍没有完整崩溃一致性和发布 CAS。
  • DD-009:二进制上传后 Mongo 切换失败仍可能产生孤儿对象。
  • DD-010:ZIP 构建、Renderer 首次生成和上游二进制获取仍有内存放大。
  • DD-015:归档内部条目读取仍缺少大小上限。
  • DD-023:缺少 Redis/Asynq/MongoDB/PostgreSQL/MinIO 跨进程与故障注入测试。