# 统一 fileUrl 字段命名 **日期**: 2026-02-06 **类型**: API 优化 **关联**: RFC 140 - 剧本文件存储重构 --- ## 🎯 目标 统一剧本相关接口的 `fileUrl` 字段命名,确保所有接口返回的解析后 Markdown 文件 URL 字段名保持一致。 --- ## 📋 变更内容 ### 修改前 不同接口使用不同的字段名返回 Markdown 文件 URL: ```json // 列表接口 GET /api/v1/screenplays { "parsedFileUrl": "http://..." // ❌ 使用 parsedFileUrl } // 状态接口 GET /api/v1/screenplays/{id}/parse-status { "parsedFileUrl": "http://..." // ❌ 使用 parsedFileUrl } ``` ### 修改后 所有接口统一使用 `fileUrl` 字段名: ```json // 列表接口 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` ```python 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` ```python 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` #### 修改前 ```json { "data": { "items": [ { "screenplayId": "019c325e-ef62-7e72-a4b3-c5bc5577170b", "name": "测试剧本", "parsedFileUrl": "http://localhost:9000/jointo/screenplays/parsed/019c325e-ef62-7e72-a4b3-c5bc5577170b.md" } ] } } ``` #### 修改后 ```json { "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` #### 修改前 ```json { "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": "解析完成" } } ``` #### 修改后 ```json { "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` 确保内部字段不泄露 --- ## 🧪 前端适配 ### 修改前 ```typescript // 列表接口 const screenplay = response.data.items[0]; const fileUrl = screenplay.parsedFileUrl; // ❌ parsedFileUrl // 状态接口 const status = response.data; const fileUrl = status.parsedFileUrl; // ❌ parsedFileUrl ``` ### 修改后 ```typescript // 列表接口 const screenplay = response.data.items[0]; const fileUrl = screenplay.fileUrl; // ✅ 统一 fileUrl // 状态接口 const status = response.data; const fileUrl = status.fileUrl; // ✅ 统一 fileUrl ``` --- ## 📝 相关文档 - [RFC 140: 剧本文件存储重构](../rfcs/140-screenplay-file-storage-refactor.md) - [URL 存储策略与助手函数](../../app/core/STORAGE_URL_HELPERS.md) - [解析状态更新修复](./2026-02-06-fix-parsing-status-update.md) --- ## ✨ 总结 通过统一 `fileUrl` 字段命名: - ✅ 提升 API 一致性和可维护性 - ✅ 简化前端代码逻辑 - ✅ 符合 RESTful API 最佳实践 - ✅ 保持数据库层与 API 层的职责分离 **Breaking Change**: 前端需要将 `parsedFileUrl` 改为 `fileUrl`。