# 分镜服务完整实现总结 **日期**:2026-02-04 **类型**:功能实现总结 **影响范围**:后端服务层、API 层、数据库 --- ## 实现概述 完成了分镜管理系统的核心功能实现,包括: 1. 分镜基础管理(CRUD) 2. 分镜看板服务(可视化展示) 3. 项目素材引用计数(已有字段支持) --- ## 已完成功能 ### 1. 分镜基础管理 **数据库表**: - ✅ `storyboards` - 分镜主表 - ✅ `storyboard_items` - 分镜元素关联表(统一管理 ElementTag 和 Resource) **核心功能**: - ✅ 分镜 CRUD 操作 - ✅ 分镜排序管理(`order_index` 字段) - ✅ 分镜元素关联(ElementTag + Resource 统一管理) - ✅ 分镜筛选(按景别、运镜) - ✅ 分镜搜索(全文搜索) - ✅ 时长统计 **API 端点**(13个): - ✅ GET `/storyboards` - 获取分镜列表 - ✅ GET `/storyboards/{id}` - 获取分镜详情 - ✅ POST `/storyboards` - 创建分镜 - ✅ PATCH `/storyboards/{id}` - 更新分镜 - ✅ DELETE `/storyboards/{id}` - 删除分镜 - ✅ POST `/storyboards/reorder` - 重新排序 - ✅ GET `/storyboards/filter` - 筛选分镜 - ✅ GET `/storyboards/search` - 搜索分镜 - ✅ GET `/storyboards/statistics/duration` - 时长统计 - ✅ POST `/storyboards/{id}/items` - 添加元素 - ✅ DELETE `/storyboards/{id}/items/{item_id}` - 移除元素 - ✅ PATCH `/storyboards/{id}/items/{item_id}` - 更新元素 - ✅ POST `/storyboards/{id}/items/reorder` - 调整元素顺序 ### 2. 分镜看板服务 **设计特点**: - ✅ 无独立数据存储(实时从分镜表计算) - ✅ 六种轨道类型支持(当前实现 storyboard 轨道) - ✅ 时间轴计算(累积计算每个分镜的 start_time 和 end_time) **API 端点**(4个): - ✅ GET `/projects/{project_id}/storyboard-board` - 获取看板数据 - ✅ POST `/projects/{project_id}/storyboard-board/reorder` - 调整分镜顺序 - ✅ GET `/projects/{project_id}/storyboard-board/tracks/{track_type}` - 获取轨道数据 - ✅ GET `/projects/{project_id}/storyboard-board/items/time-range` - 时间范围查询 ### 3. 项目素材引用计数 **数据库字段**: - ✅ `project_resources.usage_count` - 引用计数字段(已存在) - ✅ CHECK 约束:`usage_count >= 0` - ✅ 索引:`idx_project_resources_usage_count` **实现状态**: - ⚠️ 字段已存在,但引用计数维护逻辑需要在 `StoryboardService` 中实现 - ⚠️ 当前 `add_element_to_storyboard()` 和 `remove_element_from_storyboard()` 方法未包含计数更新逻辑 --- ## 技术栈符合性 ✅ **完全符合 jointo-tech-stack 规范**: 1. **数据库设计** - UUID v7 应用层生成(`generate_uuid()`) - 无物理外键约束 - 枚举类型使用 SMALLINT + IntEnum - 时间戳使用 TIMESTAMPTZ - 全文搜索索引(GIN + pg_trgm) 2. **日志系统** - 使用标准库 `logging` - 使用 %-formatting 格式化 - 异常日志使用 `exc_info=True` 3. **异步编程** - 所有数据库操作使用 `async/await` - 使用 `AsyncSession` 4. **事务管理** - Repository 使用 `flush()` - Service 使用 `commit()` - 批量操作使用事务 5. **统一响应格式** - 所有 API 使用 `ApiResponse` 格式 --- ## 数据库表结构 ### storyboards 表 | 字段 | 类型 | 说明 | |------|------|------| | storyboard_id | UUID | 主键(UUID v7) | | project_id | UUID | 所属项目 | | title | VARCHAR(255) | 分镜标题 | | description | TEXT | 分镜描述 | | shooting_description | TEXT | 拍摄描述 | | shot_size | SMALLINT | 景别类型(1-8) | | camera_movement | SMALLINT | 运镜类型(1-9) | | estimated_duration | NUMERIC(10,3) | 预估时长(秒) | | actual_duration | NUMERIC(10,3) | 实际时长(秒) | | start_time | NUMERIC(10,3) | 时间轴开始时间 | | end_time | NUMERIC(10,3) | 时间轴结束时间 | | thumbnail_url | TEXT | 缩略图 URL | | thumbnail_id | UUID | 缩略图附件 ID | | order_index | INTEGER | 显示顺序(镜号) | | transition_type | TEXT | 转场类型 | | transition_duration | NUMERIC(5,2) | 转场时长 | | metadata | JSONB | 扩展字段 | | created_at | TIMESTAMPTZ | 创建时间 | | updated_at | TIMESTAMPTZ | 更新时间 | **索引**: - `idx_storyboards_project_id` - `idx_storyboards_order` - `idx_storyboards_shot_size` - `idx_storyboards_camera_movement` - `idx_storyboards_thumbnail_id` - `idx_storyboards_metadata_gin` - `idx_storyboards_title_trgm`(全文搜索) - `idx_storyboards_description_trgm`(全文搜索) - `idx_storyboards_shooting_description_trgm`(全文搜索) **约束**: - `storyboards_order_unique`:`(project_id, order_index)` 唯一 - `storyboards_time_check`:`end_time > start_time` - `storyboards_duration_check`:时长必须 > 0 ### storyboard_items 表 | 字段 | 类型 | 说明 | |------|------|------| | item_id | UUID | 主键(UUID v7) | | storyboard_id | UUID | 所属分镜 | | item_type | SMALLINT | 元素类型(1=ElementTag, 2=Resource) | | element_tag_id | UUID | 剧本元素标签 ID | | resource_id | UUID | 项目素材 ID | | element_name | TEXT | 元素名称(冗余) | | tag_label | TEXT | 标签名称(冗余) | | cover_url | TEXT | 封面 URL(冗余) | | is_visible | BOOLEAN | 是否在画面内 | | spatial_position | TEXT | 画面位置 | | action_description | TEXT | 动作描述 | | display_order | INTEGER | 显示顺序 | | z_index | INTEGER | 视觉层级 | | metadata | JSONB | 扩展属性 | | created_at | TIMESTAMPTZ | 创建时间 | **索引**: - `idx_storyboard_items_storyboard_id` - `idx_storyboard_items_element_tag` - `idx_storyboard_items_resource` - `idx_storyboard_items_order` - `idx_storyboard_items_type` **约束**: - `storyboard_items_one_fk_check`:确保只有一个外键字段非空 - `storyboard_items_tag_unique`:`(storyboard_id, element_tag_id)` 唯一 - `storyboard_items_resource_unique`:`(storyboard_id, resource_id)` 唯一 --- ## 待实现功能 ### 1. 引用计数维护逻辑 需要在 `StoryboardService` 中实现: ```python async def add_element_to_storyboard(self, ...): # ... 现有逻辑 ... # 如果是项目素材,增加引用计数 if item_type == ItemType.RESOURCE and resource_id: await self.session.execute( update(ProjectResource) .where(ProjectResource.project_resource_id == resource_id) .values(usage_count=ProjectResource.usage_count + 1) ) # ... 提交事务 ... async def remove_element_from_storyboard(self, ...): # ... 现有逻辑 ... # 如果是项目素材,减少引用计数 if item.item_type == ItemType.RESOURCE and item.resource_id: await self.session.execute( update(ProjectResource) .where(ProjectResource.project_resource_id == item.resource_id) .values(usage_count=ProjectResource.usage_count - 1) ) # ... 提交事务 ... ``` ### 2. 删除保护 需要在 `ProjectResourceService.delete_resource()` 中添加: ```python async def delete_resource(self, user_id, resource_id, force=False): resource = await self.repo.get_by_id(resource_id) # 检查引用计数 if resource.usage_count > 0 and not force: raise ValidationError( "该素材正在被 %d 个分镜使用,无法删除。" "请先从分镜中移除该素材,或使用强制删除。" % resource.usage_count ) # ... 删除逻辑 ... ``` ### 3. 其他轨道数据 在 `StoryboardBoardService._calculate_storyboard_board_tracks()` 中添加: - resource 轨道(从 `storyboard_items` 查询) - video 轨道(需要实现 `storyboard_videos` 表) - sound 轨道(需要实现 `storyboard_sound_effects` 表) - subtitle 轨道(需要实现 `storyboard_dialogues` 表) - voice 轨道(需要实现 `storyboard_voiceovers` 表) --- ## 性能优化建议 ### 1. 分镜看板缓存 ```python # 使用 Redis 缓存分镜看板数据(5 分钟) cache_key = "storyboard-board:%s" % project_id cached_data = await redis_client.get(cache_key) if cached_data: return StoryboardBoardData.parse_raw(cached_data) # 计算数据并缓存 storyboard_board_data = await self.get_storyboard_board_data(...) await redis_client.setex(cache_key, 300, storyboard_board_data.json()) ``` ### 2. 分页加载 对于大型项目(100+ 分镜),实现分页: ```python async def get_storyboard_board_data_paginated( self, user_id, project_id, page=1, page_size=50 ): offset = (page - 1) * page_size storyboards = await self.storyboard_repo.get_by_project( project_id, order_by='order_index', limit=page_size, offset=offset ) # ... 计算轨道数据 ... ``` ### 3. 预加载关联数据 使用 `selectinload()` 减少 N+1 查询: ```python from sqlalchemy.orm import selectinload storyboards = await session.execute( select(Storyboard) .where(Storyboard.project_id == project_id) .options( selectinload(Storyboard.items), selectinload(Storyboard.generated_video), selectinload(Storyboard.dialogues) ) .order_by(Storyboard.order_index) ) ``` --- ## 测试建议 ### 单元测试 **StoryboardService**: - `test_create_storyboard()` - 测试创建分镜 - `test_update_storyboard()` - 测试更新分镜 - `test_delete_storyboard()` - 测试删除分镜 - `test_reorder_storyboards()` - 测试重新排序 - `test_add_element_to_storyboard()` - 测试添加元素 - `test_remove_element_from_storyboard()` - 测试移除元素 - `test_search_storyboards()` - 测试搜索功能 **StoryboardBoardService**: - `test_get_storyboard_board_data()` - 测试获取看板数据 - `test_reorder_storyboards()` - 测试调整分镜顺序 - `test_get_track_items()` - 测试获取轨道数据 - `test_get_items_by_time_range()` - 测试时间范围查询 ### 集成测试 **API 测试**: - `test_storyboard_crud_api()` - 测试分镜 CRUD 端点 - `test_storyboard_board_api()` - 测试分镜看板端点 - `test_storyboard_filter_api()` - 测试筛选端点 - `test_storyboard_search_api()` - 测试搜索端点 --- ## 相关文档 - [分镜管理服务实现](./2026-02-04-storyboard-service-implementation.md) - [分镜看板服务实现](./2026-02-04-storyboard-board-service-implementation.md) - [需求文档:分镜管理服务](../../requirements/backend/04-services/project/storyboard-service.md) - [需求文档:分镜看板服务](../../requirements/backend/04-services/project/storyboard-board-service.md) - [需求文档:分镜-项目素材关联](../../requirements/backend/04-services/project/storyboard-project-resource-association.md) - [ADR-008: 分镜统一关联表](../../architecture/adrs/008-storyboard-unified-association-table.md) --- ## 总结 分镜管理系统的核心功能已完整实现,包括: - ✅ 分镜 CRUD 和排序管理 - ✅ 分镜元素关联(统一管理 ElementTag 和 Resource) - ✅ 分镜筛选和搜索 - ✅ 分镜看板服务(实时计算,无独立存储) - ✅ 数据库表结构和索引 - ✅ 17 个 API 端点 **待完善**: - ⏸️ 引用计数维护逻辑(字段已存在,需添加更新逻辑) - ⏸️ 其他轨道数据(resource、video、sound、subtitle、voice) - ⏸️ 性能优化(缓存、分页、预加载) - ⏸️ 单元测试和集成测试 系统已具备基础的分镜管理和可视化看板功能,可以支持分镜的创建、编辑、排序、筛选、搜索和看板展示。 --- **变更作者**:Kiro AI **审核状态**:待审核 **部署状态**:待部署