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

267 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计下载大文件功能v1
- 文档类别:设计(系统设计)
- 创建时间2026-03-27 12:09 (Asia/Shanghai)
- 适用项目ToolsShowNestJS + Prisma + GitLab Generic Package
- 关联模块:`access``downloads``gitlab-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 断点续传(`Range``Accept-Ranges``Content-Range``206`)。
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. 新下载接口支持 `HEAD``GET + Range`
3. 会话在有效期内可多次请求同一文件不同字节区间。
4. 下载记录改为“会话聚合 + 分段记录”,用于分析中断与重试。
### 4.2 兼容策略
1. 保留 `GET /downloads/:ticket`1-2 个版本周期。
2. 新增 `GET /downloads/sessions/:token/file`(新)。
3. 前端先读 launch 返回字段,若存在 `sessionToken` 则走新链路,否则走旧链路。
## 5. 数据模型设计
> 以下为设计层面的 Prisma 结构草案,具体字段可在实现阶段微调。
### 5.1 新增表:`download_sessions`
- `id`String (UUID)
- `sessionToken`String (Unique)
- `toolId`String
- `artifactId`String
- `channel`String?
- `clientVersion`String?
- `requestIp`String?
- `userAgent`String?
- `expiresAt`DateTime
- `lastAccessAt`DateTime
- `completedAt`DateTime?
- `revokedAt`DateTime?
- `createdAt`DateTime
索引建议:
- `(sessionToken unique)`
- `(expiresAt)`
- `(artifactId, expiresAt)`
### 5.2 新增表:`download_session_chunks`(可选但建议)
- `id`Int (auto increment)
- `sessionId`String
- `rangeStart`BigInt
- `rangeEnd`BigInt
- `bytesSent`BigInt
- `status``success | failed | cancelled`
- `errorMessage`String?
- `durationMs`Int?
- `createdAt`DateTime
说明:用于分析大文件下载过程中的断点位置、失败区间、重试质量。
## 6. API 设计
Base path: `/api/v1`
### 6.1 Launch下载模式响应升级
`POST /tools/:id/launch`
下载模式响应示例:
```json
{
"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`
响应:
- 无 Range`200` + 全量流
- 有效 Range`206` + 指定区间流
- 非法 Range`416`
关键响应头:
- `Accept-Ranges: bytes`
- `Content-Range`
- `Content-Length`
- `Content-Type`
- `Content-Disposition`
- `ETag`
### 6.4 会话失效
- 过期或撤销:`410 Gone`
- 无效 token`404``401`(按安全策略统一)
## 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`
日志字段建议:`traceId``sessionId``artifactId``rangeStart``rangeEnd``bytesSent``status``errorCode`
## 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 细节。