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
6.2 KiB
修复剧本解析时分镜 project_id 使用错误
日期: 2026-02-11
类型: Bug 修复
影响范围: 后端 - 剧本解析服务
问题描述
在 /api/v1/screenplays/{screenplay_id}/parse 接口解析剧本并创建分镜时,错误地使用了父项目ID,导致分镜被创建在父项目下,而不是当前子项目下。
架构设计
项目中存在两种资源的存储策略:
-
项目元素(角色/场景/道具/标签)
- 存储位置:父项目级别
- 共享范围:所有子项目共享
- 原因:资源复用,避免重复创建
-
分镜(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 关联 | 当前项目独享 |
为什么分镜不共享?
-
业务逻辑:
- 每个剧本解析生成独立的分镜序列
- 分镜的 order_index 是项目级别唯一的
- 不同剧本的分镜混在一起会导致顺序混乱
-
数据完整性:
storyboards表有唯一约束:(project_id, order_index)- 如果多个子项目的分镜都存在父项目下,会导致 order_index 冲突
-
查询性能:
- 查询分镜时需要按 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;
测试建议
测试场景
-
父项目直接创建剧本
- 剧本的 project_id = 父项目ID
- 分镜应该创建在父项目下
-
子项目创建剧本
- 剧本的 project_id = 子项目ID
- 分镜应该创建在子项目下(不是父项目)
-
自动创建子项目
- 使用
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- 剧本解析 APIserver/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)唯一约束