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.
4.6 KiB
4.6 KiB
Changelog: 修复分镜 API metadata 字段命名格式
日期: 2026-02-11
类型: Bug 修复
影响范围: /api/v1/storyboards 接口
问题描述
分镜接口返回的 metadata 字段内部使用 snake_case 命名(如 screenplay_id、character_tags),不符合项目 API 规范(应使用 camelCase)。
问题示例
{
"metadata": {
"screenplay_id": "019c4b4b-c98f-76c3-9a9c-19cb6b125614",
"character_tags": {"孙悟空": "youth"},
"prop_tags": {}
}
}
根本原因
StoryboardResponse使用手动alias定义字段别名,未使用alias_generatormetadata字段直接从数据库meta_data(JSONB) 读取,内部键名未转换
解决方案
1. 创建通用工具函数
文件: server/app/utils/case_converter.py
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
修改前
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)
修改后
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
StoryboardResponseStoryboardItemResponseStoryboardListResponseStoryboardDurationStatsStoryboardReorderResponse
修复效果
修复后返回格式
{
"metadata": {
"screenplayId": "019c4b4b-c98f-76c3-9a9c-19cb6b125614",
"characterTags": {"孙悟空": "youth"},
"propTags": {},
"locationTags": {}
}
}
技术细节
alias_generator 工作原理
- 顶层字段:
alias_generator自动将storyboard_id转换为storyboardId - 嵌套字典:
model_serializer递归转换metadata内部的所有键名
参考实现
参考了 server/app/schemas/project_element.py 的实现模式:
- 使用
alias_generator=to_camel自动转换字段名 - 使用
model_serializer处理特殊序列化逻辑
影响评估
破坏性变更
⚠️ 前端需要同步更新
如果前端已经在使用 snake_case 访问 metadata 字段,需要更新为 camelCase:
// 修改前
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 响应格式
- 不影响请求参数格式
测试建议
- 单元测试: 验证
convert_dict_to_camel递归转换逻辑 - 集成测试: 验证
/api/v1/storyboards接口返回格式 - 前端测试: 确认前端能正确解析新格式
相关文件
server/app/utils/case_converter.py(新增)server/app/schemas/storyboard.py(修改)server/app/api/v1/storyboards.py(无需修改)
后续优化
可考虑将其他 Schema 的 metadata 字段也统一使用此模式:
server/app/schemas/screenplay.pyserver/app/schemas/project_element_tag.pyserver/app/schemas/screenplay_tag.py