# Changelog: 修复剧本解析接口项目ID问题 **日期**: 2026-02-11 **类型**: Bug Fix **影响范围**: 剧本解析、项目资源管理 ## 问题描述 剧本解析接口 `/api/v1/screenplays/{screenplay_id}/parse` 在存储角色、场景、道具到项目级别表时,使用的是剧本所属的 `project_id`。 当剧本属于子项目时,`screenplay.project_id` 是子项目ID,但 `project_characters/locations/props` 表的 `project_id` 字段设计为存储"父级项目ID"(`parent_project_id = NULL` 的项目),导致数据存储到错误的项目。 ## 根本原因 数据库设计约定: - `project_characters/locations/props` 表的 `project_id` 字段存储父级项目ID - 所有子项目共享父项目的角色/场景/道具资源 - 但代码实现中直接使用了 `screenplay.project_id`,未判断是否为子项目 ## 解决方案 ### 1. 新增辅助方法 `_get_parent_project_id` 在 `ScreenplayService` 中添加辅助方法,用于获取父级项目ID: ```python async def _get_parent_project_id(self, project_id: UUID) -> UUID: """获取父级项目ID 如果项目是子项目(parent_project_id != NULL),返回其父项目ID 如果项目是父项目(parent_project_id == NULL),返回自身ID """ project_repo = ProjectRepository(self.db) project = await project_repo.get_by_id(str(project_id)) if not project: raise Exception(f"项目不存在: {project_id}") # 如果是子项目,返回父项目ID;否则返回自身ID if project.parent_project_id: return project.parent_project_id else: return project.id ``` ### 2. 修改 `store_parsed_elements` 方法 在存储解析结果前,先获取父级项目ID: ```python # 0. 获取剧本和父级项目 ID screenplay = await self.repository.get_by_id(screenplay_id) if not screenplay: raise Exception(f"剧本不存在: {screenplay_id}") # ✅ 获取父级项目ID(如果是子项目,需要获取其父项目ID) parent_project_id = await self._get_parent_project_id(screenplay.project_id) logger.info("剧本所属项目 ID: %s, 父级项目 ID: %s", screenplay.project_id, parent_project_id) ``` ### 3. 更新所有资源创建调用 将所有 `project_id` 参数替换为 `parent_project_id`: - 创建项目角色:`char_repo.create_or_update(project_id=parent_project_id, ...)` - 创建项目场景:`loc_repo.create_or_update(project_id=parent_project_id, ...)` - 创建项目道具:`prop_repo.create_or_update(project_id=parent_project_id, ...)` - 创建元素标签:`ProjectElementTag(project_id=parent_project_id, ...)` - 创建分镜:`_create_storyboards_from_ai(project_id=parent_project_id, ...)` ## 影响范围 ### 修改文件 - `server/app/services/screenplay_service.py` - 新增方法:`_get_parent_project_id` - 修改方法:`store_parsed_elements` ### 影响功能 - ✅ 剧本解析(AI 提取角色/场景/道具) - ✅ 项目资源管理(角色/场景/道具列表) - ✅ 分镜创建(元素关联) ## 测试建议 ### 1. 测试场景1:父项目剧本解析 ```bash # 1. 创建父项目 POST /api/v1/projects { "name": "测试父项目", "type": "mine" } # 2. 上传剧本到父项目(不自动创建子项目) POST /api/v1/screenplays/upload { "project_id": "<父项目ID>", "name": "测试剧本", "auto_create_subproject": false, "file": <剧本文件> } # 3. 触发解析 POST /api/v1/screenplays/{screenplay_id}/parse # 4. 验证:角色/场景/道具的 project_id 应该是父项目ID SELECT * FROM project_characters WHERE project_id = '<父项目ID>'; ``` ### 2. 测试场景2:子项目剧本解析 ```bash # 1. 创建父项目 POST /api/v1/projects { "name": "测试父项目", "type": "mine" } # 2. 上传剧本并自动创建子项目 POST /api/v1/screenplays/upload { "project_id": "<父项目ID>", "name": "测试剧本", "auto_create_subproject": true, "file": <剧本文件> } # 3. 触发解析 POST /api/v1/screenplays/{screenplay_id}/parse # 4. 验证:角色/场景/道具的 project_id 应该是父项目ID(不是子项目ID) SELECT * FROM project_characters WHERE project_id = '<父项目ID>'; SELECT * FROM projects WHERE parent_project_id = '<父项目ID>'; ``` ### 3. 验证SQL ```sql -- 验证角色/场景/道具是否存储到父项目 SELECT pc.character_id, pc.name AS character_name, pc.project_id, p.name AS project_name, p.parent_project_id FROM project_characters pc JOIN projects p ON pc.project_id = p.id WHERE pc.project_id = '<父项目ID>'; -- 验证分镜是否关联到正确的项目 SELECT s.storyboard_id, s.title, s.project_id, p.name AS project_name, p.parent_project_id FROM storyboards s JOIN projects p ON s.project_id = p.id WHERE s.project_id IN ('<父项目ID>', '<子项目ID>'); ``` ## 向后兼容性 ✅ 完全向后兼容 - 对于父项目剧本:行为不变(`parent_project_id` 返回自身ID) - 对于子项目剧本:修复了错误行为(现在正确返回父项目ID) ## 相关文档 - 数据库迁移:`server/alembic/versions/20260208_1000_add_project_level_resources.py` - 架构决策:`docs/architecture/adrs/003-screenplay-resource-architecture.md` - 项目模型:`server/app/models/project.py` - 剧本服务:`server/app/services/screenplay_service.py` ## 总结 此次修复确保剧本解析接口正确处理子项目场景,将角色/场景/道具资源存储到父级项目,符合数据库设计约定和业务逻辑。