fix: 封面选帧输入源改为最终成片,预览片段作为兼容回退 #1534
Reference in New Issue
Block a user
Delete Branch "fix/cover-from-final-video"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
改动内容
配合前端步骤调整(选择标题(含预览) → 选择配音 → 确认生成 → 选择封面),封面选帧接口的输入视频源从「预览片段」改为「确认生成产出的最终成片」。
与前端 PR #1533 契约对齐:前端传入
generated_video_id+video_url。后端改动
GenerateCoverRequest新增两个可选字段:generated_video_id:确认生成产出的最终视频 IDvideo_url:最终视频 URL(兜底)视频源查找优先级调整:
generated_video_id→ 查GeneratedVideo.file_urlvideo_url→ 直接使用plan.config.rendered_storage_keyplan.config.generation_task_idsource_edit_plan_id关联的is_preview=False已完成任务(最终成片优先)source_edit_plan_id关联的is_preview=True预览任务(回退)user+template最近预览任务(兜底)cover_url 查找同步调整:步骤B 优先取最终成片任务的
cover_url,再回退预览任务MediaKit 调用完全不变:
strategy=SpecifiedFrames、max_frames=1、轮询间隔、重试次数、降级逻辑全部保持原样,只是传入的视频 URL 从预览换成最终成片权限校验:通过
generated_video_id查找视频后,校验其关联generation_task的用户归属兼容性
upload类型封面完全不走视频查找,不受影响测试
🚀 预览环境已部署
CI全绿,自动审批通过。
CI全绿,自动审批通过。
AI Code Review 指出的两个阻塞问题: 1. 权限校验绕过(步骤0a):原逻辑仅在关联任务存在且 created_by_user_id 非空时才校验归属;若 GeneratedVideo 的 generation_task_id 为空或关联任务被删除,校验被静默跳过。 修复: - 优先校验 GeneratedVideo.user_id 直接归属 - 关联任务存在时校验 task.created_by_user_id - video 无 owner 且关联任务也查不到(归属无法确认)→ 403, 不再静默放行 2. video_url SSRF(步骤0b):原逻辑直接使用请求体传入的 URL, 攻击者可传入内网/云元数据地址诱导服务端请求。 修复:新增 _is_trusted_media_url() 白名单校验: - 仅允许 http/https 且主机为自家 OSS bucket/endpoint 域名 - 显式拒绝 localhost/127.*/10.*/192.168.*/169.254.*/172.16-31.* - 校验失败静默忽略该 URL,回退后续查找链 附带改进(review 建议项): - 函数内联 import 全部移至模块顶部(storage/re) - 新增 2 个安全测试:SSRF 内网地址拦截、归属无法确认 403 - 现有 32 个测试适配新的 mock 命名空间,共 34 个测试全过AI Code Review 第二轮指出的两个阻塞问题: 1. endpoint 主机名解析绕过:原逻辑 ep.split(':')[0] 在 endpoint 带 scheme(http://host:9000)时取到 'http',虽白名单顺序使 public_url 先生效,但 endpoint 分支可能错误匹配 '.http' 后缀。 修复:新增 _endpoint_host() 统一用 urlparse 提取主机名, 兼容有无 scheme、带端口等各种配置形式。 2. 缺失 IPv6 内网地址校验:[::1]、fe80::/10(链路本地)、 fc00::/7(唯一本地)等 IPv6 本地地址未拦截。 修复:补充 IPv6 回环/链路本地/ULA 地址显式拒绝。 附带: - 移除函数内重复的 urlparse 导入,统一使用顶部导入 - 新增 2 个白名单单元测试(IPv4/IPv6/元数据拦截 + scheme 解析) - 共 36 个测试全部通过【阻塞级判定】
📊 审查概览
🔴 阻塞级问题(必须修复)
无
💡 改进建议(不阻塞合并)
[apps/api/app/api/routes/generation_cover.py: 418, 437, 538] 性能优化:避免重复数据库查询
generate_cover函数中,_repo.list_by_source_edit_plan(plan_id)被调用了多次(步骤3、步骤4以及后续查找封面URL的步骤B)。如果该 plan 关联的任务较多,这会导致不必要的数据库 I/O 开销。建议将查询结果缓存到局部变量中,在内存中进行过滤(如区分is_preview),以减少数据库压力。[apps/api/app/api/routes/generation_cover.py: 326, 366, 425] 敏感信息日志脱敏
url[:80]记录日志。如果video_url或file_url是包含签名(Signature)或临时 Token 的 OSS 预签名 URL,日志可能会泄露部分敏感凭证。建议在记录 URL 日志时,剔除 query 参数或仅记录 path 部分,防止敏感信息泄露到日志系统中。✅ 良好实践
_is_trusted_media_url函数实现较为严谨,结合了ipaddress标准库和域名白名单机制,有效防止了通过video_url参数发起的内网 SSRF 攻击。generated_video_id的归属进行了双重校验(Video 自身的 user_id 和关联 Task 的 created_by_user_id),且在无法确认归属时选择拒绝访问(Fail-close),符合安全最佳实践。_resolve_storage_key_to_url)均包含异常捕获,且在异常时返回None或记录日志,保证了主流程的健壮性,不会因非关键路径的错误导致接口崩溃。_resolve_storage_key_to_url函数,减少了代码冗余。🤖 由 AI 代码审查机器人自动生成 | 2026-08-28 17:05:25 | 模型:
🗑️ 预览环境已清理
PR #1534 已关闭或合并,对应的预览环境已被清理。