8.6 KiB
8.6 KiB
设计:下载大文件功能(v1)
- 文档类别:设计(系统设计)
- 创建时间:2026-03-27 12:09 (Asia/Shanghai)
- 适用项目:ToolsShow(NestJS + Prisma + GitLab Generic Package)
- 关联模块:
access、downloads、gitlab-storage
1. 背景与问题
当前下载链路(POST /tools/:id/launch -> GET /downloads/:ticket)可以完成普通文件下载,但在大文件场景存在明显短板:
- 现有 ticket 为“一次性消费”(
consumedAt写入后不可重用),网络中断后无法原 ticket 续传。 GET /downloads/:ticket仅做整文件流式透传,没有Range/206 Partial Content,无法断点续传。- 对下载过程缺少阶段化记录(开始、部分成功、失败重试),难以统计真实下载质量。
- 大文件下载失败时用户体验较差(必须回到前端重新触发 launch)。
2. 目标与非目标
2.1 目标
- 支持标准 HTTP 断点续传(
Range、Accept-Ranges、Content-Range、206)。 - 网络中断后允许在有效期内继续下载,不要求重新 launch。
- 在不改变“统一 launch 入口”前提下完成兼容升级。
- 增加大文件下载可观测性(失败率、平均耗时、重试次数、完成率)。
- 兼容两种存储后端:GitLab 远端包与本地文件回退存储。
2.2 非目标(本期不做)
- P2P/BT 分发。
- CDN 回源策略编排。
- 客户端多线程分片加速协议(先支持标准浏览器与下载器)。
3. 设计原则
- 兼容优先:旧接口可保留短期兼容,前端可灰度切换。
- 安全优先:令牌短期有效、可撤销、与工具/制品强绑定。
- 可恢复优先:会话级下载权限 > 一次性 ticket。
- 可运维优先:必须可追踪失败原因与瓶颈位置(应用层/存储层/网络层)。
4. 总体方案
采用“下载会话(Download Session)+ Range 流式传输”替代“一次性 ticket + 全量下载”。
4.1 核心变化
- 下载模式 launch 不再返回一次性 ticket,而是返回可续传会话 token。
- 新下载接口支持
HEAD与GET + Range。 - 会话在有效期内可多次请求同一文件不同字节区间。
- 下载记录改为“会话聚合 + 分段记录”,用于分析中断与重试。
4.2 兼容策略
- 保留
GET /downloads/:ticket(旧)1-2 个版本周期。 - 新增
GET /downloads/sessions/:token/file(新)。 - 前端先读 launch 返回字段,若存在
sessionToken则走新链路,否则走旧链路。
5. 数据模型设计
以下为设计层面的 Prisma 结构草案,具体字段可在实现阶段微调。
5.1 新增表:download_sessions
id:String (UUID)sessionToken:String (Unique)toolId:StringartifactId:Stringchannel:String?clientVersion:String?requestIp:String?userAgent:String?expiresAt:DateTimelastAccessAt:DateTimecompletedAt:DateTime?revokedAt:DateTime?createdAt:DateTime
索引建议:
(sessionToken unique)(expiresAt)(artifactId, expiresAt)
5.2 新增表:download_session_chunks(可选但建议)
id:Int (auto increment)sessionId:StringrangeStart:BigIntrangeEnd:BigIntbytesSent:BigIntstatus:success | failed | cancellederrorMessage:String?durationMs:Int?createdAt:DateTime
说明:用于分析大文件下载过程中的断点位置、失败区间、重试质量。
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: bytesContent-LengthETag(建议使用 artifact sha256)Content-Disposition
6.3 下载文件(支持 Range)
GET /downloads/sessions/:token/file
请求头:
- 可选
Range: bytes=0-1048575
响应:
- 无 Range:
200+ 全量流 - 有效 Range:
206+ 指定区间流 - 非法 Range:
416
关键响应头:
Accept-Ranges: bytesContent-RangeContent-LengthContent-TypeContent-DispositionETag
6.4 会话失效
- 过期或撤销:
410 Gone - 无效 token:
404或401(按安全策略统一)
7. 服务端实现设计
7.1 AccessService 改造
文件:server/src/modules/access/access.service.ts
- 下载模式不再创建一次性
downloadTicket,改为创建downloadSession。 - 默认会话 TTL 建议 1 小时(可配置
DOWNLOAD_SESSION_TTL_SEC)。 - 返回
sessionToken + actionUrl + file meta。
7.2 DownloadsController 改造
文件:server/src/modules/downloads/downloads.controller.ts
新增路由:
HEAD /downloads/sessions/:token/fileGET /downloads/sessions/:token/file
保留旧路由:
GET /downloads/:ticket(兼容期)
7.3 DownloadsService 改造
文件:server/src/modules/downloads/downloads.service.ts
新增能力:
- 解析并校验 Range。
- 校验会话有效性、工具状态、制品状态。
- 根据 Range 组装响应头并返回
200/206/416。 - 在响应关闭/中断时记录 chunk 结果。
- 当客户端拿到完整文件后标记
completedAt(可通过全量下载成功或累计字节判定)。
7.4 GitlabStorageService 改造
文件:server/src/modules/gitlab-storage/gitlab-storage.service.ts
新增方法建议:
getArtifactStream(artifact, range?)headArtifact(artifact)
实现要点:
- 远端 GitLab 下载请求透传
Range头。 - 若 GitLab 返回
206,直接桥接状态码与头。 - 若远端不支持 Range,则回退为
200全量(并在响应中标记resumeSupported=false)。 - 本地文件场景使用
createReadStream(path, { start, end })。
8. 安全与风控
sessionToken使用高熵随机串;数据库只保存 hash(推荐)。- 会话与
toolId/artifactId强绑定,防止跨资源复用。 - 限制并发分段请求数(例如单会话最多 4 并发)。
- 单 IP / 单工具限流,防止恶意刷取带宽。
- 所有下载响应增加
X-Content-Type-Options: nosniff。
9. 观测指标
download_session_started_totaldownload_chunk_success_totaldownload_chunk_failed_totaldownload_session_completed_totaldownload_resume_ratio(续传请求占比)download_5xx_ratiop95_chunk_duration_ms
日志字段建议:traceId、sessionId、artifactId、rangeStart、rangeEnd、bytesSent、status、errorCode。
10. 迁移与发布计划
Phase 1(后端可用)
- 落库
download_sessions。 - 新增会话下载接口 + Range 支持。
- Access 返回新字段。
- 保留旧 ticket 接口。
Phase 2(前端切换)
- 前端优先走
sessionToken新链路。 - 引入失败重试与断点续传提示。
- 观察 1 周核心指标。
Phase 3(收敛)
- 宣布下线旧 ticket 下载接口。
- 清理旧表和兼容逻辑(按版本策略执行)。
11. 风险与应对
- GitLab Range 兼容性不一致:先做能力探测(HEAD/小范围 GET),不支持时降级。
- 高并发导致服务端带宽占满:增加会话并发限制 + 网关限流。
- 大文件长连接导致 Node 资源占用:严格使用流式处理,避免读入内存;设置连接超时与中断清理。
- 统计口径变化:区分“会话完成”与“分段成功”,避免误解下载成功率。
12. 验收标准
- 2GB 文件可在中断后 5 分钟内基于同一
sessionToken成功续传。 Range请求返回符合 RFC 7233 的响应码与头部。- 下载中断、失败、成功均有可追踪日志。
- 旧版前端不改动时仍可通过旧 ticket 接口下载。
13. 对应当前代码的最小改造清单
access.service.ts:创建并返回downloadSession。downloads.controller.ts:新增HEAD/GET /downloads/sessions/:token/file。downloads.service.ts:实现会话校验、Range 解析、206/416 响应、chunk 记录。gitlab-storage.service.ts:支持 Range 透传与本地分段读取。prisma/schema.prisma:新增会话与分段记录模型。
本设计文档用于“下载大文件功能”研发基线,可在进入实现阶段前补充更细的 DTO、错误码扩展和数据库 migration 细节。