# Changelog: 修复分镜 API metadata 字段命名格式 **日期**: 2026-02-11 **类型**: Bug 修复 **影响范围**: `/api/v1/storyboards` 接口 ## 问题描述 分镜接口返回的 `metadata` 字段内部使用 snake_case 命名(如 `screenplay_id`、`character_tags`),不符合项目 API 规范(应使用 camelCase)。 ### 问题示例 ```json { "metadata": { "screenplay_id": "019c4b4b-c98f-76c3-9a9c-19cb6b125614", "character_tags": {"孙悟空": "youth"}, "prop_tags": {} } } ``` ## 根本原因 1. `StoryboardResponse` 使用手动 `alias` 定义字段别名,未使用 `alias_generator` 2. `metadata` 字段直接从数据库 `meta_data` (JSONB) 读取,内部键名未转换 ## 解决方案 ### 1. 创建通用工具函数 **文件**: `server/app/utils/case_converter.py` ```python def to_camel(string: str) -> str: """将 snake_case 转换为 camelCase""" components = string.split('_') return components[0] + ''.join(x.title() for x in components[1:]) def convert_dict_to_camel(data: Any) -> Any: """递归转换字典键名从 snake_case 到 camelCase""" if isinstance(data, dict): return { to_camel(key): convert_dict_to_camel(value) for key, value in data.items() } elif isinstance(data, list): return [convert_dict_to_camel(item) for item in data] else: return data ``` ### 2. 更新 Schema 定义 **文件**: `server/app/schemas/storyboard.py` #### 修改前 ```python class StoryboardResponse(BaseModel): model_config = ConfigDict(from_attributes=True, populate_by_name=True) storyboard_id: str = Field(..., alias="storyboardId") project_id: str = Field(..., alias="projectId") # ... 手动定义所有 alias metadata: Dict[str, Any] = Field(default_factory=dict) ``` #### 修改后 ```python from pydantic import model_serializer from app.utils.case_converter import to_camel, convert_dict_to_camel class StoryboardResponse(BaseModel): model_config = ConfigDict( from_attributes=True, populate_by_name=True, alias_generator=to_camel # 自动生成 camelCase 别名 ) storyboard_id: str = Field(...) project_id: str = Field(...) # ... 无需手动定义 alias metadata: Dict[str, Any] = Field(default_factory=dict) @model_serializer def serialize_model(self) -> Dict[str, Any]: """序列化时转换 metadata 内部键名为 camelCase""" data = {to_camel(k): v for k, v in self.__dict__.items()} if 'metadata' in data and data['metadata']: data['metadata'] = convert_dict_to_camel(data['metadata']) return data ``` ### 3. 同步更新的 Schema - `StoryboardResponse` - `StoryboardItemResponse` - `StoryboardListResponse` - `StoryboardDurationStats` - `StoryboardReorderResponse` ## 修复效果 ### 修复后返回格式 ```json { "metadata": { "screenplayId": "019c4b4b-c98f-76c3-9a9c-19cb6b125614", "characterTags": {"孙悟空": "youth"}, "propTags": {}, "locationTags": {} } } ``` ## 技术细节 ### alias_generator 工作原理 1. **顶层字段**: `alias_generator` 自动将 `storyboard_id` 转换为 `storyboardId` 2. **嵌套字典**: `model_serializer` 递归转换 `metadata` 内部的所有键名 ### 参考实现 参考了 `server/app/schemas/project_element.py` 的实现模式: - 使用 `alias_generator=to_camel` 自动转换字段名 - 使用 `model_serializer` 处理特殊序列化逻辑 ## 影响评估 ### 破坏性变更 ⚠️ **前端需要同步更新** 如果前端已经在使用 snake_case 访问 `metadata` 字段,需要更新为 camelCase: ```typescript // 修改前 const screenplayId = storyboard.metadata.screenplay_id; const characterTags = storyboard.metadata.character_tags; // 修改后 const screenplayId = storyboard.metadata.screenplayId; const characterTags = storyboard.metadata.characterTags; ``` ### 兼容性 - 数据库无需变更(仍存储 snake_case) - 仅影响 API 响应格式 - 不影响请求参数格式 ## 测试建议 1. **单元测试**: 验证 `convert_dict_to_camel` 递归转换逻辑 2. **集成测试**: 验证 `/api/v1/storyboards` 接口返回格式 3. **前端测试**: 确认前端能正确解析新格式 ## 相关文件 - `server/app/utils/case_converter.py` (新增) - `server/app/schemas/storyboard.py` (修改) - `server/app/api/v1/storyboards.py` (无需修改) ## 后续优化 可考虑将其他 Schema 的 `metadata` 字段也统一使用此模式: - `server/app/schemas/screenplay.py` - `server/app/schemas/project_element_tag.py` - `server/app/schemas/screenplay_tag.py`