You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

6.2 KiB

修复剧本解析时分镜 project_id 使用错误

日期: 2026-02-11
类型: Bug 修复
影响范围: 后端 - 剧本解析服务

问题描述

/api/v1/screenplays/{screenplay_id}/parse 接口解析剧本并创建分镜时,错误地使用了父项目ID,导致分镜被创建在父项目下,而不是当前子项目下。

架构设计

项目中存在两种资源的存储策略:

  1. 项目元素(角色/场景/道具/标签)

    • 存储位置:父项目级别
    • 共享范围:所有子项目共享
    • 原因:资源复用,避免重复创建
  2. 分镜(Storyboards)

    • 存储位置:当前项目级别(子项目)
    • 共享范围:不共享,每个子项目独立
    • 原因:
      • 每个剧本对应一个子项目
      • 分镜是剧本的具体实现,属于子项目的产出物
      • 不同子项目的分镜不应该混在一起

错误行为

修复前

# ❌ 错误:分镜使用了父项目ID
parent_project_id = await self._get_parent_project_id(screenplay.project_id)

storyboard_ids = await self._create_storyboards_from_ai(
    screenplay_id=screenplay_id,
    project_id=parent_project_id,  # ❌ 错误:使用父项目ID
    storyboards_data=parsed_data['storyboards'],
    ...
)

影响

  • 所有子项目的分镜都被创建在父项目下
  • 查询子项目的分镜时返回空结果
  • 父项目下混杂了所有子项目的分镜,无法区分

修复内容

修改文件

文件: server/app/services/screenplay_service.py

方法: store_parsed_elements

修复逻辑

# ✅ 正确:分镜使用当前项目ID(子项目ID)
storyboard_ids = await self._create_storyboards_from_ai(
    screenplay_id=screenplay_id,
    project_id=screenplay.project_id,  # ✅ 使用当前项目ID(子项目)
    storyboards_data=parsed_data['storyboards'],
    character_id_map=character_id_map,
    location_id_map=location_id_map,
    prop_id_map=prop_id_map,
    tag_id_maps=tag_id_maps
)

完整流程

剧本解析流程:
1. 获取剧本 → screenplay.project_id (可能是子项目ID)
2. 获取父项目ID → parent_project_id = _get_parent_project_id(screenplay.project_id)
3. 创建项目元素 → 使用 parent_project_id
   - 角色 (project_characters)
   - 场景 (project_locations)
   - 道具 (project_props)
   - 标签 (project_element_tags)
4. 创建分镜 → 使用 screenplay.project_id (当前项目ID)
   - 分镜 (storyboards)
   - 对白 (storyboard_dialogues)

技术细节

数据存储策略对比

资源类型 存储位置 project_id 共享范围
角色 (project_characters) 父项目 parent_project_id 所有子项目
场景 (project_locations) 父项目 parent_project_id 所有子项目
道具 (project_props) 父项目 parent_project_id 所有子项目
标签 (project_element_tags) 父项目 parent_project_id 所有子项目
分镜 (storyboards) 当前项目 screenplay.project_id 当前项目独享
对白 (storyboard_dialogues) 当前项目 通过 storyboard_id 关联 当前项目独享

为什么分镜不共享?

  1. 业务逻辑

    • 每个剧本解析生成独立的分镜序列
    • 分镜的 order_index 是项目级别唯一的
    • 不同剧本的分镜混在一起会导致顺序混乱
  2. 数据完整性

    • storyboards 表有唯一约束:(project_id, order_index)
    • 如果多个子项目的分镜都存在父项目下,会导致 order_index 冲突
  3. 查询性能

    • 查询分镜时需要按 project_id 过滤
    • 如果所有分镜都在父项目下,需要额外的过滤条件

影响分析

兼容性

  • 向后兼容:修复后新创建的分镜使用正确的 project_id
  • ⚠️ 历史数据:已创建的分镜仍在父项目下,需要数据迁移

数据迁移

如果需要修复历史数据,可以执行以下 SQL:

-- 查询需要迁移的分镜(project_id 是父项目,但应该属于子项目)
SELECT 
    s.storyboard_id,
    s.project_id AS current_project_id,
    s.meta_data->>'screenplay_id' AS screenplay_id,
    sc.project_id AS correct_project_id
FROM storyboards s
JOIN screenplays sc ON s.meta_data->>'screenplay_id' = sc.screenplay_id::text
WHERE s.project_id != sc.project_id;

-- 迁移分镜到正确的项目
UPDATE storyboards s
SET project_id = sc.project_id
FROM screenplays sc
WHERE s.meta_data->>'screenplay_id' = sc.screenplay_id::text
  AND s.project_id != sc.project_id;

测试建议

测试场景

  1. 父项目直接创建剧本

    • 剧本的 project_id = 父项目ID
    • 分镜应该创建在父项目下
  2. 子项目创建剧本

    • 剧本的 project_id = 子项目ID
    • 分镜应该创建在子项目下(不是父项目)
  3. 自动创建子项目

    • 使用 upload_and_parse_screenplay 接口
    • 自动创建子项目并关联剧本
    • 分镜应该创建在新创建的子项目下

测试用例

# 1. 创建父项目
POST /api/v1/projects
{
  "name": "测试父项目",
  "type": 1
}
# 返回: parent_project_id

# 2. 上传剧本并自动创建子项目
POST /api/v1/screenplays/upload-and-parse
{
  "project_id": "{parent_project_id}",
  "name": "测试剧本",
  "file": "test.txt",
  "auto_create_subproject": true
}
# 返回: screenplay_id, subproject.project_id

# 3. 解析剧本
POST /api/v1/screenplays/{screenplay_id}/parse
{
  "custom_requirements": "创建5个分镜"
}

# 4. 验证分镜的 project_id
SELECT project_id, COUNT(*) 
FROM storyboards 
WHERE meta_data->>'screenplay_id' = '{screenplay_id}'
GROUP BY project_id;

# 预期结果:
# - project_id = subproject.project_id (子项目ID)
# - count = 5

相关文件

  • server/app/services/screenplay_service.py - 修复分镜 project_id 使用
  • server/app/models/storyboard.py - Storyboard 模型定义
  • server/app/api/v1/screenplays.py - 剧本解析 API
  • server/app/tasks/ai_tasks.py - 剧本解析 Celery 任务

参考

  • 项目元素 parent_id 解决方案: docs/server/changelogs/2026-02-11-project-elements-parent-id-resolution.md
  • 数据库约束: storyboards 表的 (project_id, order_index) 唯一约束