You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8.5 KiB
8.5 KiB
Changelog: 实现资源默认命名逻辑
日期: 2026-02-12
类型: Feature
影响范围: 后端 Service 层
变更概述
在 StoryboardResourceService 中实现了资源默认命名生成逻辑,当用户创建分镜图片或视频时,如果未提供自定义名称,系统将自动生成规范化的默认名称。
实现内容
1. 新增辅助方法
文件: server/app/services/storyboard_resource_service.py
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 方法
变更点:
- 在创建图片前查询分镜信息(获取 order_index 和 title)
- 检查用户是否提供了自定义名称
- 如果未提供,调用
_generate_default_resource_name()生成默认名称 - 将名称和描述字段添加到 StoryboardImage 对象
代码片段:
# 获取分镜信息(用于生成默认名称)
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}
组成部分
-
镜号:3位数字,左侧补零(如 001, 002, 012)
- 来源:
storyboards.order_index
- 来源:
-
分镜名称:分镜的标题
- 来源:
storyboards.title
- 来源:
-
方案编号: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 自动生成图片
# 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:用户自定义命名
# 用户提供自定义名称
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:多版本管理
# 第一个图片
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:删除资源后的编号
# 假设已有 3 个图片:方案01, 方案02, 方案03
# 删除方案02后,再创建新图片
# 当前图片数量:2(方案01, 方案03)
new_image = await service.create_image(...)
# new_image.name = "001-开场镜头-方案03" (基于当前数量 2 + 1)
# 注意:删除资源不会影响已有资源的名称
# 新资源的方案编号基于当前实际数量
性能影响
额外查询
每次创建资源时需要额外查询分镜信息:
SELECT * FROM storyboards WHERE storyboard_id = $1;
影响评估:
- 查询开销:~1-2ms(有索引)
- 频率:仅在创建资源时
- 可接受性:✅ 创建操作本身就是低频操作
优化建议
如果未来需要优化性能,可以考虑:
- 缓存分镜信息:在 Redis 中缓存分镜的 order_index 和 title
- 批量创建优化:如果需要批量创建资源,可以一次查询分镜信息,复用于多个资源
日志记录
新增日志输出:
logger.debug("自动生成资源名称: %s", name)
logger.info("分镜图片创建成功: image_id=%s, name=%s, version=%d", ...)
logger.info("分镜视频创建成功: video_id=%s, name=%s, version=%d", ...)
向后兼容性
- ✅ 完全向后兼容
- ✅ 现有 API 调用不受影响
- ✅ 用户可以选择提供或不提供名称
测试建议
单元测试
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 == "自定义名称"
集成测试
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")
长期
- 支持命名规则的项目级配置
- 支持命名冲突检测和自动重命名
相关文档
变更记录
- 2026-02-12: 初始版本,实现默认命名逻辑