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

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 方法

变更点

  1. 在创建图片前查询分镜信息(获取 order_index 和 title)
  2. 检查用户是否提供了自定义名称
  3. 如果未提供,调用 _generate_default_resource_name() 生成默认名称
  4. 将名称和描述字段添加到 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}

组成部分

  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 自动生成图片

# 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(有索引)
  • 频率:仅在创建资源时
  • 可接受性: 创建操作本身就是低频操作

优化建议

如果未来需要优化性能,可以考虑:

  1. 缓存分镜信息:在 Redis 中缓存分镜的 order_index 和 title
  2. 批量创建优化:如果需要批量创建资源,可以一次查询分镜信息,复用于多个资源

日志记录

新增日志输出:

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: 初始版本,实现默认命名逻辑