fix: generation_tasks results 端点返回 OSS 预签名 URL(24h 有效期) #206

Merged
xiaoxia merged 1 commits from fix/generated-video-url-403 into develop 2026-07-10 12:09:52 +08:00
Owner

P0-2 修复

问题GET /api/v1/generation/tasks/{task_id}/results 返回的 download_url 为 null,前端使用 file_url(OSS 私有 bucket 原始 URL)访问视频返回 403。

根因:该端点未像 generated_videos.py 中的其他端点一样调用 storage_service.get_download_url() 生成预签名 URL。

修复

  • 注入 storage_service 依赖(复用已有 OSSStorageService.get_download_url
  • 对每个视频的 file_url 生成预签名 URL,有效期 86400 秒(24h)
  • _to_generated_video_response 支持 download_url 参数
  • generated_videos.py 中的模式保持一致

测试

  • test_results_endpoint_generates_presigned_urls:验证预签名 URL 生成 + 24h 有效期
  • test_results_endpoint_handles_empty_videos:验证空结果正常返回
  • 全部 17 个 generation API 测试通过

未改 bucket ACL(保持 private,安全无风险)

P0-3 调查结论

Worker 容器内存限制为 2GB(旧备份为 4GB)。FFmpeg xfade 转场需要同时解码多个视频,内存峰值容易超过 2GB。建议:

  1. 将 worker 容器内存限制从 2GB 提升到 4GB
  2. 考虑将 Celery concurrency 从 2 降到 1,避免两个 FFmpeg 同时运行

Closes #P0-2
Closes #P0-3

## P0-2 修复 **问题**:`GET /api/v1/generation/tasks/{task_id}/results` 返回的 `download_url` 为 null,前端使用 `file_url`(OSS 私有 bucket 原始 URL)访问视频返回 403。 **根因**:该端点未像 `generated_videos.py` 中的其他端点一样调用 `storage_service.get_download_url()` 生成预签名 URL。 **修复**: - 注入 `storage_service` 依赖(复用已有 `OSSStorageService.get_download_url`) - 对每个视频的 `file_url` 生成预签名 URL,有效期 86400 秒(24h) - `_to_generated_video_response` 支持 `download_url` 参数 - 与 `generated_videos.py` 中的模式保持一致 **测试**: - `test_results_endpoint_generates_presigned_urls`:验证预签名 URL 生成 + 24h 有效期 - `test_results_endpoint_handles_empty_videos`:验证空结果正常返回 - 全部 17 个 generation API 测试通过 **未改 bucket ACL**(保持 private,安全无风险) ## P0-3 调查结论 Worker 容器内存限制为 **2GB**(旧备份为 4GB)。FFmpeg xfade 转场需要同时解码多个视频,内存峰值容易超过 2GB。建议: 1. 将 worker 容器内存限制从 2GB 提升到 **4GB** 2. 考虑将 Celery concurrency 从 2 降到 1,避免两个 FFmpeg 同时运行 Closes #P0-2 Closes #P0-3
xiaoxia added 1 commit 2026-07-10 11:27:01 +08:00
fix: generation_tasks results 端点返回 OSS 预签名 URL(24h 有效期)
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m18s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m19s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
8fa287036d
P0-2 修复:list_generation_results 端点原先直接返回原始 file_url,
OSS bucket 为 private 导致前端访问视频返回 403。

改动:
- 注入 storage_service 依赖(复用已有 OSSStorageService.get_download_url)
- 对每个视频的 file_url 生成预签名 URL,有效期 86400 秒(24h)
- _to_generated_video_response 支持 download_url 参数
- 与 generated_videos.py 中的模式保持一致

新增测试:
- test_results_endpoint_generates_presigned_urls:验证预签名 URL 生成
- test_results_endpoint_handles_empty_videos:验证空结果正常返回

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

PR #206 审计通过

结论:通过 — 0 P0 / 0 P1 / 0 P2 / 0 P3(本 PR 新增)


修复验证

文件apps/api/app/api/routes/generation_tasks.py

  • 预签名逻辑正确:调用 `storage_service.get_download_url(item.file_url, expires_seconds=86400)
    • 底层使用 OSS SDK bucket.sign_url("GET", key, expires) — 标准实现,无安全问题
    • 签名 URL 只能用于 GET 下载,不可修改/删除,权限最小化
  • 24h 有效期合理:86400s = 24h,用户有充足时间下载,又不会长期有效
  • 域名安全_normalize_storage_key 只提取路径,签名后 URL 一定是 OSS 域名,不存在跳转到恶意站点的风险
  • 字段完整GeneratedVideoResponse.download_url 已存在(Optional[str]),file_url 原始值保留不变
  • 权限控制:端点有 get_current_user + _check_project_access 双重校验,未登录/无权限用户无法获取预签名 URL
  • 异常降级:签名失败时返回原始公网 URL(get_url),不会导致整个接口 500

测试验证

文件tests/unit/test_generation_presigned_url.py(179 行,2 个用例)

  • test_results_endpoint_generates_presigned_urls:验证每个视频都生成预签名 URL,expires=86400,download_url 字段存在,file_url 保留原值
  • test_results_endpoint_handles_empty_videos:无视频时返回空列表,不调用 storage_service

补充说明(非阻塞,历史遗留)

以下是 OSSStorageService.get_download_url 已有实现中的可优化点,非本 PR 引入,不阻塞合并:

  1. 缺少域名校验(P3)_normalize_storage_key 不校验 URL 域名,非 OSS 域名也会提取 key 签名。虽然无安全风险(签名结果仍是 OSS 域名),但不够严谨,建议后续加上白名单校验。
  2. 签名失败无日志(P3)except Exception 静默降级返回原始 URL,私有 bucket 下原始 URL 仍是 403,排障时难发现问题,建议补一条 warning 日志。

最终结论:P0-2 修复正确到位,PR #206 可以合并。

## ✅ PR #206 审计通过 **结论:通过** — 0 P0 / 0 P1 / 0 P2 / 0 P3(本 PR 新增) --- ### 修复验证 ✅ **文件**:`apps/api/app/api/routes/generation_tasks.py` - ✅ **预签名逻辑正确**:调用 `storage_service.get_download_url(item.file_url, expires_seconds=86400) - 底层使用 OSS SDK `bucket.sign_url("GET", key, expires)` — 标准实现,无安全问题 - 签名 URL 只能用于 GET 下载,不可修改/删除,权限最小化 - ✅ **24h 有效期合理**:86400s = 24h,用户有充足时间下载,又不会长期有效 - ✅ **域名安全**:`_normalize_storage_key` 只提取路径,签名后 URL 一定是 OSS 域名,不存在跳转到恶意站点的风险 - ✅ **字段完整**:`GeneratedVideoResponse.download_url` 已存在(Optional[str]),file_url 原始值保留不变 - ✅ **权限控制**:端点有 `get_current_user` + `_check_project_access` 双重校验,未登录/无权限用户无法获取预签名 URL - ✅ **异常降级**:签名失败时返回原始公网 URL(`get_url`),不会导致整个接口 500 ### 测试验证 ✅ **文件**:`tests/unit/test_generation_presigned_url.py`(179 行,2 个用例) - ✅ `test_results_endpoint_generates_presigned_urls`:验证每个视频都生成预签名 URL,expires=86400,download_url 字段存在,file_url 保留原值 - ✅ `test_results_endpoint_handles_empty_videos`:无视频时返回空列表,不调用 storage_service ### 补充说明(非阻塞,历史遗留) 以下是 `OSSStorageService.get_download_url` 已有实现中的可优化点,**非本 PR 引入**,不阻塞合并: 1. **缺少域名校验(P3)**:`_normalize_storage_key` 不校验 URL 域名,非 OSS 域名也会提取 key 签名。虽然无安全风险(签名结果仍是 OSS 域名),但不够严谨,建议后续加上白名单校验。 2. **签名失败无日志(P3)**:`except Exception` 静默降级返回原始 URL,私有 bucket 下原始 URL 仍是 403,排障时难发现问题,建议补一条 warning 日志。 --- **最终结论:P0-2 修复正确到位,PR #206 可以合并。**
xiaoxia merged commit f7bcebecc4 into develop 2026-07-10 12:09:52 +08:00
Sign in to join this conversation.