2525│ └──────────────────┘ └──────────────────┘ │
2626└─────────┬──────────────────────────┬────────────────────────┘
2727 │ │
28- │ GET /documents/{id}/download (同步,流式返回)
29- │ POST /collections/{id}/export (异步,生成下载链接)
28+ │ GET /collections/{id}/ documents/{id}/download (同步,流式返回)
29+ │ POST /collections/{id}/export (异步,生成下载链接)
3030 ▼ ▼
3131┌─────────────────────────────────────────────────────────────┐
3232│ View Layer │
111111
112112| 场景 | API | 模式 | 说明 |
113113| ------| -----| ------| ------|
114- | ** 单个文档下载** | ` GET /documents/{id}/download ` | 同步流式 | 直接返回文件流 |
114+ | ** 单个文档下载** | ` GET /collections/{collection_id}/ documents/{id}/download ` | 同步流式 | 直接返回文件流 |
115115| ** 知识库导出** | ` POST /collections/{id}/export ` | 异步 | 生成后端下载 URL |
116116
117117## 核心流程详解
124124用户点击"下载"按钮
125125 │
126126 ▼
127- GET /api/v1/documents/{document_id}/download
127+ GET /api/v1/collections/{collection_id}/ documents/{document_id}/download
128128 │
129129 ▼
130130后端处理:
131131 │
132132 ├─► 验证用户身份(JWT)
133133 │
134- ├─► 验证文档访问权限
134+ ├─► 验证文档访问权限(user、collection_id 匹配)
135135 │
136- ├─► 查询 Document 记录
136+ ├─► 查询 Document 记录(过滤软删除文档)
137137 │
138- ├─► 从 doc_metadata 获取 object_path
138+ ├─► 检查文档状态(只禁止 EXPIRED/DELETED 状态)
139+ │ ├─ UPLOADED: ✅ 允许下载(上传后 24 小时内)
140+ │ ├─ PENDING/RUNNING/COMPLETE/FAILED: ✅ 允许下载(永久)
141+ │ ├─ EXPIRED: ❌ 禁止(文件已被清理)
142+ │ └─ DELETED: ❌ 禁止(用户已删除)
143+ │
144+ ├─► 从 doc_metadata JSON 获取 object_path
139145 │
140146 ├─► 从对象存储读取文件(流式)
141- │ └─ 路径:user-{user_id}/{collection_id}/{doc_id}/original.pdf
147+ │ └─ 路径:user-{user_id}/{collection_id}/{doc_id}/original.xxx
142148 │
143149 └─► 返回 StreamingResponse
144- ├─ Content-Type: application/octet-stream
145- ├─ Content-Disposition: attachment; filename="xxx.pdf "
146- └─ Transfer-Encoding: chunked (流式传输)
150+ ├─ Content-Type: 根据文件扩展名判断(默认 application/octet-stream)
151+ ├─ Content-Disposition: attachment; filename="原始文件名 "
152+ └─ Content-Length: 文件大小(从对象存储获取)
147153 │
148154 ▼
149155文件通过后端流式传输给客户端
@@ -156,7 +162,7 @@ GET /api/v1/documents/{document_id}/download
156162
157163** 请求** :
158164``` http
159- GET /api/v1/documents/{document_id}/download
165+ GET /api/v1/collections/{collection_id}/ documents/{document_id}/download
160166Authorization: Bearer {token}
161167```
162168
@@ -166,11 +172,14 @@ HTTP/1.1 200 OK
166172Content-Type: application/octet-stream
167173Content-Disposition: attachment; filename="user_manual.pdf"
168174Content-Length: 5242880
169- Transfer-Encoding: chunked
170175
171176[文件二进制流]
172177```
173178
179+ ** 说明** :
180+ - 实际实现中不使用 ` Transfer-Encoding: chunked ` 响应头,而是通过 FastAPI 的 ` StreamingResponse ` 自动处理流式传输
181+ - ` Content-Length ` 会从对象存储获取文件大小后设置
182+
174183#### 1.3 关键特性
175184
176185- ** 流式读取** :从对象存储按块读取(chunk size = 64KB)
@@ -179,6 +188,58 @@ Transfer-Encoding: chunked
179188- ** 超时控制** :设置合理的读取超时(如 30 分钟)
180189- ** 权限控制** :每次下载都验证用户权限
181190- ** 审计日志** :记录下载操作(用户、时间、文档)
191+ - ** 状态检查** :只禁止下载 EXPIRED/DELETED 状态的文档
192+
193+ #### 1.4 文档生命周期与下载可用性
194+
195+ ** 文档状态说明** :
196+
197+ | 状态 | 说明 | 可下载 | 自动清理 | 触发条件 |
198+ | ------| ------| --------| ----------| ----------|
199+ | ` UPLOADED ` | 已上传,未确认 | ✅ | 是(24小时后) | 用户上传文件 |
200+ | ` PENDING ` | 已确认,等待处理 | ✅ | 否 | 用户确认文档 |
201+ | ` RUNNING ` | 正在处理索引 | ✅ | 否 | 后台任务开始处理 |
202+ | ` COMPLETE ` | 处理完成 | ✅ | 否 | 索引创建成功 |
203+ | ` FAILED ` | 处理失败 | ✅ | 否 | 索引创建失败 |
204+ | ` EXPIRED ` | 已过期 | ❌ | - | 自动清理任务 |
205+ | ` DELETED ` | 已删除 | ❌ | - | 用户删除操作 |
206+
207+ ** 自动清理机制** :
208+
209+ ```
210+ 定时任务:每 10 分钟运行一次
211+ 清理目标:UPLOADED 状态 且 创建时间 > 24 小时 的文档
212+ 清理操作:
213+ 1. 删除对象存储中的文件(包括所有相关文件)
214+ 2. 将文档状态更新为 EXPIRED
215+ 3. 记录清理日志
216+
217+ 配置位置:config/celery.py
218+ 任务名称:cleanup_expired_documents_task
219+ 执行频率:600 秒(10 分钟)
220+ ```
221+
222+ ** 设计理念** :
223+ - ✅ ** 用户友好** :上传后即可下载预览,无需等待确认
224+ - ✅ ** 资源优化** :未确认的临时文件自动清理,节省存储空间
225+ - ✅ ** 数据安全** :确认后的文档永久保留,不会被自动删除
226+ - ✅ ** 清晰提示** :EXPIRED 状态的文档返回明确错误信息
227+
228+ ** 典型使用流程** :
229+
230+ ```
231+ 1. 用户上传文档
232+ └─► 状态:UPLOADED(可下载,24小时有效期)
233+
234+ 2. 场景 A:用户及时确认(< 24 小时)
235+ └─► 状态:PENDING → RUNNING → COMPLETE
236+ └─► 可永久下载,不会被清理 ✅
237+
238+ 3. 场景 B:用户未及时确认(> 24 小时)
239+ └─► 自动清理任务执行
240+ └─► 状态:EXPIRED(无法下载) ❌
241+ └─► 用户需要重新上传
242+ ```
182243
183244### 场景 2: 知识库导出(异步打包)
184245
@@ -617,7 +678,7 @@ Celery 任务超时:
617678
618679| 方法 | 路径 | 说明 | 模式 |
619680| ------| ------| ------| ------|
620- | GET | ` /documents/{id}/download ` | 下载单个文档 | 同步流式 |
681+ | GET | ` /collections/{collection_id}/ documents/{id}/download ` | 下载单个文档 | 同步流式 |
621682| POST | ` /collections/{id}/export ` | 知识库导出 | 异步 |
622683| GET | ` /export-tasks/{id} ` | 查询导出任务状态 | - |
623684| GET | ` /export-tasks/{id}/download ` | 下载导出结果 | 同步流式 |
0 commit comments