Files
tools-show/docs/2026-03-27-12-09-设计-下载大文件功能.md
dlandy b627f8c020 init
2026-03-30 09:36:36 +08:00

8.6 KiB
Raw Blame History

设计下载大文件功能v1

  • 文档类别:设计(系统设计)
  • 创建时间2026-03-27 12:09 (Asia/Shanghai)
  • 适用项目ToolsShowNestJS + Prisma + GitLab Generic Package
  • 关联模块:accessdownloadsgitlab-storage

1. 背景与问题

当前下载链路(POST /tools/:id/launch -> GET /downloads/:ticket)可以完成普通文件下载,但在大文件场景存在明显短板:

  1. 现有 ticket 为“一次性消费”(consumedAt 写入后不可重用),网络中断后无法原 ticket 续传。
  2. GET /downloads/:ticket 仅做整文件流式透传,没有 Range / 206 Partial Content,无法断点续传。
  3. 对下载过程缺少阶段化记录(开始、部分成功、失败重试),难以统计真实下载质量。
  4. 大文件下载失败时用户体验较差(必须回到前端重新触发 launch

2. 目标与非目标

2.1 目标

  1. 支持标准 HTTP 断点续传(RangeAccept-RangesContent-Range206)。
  2. 网络中断后允许在有效期内继续下载,不要求重新 launch。
  3. 在不改变“统一 launch 入口”前提下完成兼容升级。
  4. 增加大文件下载可观测性(失败率、平均耗时、重试次数、完成率)。
  5. 兼容两种存储后端GitLab 远端包与本地文件回退存储。

2.2 非目标(本期不做)

  1. P2P/BT 分发。
  2. CDN 回源策略编排。
  3. 客户端多线程分片加速协议(先支持标准浏览器与下载器)。

3. 设计原则

  1. 兼容优先:旧接口可保留短期兼容,前端可灰度切换。
  2. 安全优先:令牌短期有效、可撤销、与工具/制品强绑定。
  3. 可恢复优先:会话级下载权限 > 一次性 ticket。
  4. 可运维优先:必须可追踪失败原因与瓶颈位置(应用层/存储层/网络层)。

4. 总体方案

采用“下载会话Download Session+ Range 流式传输”替代“一次性 ticket + 全量下载”。

4.1 核心变化

  1. 下载模式 launch 不再返回一次性 ticket而是返回可续传会话 token。
  2. 新下载接口支持 HEADGET + Range
  3. 会话在有效期内可多次请求同一文件不同字节区间。
  4. 下载记录改为“会话聚合 + 分段记录”,用于分析中断与重试。

4.2 兼容策略

  1. 保留 GET /downloads/:ticket1-2 个版本周期。
  2. 新增 GET /downloads/sessions/:token/file(新)。
  3. 前端先读 launch 返回字段,若存在 sessionToken 则走新链路,否则走旧链路。

5. 数据模型设计

以下为设计层面的 Prisma 结构草案,具体字段可在实现阶段微调。

5.1 新增表:download_sessions

  • idString (UUID)
  • sessionTokenString (Unique)
  • toolIdString
  • artifactIdString
  • channelString?
  • clientVersionString?
  • requestIpString?
  • userAgentString?
  • expiresAtDateTime
  • lastAccessAtDateTime
  • completedAtDateTime?
  • revokedAtDateTime?
  • createdAtDateTime

索引建议:

  • (sessionToken unique)
  • (expiresAt)
  • (artifactId, expiresAt)

5.2 新增表:download_session_chunks(可选但建议)

  • idInt (auto increment)
  • sessionIdString
  • rangeStartBigInt
  • rangeEndBigInt
  • bytesSentBigInt
  • statussuccess | failed | cancelled
  • errorMessageString?
  • durationMsInt?
  • createdAtDateTime

说明:用于分析大文件下载过程中的断点位置、失败区间、重试质量。

6. API 设计

Base path: /api/v1

6.1 Launch下载模式响应升级

POST /tools/:id/launch

下载模式响应示例:

{
  "mode": "download",
  "sessionToken": "dl_sess_xxx",
  "expiresInSec": 3600,
  "actionUrl": "/api/v1/downloads/sessions/dl_sess_xxx/file",
  "resumeSupported": true,
  "file": {
    "name": "tool-v2.1.0.zip",
    "size": 2147483648,
    "sha256": "..."
  }
}

6.2 文件元信息探测

HEAD /downloads/sessions/:token/file

返回:

  • Accept-Ranges: bytes
  • Content-Length
  • ETag(建议使用 artifact sha256
  • Content-Disposition

6.3 下载文件(支持 Range

GET /downloads/sessions/:token/file

请求头:

  • 可选 Range: bytes=0-1048575

响应:

  • 无 Range200 + 全量流
  • 有效 Range206 + 指定区间流
  • 非法 Range416

关键响应头:

  • Accept-Ranges: bytes
  • Content-Range
  • Content-Length
  • Content-Type
  • Content-Disposition
  • ETag

6.4 会话失效

  • 过期或撤销:410 Gone
  • 无效 token404401(按安全策略统一)

7. 服务端实现设计

7.1 AccessService 改造

文件:server/src/modules/access/access.service.ts

  1. 下载模式不再创建一次性 downloadTicket,改为创建 downloadSession
  2. 默认会话 TTL 建议 1 小时(可配置 DOWNLOAD_SESSION_TTL_SEC)。
  3. 返回 sessionToken + actionUrl + file meta

7.2 DownloadsController 改造

文件:server/src/modules/downloads/downloads.controller.ts

新增路由:

  • HEAD /downloads/sessions/:token/file
  • GET /downloads/sessions/:token/file

保留旧路由:

  • GET /downloads/:ticket(兼容期)

7.3 DownloadsService 改造

文件:server/src/modules/downloads/downloads.service.ts

新增能力:

  1. 解析并校验 Range。
  2. 校验会话有效性、工具状态、制品状态。
  3. 根据 Range 组装响应头并返回 200/206/416
  4. 在响应关闭/中断时记录 chunk 结果。
  5. 当客户端拿到完整文件后标记 completedAt(可通过全量下载成功或累计字节判定)。

7.4 GitlabStorageService 改造

文件:server/src/modules/gitlab-storage/gitlab-storage.service.ts

新增方法建议:

  • getArtifactStream(artifact, range?)
  • headArtifact(artifact)

实现要点:

  1. 远端 GitLab 下载请求透传 Range 头。
  2. 若 GitLab 返回 206,直接桥接状态码与头。
  3. 若远端不支持 Range则回退为 200 全量(并在响应中标记 resumeSupported=false)。
  4. 本地文件场景使用 createReadStream(path, { start, end })

8. 安全与风控

  1. sessionToken 使用高熵随机串;数据库只保存 hash推荐
  2. 会话与 toolId/artifactId 强绑定,防止跨资源复用。
  3. 限制并发分段请求数(例如单会话最多 4 并发)。
  4. 单 IP / 单工具限流,防止恶意刷取带宽。
  5. 所有下载响应增加 X-Content-Type-Options: nosniff

9. 观测指标

  1. download_session_started_total
  2. download_chunk_success_total
  3. download_chunk_failed_total
  4. download_session_completed_total
  5. download_resume_ratio(续传请求占比)
  6. download_5xx_ratio
  7. p95_chunk_duration_ms

日志字段建议:traceIdsessionIdartifactIdrangeStartrangeEndbytesSentstatuserrorCode

10. 迁移与发布计划

Phase 1后端可用

  1. 落库 download_sessions
  2. 新增会话下载接口 + Range 支持。
  3. Access 返回新字段。
  4. 保留旧 ticket 接口。

Phase 2前端切换

  1. 前端优先走 sessionToken 新链路。
  2. 引入失败重试与断点续传提示。
  3. 观察 1 周核心指标。

Phase 3收敛

  1. 宣布下线旧 ticket 下载接口。
  2. 清理旧表和兼容逻辑(按版本策略执行)。

11. 风险与应对

  1. GitLab Range 兼容性不一致先做能力探测HEAD/小范围 GET不支持时降级。
  2. 高并发导致服务端带宽占满:增加会话并发限制 + 网关限流。
  3. 大文件长连接导致 Node 资源占用:严格使用流式处理,避免读入内存;设置连接超时与中断清理。
  4. 统计口径变化:区分“会话完成”与“分段成功”,避免误解下载成功率。

12. 验收标准

  1. 2GB 文件可在中断后 5 分钟内基于同一 sessionToken 成功续传。
  2. Range 请求返回符合 RFC 7233 的响应码与头部。
  3. 下载中断、失败、成功均有可追踪日志。
  4. 旧版前端不改动时仍可通过旧 ticket 接口下载。

13. 对应当前代码的最小改造清单

  1. access.service.ts:创建并返回 downloadSession
  2. downloads.controller.ts:新增 HEAD/GET /downloads/sessions/:token/file
  3. downloads.service.ts实现会话校验、Range 解析、206/416 响应、chunk 记录。
  4. gitlab-storage.service.ts:支持 Range 透传与本地分段读取。
  5. prisma/schema.prisma:新增会话与分段记录模型。

本设计文档用于“下载大文件功能”研发基线,可在进入实现阶段前补充更细的 DTO、错误码扩展和数据库 migration 细节。