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

统一 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