# 修复剧本解析时分镜 project_id 使用错误 **日期**: 2026-02-11 **类型**: Bug 修复 **影响范围**: 后端 - 剧本解析服务 ## 问题描述 在 `/api/v1/screenplays/{screenplay_id}/parse` 接口解析剧本并创建分镜时,错误地使用了父项目ID,导致分镜被创建在父项目下,而不是当前子项目下。 ### 架构设计 项目中存在两种资源的存储策略: 1. **项目元素(角色/场景/道具/标签)** - 存储位置:父项目级别 - 共享范围:所有子项目共享 - 原因:资源复用,避免重复创建 2. **分镜(Storyboards)** - 存储位置:当前项目级别(子项目) - 共享范围:不共享,每个子项目独立 - 原因: - 每个剧本对应一个子项目 - 分镜是剧本的具体实现,属于子项目的产出物 - 不同子项目的分镜不应该混在一起 ### 错误行为 **修复前**: ```python # ❌ 错误:分镜使用了父项目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` ### 修复逻辑 ```python # ✅ 正确:分镜使用当前项目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: ```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` 接口 - 自动创建子项目并关联剧本 - 分镜应该创建在新创建的子项目下 ### 测试用例 ```bash # 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)` 唯一约束