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.
5.8 KiB
5.8 KiB
统一 fileUrl 字段命名
日期: 2026-02-06
类型: API 优化
关联: RFC 140 - 剧本文件存储重构
🎯 目标
统一剧本相关接口的 fileUrl 字段命名,确保所有接口返回的解析后 Markdown 文件 URL 字段名保持一致。
📋 变更内容
修改前
不同接口使用不同的字段名返回 Markdown 文件 URL:
// 列表接口 GET /api/v1/screenplays
{
"parsedFileUrl": "http://..." // ❌ 使用 parsedFileUrl
}
// 状态接口 GET /api/v1/screenplays/{id}/parse-status
{
"parsedFileUrl": "http://..." // ❌ 使用 parsedFileUrl
}
修改后
所有接口统一使用 fileUrl 字段名:
// 列表接口 GET /api/v1/screenplays
{
"fileUrl": "http://..." // ✅ 统一使用 fileUrl
}
// 状态接口 GET /api/v1/screenplays/{id}/parse-status
{
"fileUrl": "http://..." // ✅ 统一使用 fileUrl
}
🔧 实现细节
1. 修改 ScreenplayResponse Schema
文件: server/app/schemas/screenplay.py
class ScreenplayResponse(BaseModel):
# 内部字段(不对外暴露)
file_url: Optional[str] = Field(
None,
description="解析后Markdown文件存储路径(相对路径,内部字段)",
exclude=True # ← 不在 API 响应中返回
)
# 对外字段(computed field)
@computed_field(alias="fileUrl") # ← 修改 alias 从 "parsedFileUrl" 为 "fileUrl"
@property
def parsed_file_url(self) -> Optional[str]:
"""动态生成解析后 Markdown 文件的完整访问 URL"""
return build_file_url(self.file_url) if self.file_url else None
2. 修改 ParseStatusResponse Schema
文件: server/app/schemas/screenplay.py
class ParseStatusResponse(BaseModel):
# 内部字段(不对外暴露)
file_url: Optional[str] = Field(
None,
description="内部字段,用于计算 fileUrl",
exclude=True # ← 不在 API 响应中返回
)
# 对外字段(computed field)
@computed_field(alias="fileUrl") # ← 修改 alias 从 "parsedFileUrl" 为 "fileUrl"
@property
def parsed_file_url(self) -> Optional[str]:
"""动态生成解析后 Markdown 文件的完整访问 URL(解析完成时返回)"""
return build_file_url(self.file_url) if self.file_url else None
📊 API 响应对比
剧本列表接口
接口: GET /api/v1/screenplays
修改前
{
"data": {
"items": [
{
"screenplayId": "019c325e-ef62-7e72-a4b3-c5bc5577170b",
"name": "测试剧本",
"parsedFileUrl": "http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md"
}
]
}
}
修改后
{
"data": {
"items": [
{
"screenplayId": "019c325e-ef62-7e72-a4b3-c5bc5577170b",
"name": "测试剧本",
"fileUrl": "http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md"
}
]
}
}
解析状态接口
接口: GET /api/v1/screenplays/{screenplay_id}/parse-status
修改前
{
"data": {
"screenplayId": "019c325e-ef62-7e72-a4b3-c5bc5577170b",
"parsingStatus": "completed",
"wordCount": 27221,
"parsedFileUrl": "http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md",
"message": "解析完成"
}
}
修改后
{
"data": {
"screenplayId": "019c325e-ef62-7e72-a4b3-c5bc5577170b",
"parsingStatus": "completed",
"wordCount": 27221,
"fileUrl": "http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md",
"message": "解析完成"
}
}
🔄 字段说明
数据库层(内部)
- 字段名:
file_url - 类型:
VARCHAR - 存储内容: 相对路径(如
screenplays/019c325e-ef62-7e72-a4b3-c5bc5577170b.md) - Schema 配置:
exclude=True(不对外暴露)
API 层(对外)
- 字段名:
fileUrl(camelCase) - 类型:
string - 返回内容: 完整访问 URL(如
http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md) - 生成方式: Pydantic
computed_field通过build_file_url()动态构建
🎯 优势
1. 命名一致性
✅ 所有接口使用统一的 fileUrl 字段名
✅ 前端开发者无需记忆不同接口的不同字段名
✅ 减少前端代码中的字段映射逻辑
2. 语义清晰
fileUrl- 表示"文件的 URL",语义明确- 相比
parsedFileUrl,更简洁且符合 REST API 命名习惯
3. 向后兼容
- 数据库层字段保持不变(
file_url) - 仅 API 响应字段名变化
- 通过
exclude=True确保内部字段不泄露
🧪 前端适配
修改前
// 列表接口
const screenplay = response.data.items[0];
const fileUrl = screenplay.parsedFileUrl; // ❌ parsedFileUrl
// 状态接口
const status = response.data;
const fileUrl = status.parsedFileUrl; // ❌ parsedFileUrl
修改后
// 列表接口
const screenplay = response.data.items[0];
const fileUrl = screenplay.fileUrl; // ✅ 统一 fileUrl
// 状态接口
const status = response.data;
const fileUrl = status.fileUrl; // ✅ 统一 fileUrl
📝 相关文档
✨ 总结
通过统一 fileUrl 字段命名:
- ✅ 提升 API 一致性和可维护性
- ✅ 简化前端代码逻辑
- ✅ 符合 RESTful API 最佳实践
- ✅ 保持数据库层与 API 层的职责分离
Breaking Change: 前端需要将 parsedFileUrl 改为 fileUrl。