# Changelog: 项目资源自动关联实现 **日期**: 2026-02-08 **类型**: Feature / Enhancement **优先级**: P1 **关联文档**: - [ADR 01: 资源归属项目级迁移](../../server/adrs/01-project-level-resource-ownership.md) - [剧本标签管理服务需求](../../requirements/backend/04-services/project/screenplay-tag-service.md) - [剧本解析问题分析](./2026-02-07-screenplay-parse-issues-analysis.md) --- ## 📋 问题背景 ### 核心问题(P1-01) 在 Phase 1 实现中,AI 拆解剧本后: - ✅ 角色/场景/道具元素已存储 - ✅ 元素标签已创建(`screenplay_element_tags`) - ✅ 分镜及分镜元素关联已创建(`storyboards`、`storyboard_items`) - ❌ **`project_resources` 表未关联** ### 业务影响 根据 ADR 01 的三层架构设计: ``` 元素(Element)→ 标签(Tag)→ 资源(Resource) screenplay_characters screenplay_element_tags project_resources screenplay_locations → (统一标签表) → (图片/视频素材) screenplay_props ``` **缺失环节**: - 标签创建后,没有为其创建对应的 `project_resources` 记录 - 用户无法通过 "素材管理" 界面查看 AI 拆解的元素 - AI 生成图片功能缺少目标资源记录 ### 架构约束 根据 ADR 01: - 资源归属于**项目级**(`project_id`),而非剧本级 - 多集项目共享资源库 - 标签与资源是 `1:N` 关系(一个标签可有多张图片) --- ## ✅ 解决方案 ### 1. 新增 `_sync_storyboard_resources` 方法 **文件**: `server/app/services/screenplay_service.py` **功能**: 为 AI 拆解的所有标签自动创建占位符资源 **方法签名**: ```python async def _sync_storyboard_resources( self, screenplay_id: UUID, project_id: UUID, tag_id_maps: Dict[str, Dict[str, UUID]] ) -> int: """为标签创建占位符项目资源记录 逻辑说明: 1. 遍历所有标签(角色/场景/道具标签) 2. 为每个标签创建一条 project_resources 记录(占位符) 3. 不创建实际文件,仅关联 element_tag_id 4. 后续由 AI 生成图片并更新 file_url """ ``` **关键特性**: - ✅ **占位符机制**: `file_url = "placeholder://pending-ai-generation"` - ✅ **去重逻辑**: 检查 `element_tag_id` 是否已有关联资源 - ✅ **元数据追踪**: 标记 `source: 'ai_screenplay_parsing'`, `auto_generated: true` - ✅ **冗余字段**: 存储 `element_name` 和 `tag_label` 便于查询 - ✅ **事务安全**: 使用 `flush()` 而非 `commit()`,依赖外层事务 ### 2. 集成到 AI 解析流程 **调用时机**: 在 `store_parsed_elements` 方法的分镜创建后 ```python # 5. 存储分镜(如果启用) storyboard_ids = await self._create_storyboards_from_ai(...) # 6. 同步项目资源关联(新增) await self._sync_storyboard_resources( screenplay_id=screenplay_id, project_id=screenplay.project_id, tag_id_maps=tag_id_maps ) ``` ### 3. 占位符资源示例 ```python ProjectResource( project_resource_id=UUID_v7, project_id=project_id, name="孙悟空 - 青年", type=ResourceType.CHARACTER, # 1 description="AI 拆解剧本自动生成的 孙悟空 (青年) 占位符", file_url="placeholder://pending-ai-generation", # 占位符 checksum="pending", # 占位符 element_tag_id=tag_id, # 关联标签 element_name="孙悟空", # 冗余字段 tag_label="青年", # 冗余字段 created_by=screenplay.created_by, meta_data={ 'source': 'ai_screenplay_parsing', 'screenplay_id': str(screenplay_id), 'element_type': 1, # CHARACTER 'element_id': str(character_id), 'status': 'pending', # pending → generating → completed 'auto_generated': True } ) ``` --- ## 🧪 测试验证 ### 测试文件 `server/tests/integration/test_data_integrity.py` ### 新增验证:项目资源占位符 ```python # 验证 7: 项目资源占位符(project_resources) stmt = select(ProjectResource).where( ProjectResource.project_id == subproject.id, ProjectResource.deleted_at.is_(None) ) result = await db_session.execute(stmt) resources = result.scalars().all() assert len(resources) == 9, f"应创建 9 个占位符资源" ``` ### 测试结果 ``` ✅ 项目资源占位符验证通过 - 总资源数: 9 个 🎉 数据完整性测试通过! 📊 最终统计: - 角色: 3 个 - 场景: 2 个 - 道具: 2 个 - 标签: 9 个 - 分镜: 3 个 - 元素关联: 9 个 - 项目资源: 9 个 ← 新增验证 ``` **验证指标**: 100% 通过 - 创建资源数 = 标签数(9 个) - 每个标签一个占位符资源 - 资源正确关联到 `project_id` --- ## 📊 技术影响分析 ### 代码变更统计 | 文件 | 变更类型 | 行数 | |------|---------|------| | `screenplay_service.py` | 新增方法 | +130 | | `screenplay_service.py` | 集成调用 | +5 | | `test_data_integrity.py` | 新增验证 | +15 | ### 数据流程 ``` AI 解析剧本 ↓ 创建元素(角色/场景/道具) ↓ 创建标签(screenplay_element_tags) ↓ 创建分镜(storyboards + storyboard_items) ↓ [新增] 创建占位符资源(project_resources)← 本次实现 ↓ [未来] AI 生成图片并更新 file_url ``` ### 关键技术点 1. **避免 Lazy Loading** - 问题:`tag.element_name` 是 `@property`,访问时触发 relationship 查询 - 解决:使用预先构建的 `element_type_map` 字典 2. **事务管理** - 使用 `self.db.add()` + `await self.db.flush()` - 不调用 `commit()`,依赖外层事务控制 3. **数据库约束** - `created_by` 不能为 NULL → 从 `screenplay.created_by` 获取 4. **字段映射** - `ElementType.CHARACTER` (1) → `ResourceType.CHARACTER` (1) - `ElementType.LOCATION` (2) → `ResourceType.SCENE` (2) - `ElementType.PROP` (3) → `ResourceType.PROP` (3) --- ## 🔗 关联问题 ### 已解决 - ✅ **P1-01**: `project_resources` 表未关联(本次实现) ### 待解决(Phase 3) - ⏳ **P1-02**: AI Prompt 文档与实际实现不一致(标签格式已修复) - ⏳ 实现 AI 图片生成功能(更新 `file_url`) --- ## 📝 开发者注意事项 ### 占位符资源查询 **查询所有待生成资源**: ```python stmt = select(ProjectResource).where( ProjectResource.file_url == "placeholder://pending-ai-generation", ProjectResource.deleted_at.is_(None) ) ``` **按标签查询资源**: ```python stmt = select(ProjectResource).where( ProjectResource.element_tag_id == tag_id, ProjectResource.deleted_at.is_(None) ) ``` ### AI 图片生成流程(待实现) 1. **查询待生成资源**: 筛选 `status = 'pending'` 2. **调用 AI 图片生成服务**: 传入 `element_name`, `tag_label`, `description` 3. **上传图片到存储**: 获取 `file_url`, `checksum`, `width`, `height` 4. **更新资源记录**: ```python resource.file_url = uploaded_url resource.checksum = file_checksum resource.width = image_width resource.height = image_height resource.meta_data['status'] = 'completed' ``` ### 用户界面影响 **素材管理页面**: - 现在可以看到 AI 拆解的所有元素占位符 - 占位符资源显示为 "待生成" 状态 - 用户可以手动上传图片替换占位符 --- ## ✨ 总结 本次实现完成了 **项目资源自动关联功能**,填补了 AI 拆解剧本与素材管理之间的数据链路。 **核心价值**: 1. ✅ 打通了三层架构的完整数据流 2. ✅ 为 AI 图片生成功能准备了目标记录 3. ✅ 用户可在素材库中查看 AI 拆解的所有元素 4. ✅ 支持多集项目的资源共享 **下一步**: - Phase 3: 实现 AI 图片生成服务 - Phase 3: 完成 `file_url` 更新逻辑 - Phase 3: 前端展示占位符资源状态 --- **审核**: Pending **部署**: Pending **文档更新**: ✅ 已完成