Files
tools-show/docs/2026-03-27-12-09-设计-下载大文件功能.md

267 lines
8.6 KiB
Markdown
Raw Normal View History

2026-03-30 09:36:36 +08:00
# 设计下载大文件功能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 细节。