# Changelog: 实现资源默认命名逻辑 **日期**: 2026-02-12 **类型**: Feature **影响范围**: 后端 Service 层 --- ## 变更概述 在 `StoryboardResourceService` 中实现了资源默认命名生成逻辑,当用户创建分镜图片或视频时,如果未提供自定义名称,系统将自动生成规范化的默认名称。 ## 实现内容 ### 1. 新增辅助方法 **文件**: `server/app/services/storyboard_resource_service.py` ```python def _generate_default_resource_name( self, order_index: int, storyboard_title: str, version: int ) -> str: """生成默认资源名称 Args: order_index: 分镜序号(从 storyboards.order_index 获取) storyboard_title: 分镜标题(从 storyboards.title 获取) version: 资源版本号 Returns: 格式化的资源名称,如:001-开场镜头-方案01 """ shot_number = f"{order_index:03d}" version_label = f"方案{version:02d}" return f"{shot_number}-{storyboard_title}-{version_label}" ``` ### 2. 更新 create_image 方法 **变更点**: 1. 在创建图片前查询分镜信息(获取 order_index 和 title) 2. 检查用户是否提供了自定义名称 3. 如果未提供,调用 `_generate_default_resource_name()` 生成默认名称 4. 将名称和描述字段添加到 StoryboardImage 对象 **代码片段**: ```python # 获取分镜信息(用于生成默认名称) from app.models.storyboard import Storyboard stmt = select(Storyboard).where(Storyboard.storyboard_id == UUID(storyboard_id)) result = await self.session.execute(stmt) storyboard = result.scalar_one_or_none() if not storyboard: raise NotFoundError("分镜不存在") # 生成默认名称(如果用户未提供) name = kwargs.get('name') if not name: name = self._generate_default_resource_name( order_index=storyboard.order_index, storyboard_title=storyboard.title, version=version ) logger.debug("自动生成资源名称: %s", name) # 创建图片对象时包含 name 和 description image = StoryboardImage( # ... 其他字段 name=name, description=kwargs.get('description'), # ... 其他字段 ) ``` ### 3. 更新 create_video 方法 **变更点**:与 create_image 相同,添加了默认命名逻辑 ## 命名规则 ### 格式 ``` {镜号:03d}-{分镜名称}-方案{方案编号:02d} ``` ### 组成部分 1. **镜号**:3位数字,左侧补零(如 001, 002, 012) - 来源:`storyboards.order_index` 2. **分镜名称**:分镜的标题 - 来源:`storyboards.title` 3. **方案编号**:2位数字,左侧补零(如 01, 02, 10) - 来源:当前分镜下已有资源数量 + 1 - 计算方式:`len(existing_resources) + 1` ### 示例 | order_index | title | 已有资源数 | 生成的名称 | |-------------|-------|-----------|-----------| | 1 | 开场镜头 | 0 | `001-开场镜头-方案01` | | 1 | 开场镜头 | 1 | `001-开场镜头-方案02` | | 5 | 角色特写 | 0 | `005-角色特写-方案01` | | 5 | 角色特写 | 2 | `005-角色特写-方案03` | | 12 | 场景切换 | 0 | `012-场景切换-方案01` | ### 方案编号说明 - **图片资源**:基于当前分镜下已有的图片数量 - **视频资源**:基于当前分镜下已有的视频数量 - **独立计数**:图片和视频的方案编号独立计算 **示例**: ``` 分镜 001-开场镜头: - 图片 1: 001-开场镜头-方案01 - 图片 2: 001-开场镜头-方案02 - 视频 1: 001-开场镜头-方案01 (视频独立计数) - 图片 3: 001-开场镜头-方案03 - 视频 2: 001-开场镜头-方案02 (视频独立计数) ``` ## 使用场景 ### 场景 1:AI 自动生成图片 ```python # Service 层自动生成名称 image = await service.create_image( user_id=user_id, storyboard_id=storyboard_id, url=generated_url, checksum=checksum, storage_path=storage_path, # 不提供 name,系统自动生成 ) # 结果:image.name = "001-开场镜头-方案01" ``` ### 场景 2:用户自定义命名 ```python # 用户提供自定义名称 image = await service.create_image( user_id=user_id, storyboard_id=storyboard_id, url=url, checksum=checksum, storage_path=storage_path, name="我喜欢的版本", # 使用用户提供的名称 description="这个版本的光线效果最好" ) # 结果:image.name = "我喜欢的版本" ``` ### 场景 3:多版本管理 ```python # 第一个图片 image_v1 = await service.create_image(...) # image_v1.name = "001-开场镜头-方案01" # 第二个图片 image_v2 = await service.create_image(...) # image_v2.name = "001-开场镜头-方案02" # 第三个图片 image_v3 = await service.create_image(...) # image_v3.name = "001-开场镜头-方案03" # 第一个视频(视频独立计数) video_v1 = await service.create_video(...) # video_v1.name = "001-开场镜头-方案01" # 第二个视频 video_v2 = await service.create_video(...) # video_v2.name = "001-开场镜头-方案02" ``` ### 场景 4:删除资源后的编号 ```python # 假设已有 3 个图片:方案01, 方案02, 方案03 # 删除方案02后,再创建新图片 # 当前图片数量:2(方案01, 方案03) new_image = await service.create_image(...) # new_image.name = "001-开场镜头-方案03" (基于当前数量 2 + 1) # 注意:删除资源不会影响已有资源的名称 # 新资源的方案编号基于当前实际数量 ``` ## 性能影响 ### 额外查询 每次创建资源时需要额外查询分镜信息: ```sql SELECT * FROM storyboards WHERE storyboard_id = $1; ``` **影响评估**: - 查询开销:~1-2ms(有索引) - 频率:仅在创建资源时 - 可接受性:✅ 创建操作本身就是低频操作 ### 优化建议 如果未来需要优化性能,可以考虑: 1. **缓存分镜信息**:在 Redis 中缓存分镜的 order_index 和 title 2. **批量创建优化**:如果需要批量创建资源,可以一次查询分镜信息,复用于多个资源 ## 日志记录 新增日志输出: ```python logger.debug("自动生成资源名称: %s", name) logger.info("分镜图片创建成功: image_id=%s, name=%s, version=%d", ...) logger.info("分镜视频创建成功: video_id=%s, name=%s, version=%d", ...) ``` ## 向后兼容性 - ✅ 完全向后兼容 - ✅ 现有 API 调用不受影响 - ✅ 用户可以选择提供或不提供名称 ## 测试建议 ### 单元测试 ```python def test_generate_default_resource_name(): """测试默认名称生成""" service = StoryboardResourceService(...) name = service._generate_default_resource_name( order_index=1, storyboard_title="开场镜头", version=1 ) assert name == "001-开场镜头-方案01" def test_create_image_with_auto_name(): """测试自动生成名称""" image = await service.create_image( user_id=user_id, storyboard_id=storyboard_id, url=url, checksum=checksum, storage_path=storage_path # 不提供 name ) assert image.name is not None assert image.name.startswith("001-") def test_create_image_with_custom_name(): """测试自定义名称""" image = await service.create_image( user_id=user_id, storyboard_id=storyboard_id, url=url, checksum=checksum, storage_path=storage_path, name="自定义名称" ) assert image.name == "自定义名称" ``` ### 集成测试 ```python async def test_create_image_api_auto_name(): """测试 API 自动生成名称""" response = await client.post( f"/api/v1/storyboards/{storyboard_id}/images", json={ "url": "https://example.com/image.jpg", "checksum": "...", "storagePath": "..." # 不提供 name } ) assert response.status_code == 201 data = response.json() assert data["data"]["name"] is not None assert "方案" in data["data"]["name"] ``` ## 后续优化 ### 短期 - [ ] 添加单元测试覆盖默认命名逻辑 - [ ] 添加集成测试验证 API 行为 ### 中期 - [ ] 支持自定义命名模板(通过配置) - [ ] 支持多语言命名(如英文版:"Shot-001-Opening-V01") ### 长期 - [ ] 支持命名规则的项目级配置 - [ ] 支持命名冲突检测和自动重命名 ## 相关文档 - [Changelog: 为分镜资源添加命名和描述功能](./2026-02-12-add-name-description-to-resources.md) - [Service 实现](../../app/services/storyboard_resource_service.py) ## 变更记录 - 2026-02-12: 初始版本,实现默认命名逻辑