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.3 KiB

API 响应格式与异常处理标准化迁移

日期: 2026-02-11
类型: 重构
影响范围: 8 个 API 文件
破坏性变更: 无(仅内部实现改动)

变更概述

将 8 个 API 路由文件迁移到统一的响应格式和异常处理规范,与 ai_conversations.py 保持一致。

变更详情

1. 统一响应格式

变更前

# 使用旧的 ApiResponse 或 success_response
from app.schemas.response import ApiResponse, success_response

return ApiResponse(code=200, message="Success", data=...)
return success_response(data=...)

变更后

# 使用标准的 SuccessResponse
from app.schemas.common import SuccessResponse

return SuccessResponse(data=...)

2. 统一异常处理

变更前

# 手动捕获异常并转换为 HTTPException
try:
    result = await service.some_method()
    return ApiResponse(...)
except ValidationError as e:
    raise HTTPException(status_code=400, detail=str(e))
except NotFoundError as e:
    raise HTTPException(status_code=404, detail=str(e))

变更后

# 直接调用 Service,让异常向上传播
result = await service.some_method()
return SuccessResponse(data=result)

原理:Service 层直接抛出 app.core.exceptions 中的自定义异常(如 NotFoundError, ValidationError),这些异常继承自 HTTPException,FastAPI 的异常处理中间件会自动转换为正确的 HTTP 响应。

3. 查询参数 camelCase 支持

变更前

# 缺少 camelCase alias
scene_id: str = Query(..., description="场景ID")
storage_path: str = Query(..., description="对象存储路径")

变更后

# 添加 camelCase alias
scene_id: str = Query(..., alias="sceneId", description="场景ID")
storage_path: str = Query(..., alias="storagePath", description="对象存储路径")

涉及文件

已迁移文件

文件 变更内容
wechat.py 替换 ApiResponseSuccessResponse
删除异常捕获
添加查询参数 camelCase alias
users.py 替换 success_responseSuccessResponse
删除 .model_dump() 调用
直接返回 Pydantic Schema
health.py 替换 success_responseSuccessResponse
删除数据库异常处理(交给 Service 层)
folders.py 替换 ApiResponse/success_responseSuccessResponse
查询参数已有 camelCase 支持
file_storage.py 替换响应格式
删除异常捕获
添加查询参数 storagePath alias
auth.py 替换 ApiResponse.create_successSuccessResponse
删除异常捕获
attachments.py 替换 ApiResponse.create_successSuccessResponse
删除 .to_dict() 调用
直接返回 Pydantic Schema
ai.py 删除所有 14 个路由的手动异常捕获
清理冗余的 try-except 块
日志记录保留
credits.py 已符合标准(无需修改)

技术细节

Response Schema 自动序列化

FastAPI 会自动将 response_model 中指定的 Pydantic Schema 序列化为 JSON,包括:

  • 字段名转换(通过 alias 配置)
  • 日期时间格式化
  • UUID 转字符串

因此不再需要手动调用 .model_dump(by_alias=True, mode='json')

异常传播机制

# Service 层
from app.core.exceptions import NotFoundError

async def get_item(item_id: UUID):
    item = await repository.find_by_id(item_id)
    if not item:
        raise NotFoundError("项目不存在")  # 继承自 HTTPException(status_code=404)
    return item

# API 层
@router.get("/{item_id}", response_model=SuccessResponse[ItemResponse])
async def get_item_endpoint(item_id: UUID, service = Depends(get_service)):
    item = await service.get_item(item_id)  # 异常自动传播
    return SuccessResponse(data=ItemResponse.model_validate(item))

验证方法

1. 响应格式验证

# 所有 API 响应应符合统一格式
curl http://localhost:8000/api/v1/users/me | jq

# 预期输出
{
  "code": 200,
  "message": "success",
  "data": { ... },
  "timestamp": "2026-02-11T10:00:00Z"
}

2. 异常处理验证

# 测试 404 错误
curl http://localhost:8000/api/v1/folders/invalid-uuid | jq

# 预期输出
{
  "detail": "文件夹不存在"
}

3. camelCase 查询参数验证

# 测试 camelCase 查询参数
curl "http://localhost:8000/api/v1/wechat/result?sceneId=test123" | jq
curl "http://localhost:8000/api/v1/file_storage/presigned-url?storagePath=/path/to/file" | jq

兼容性说明

  • 前端兼容: 响应格式保持一致,前端无需修改
  • 查询参数兼容: 支持 camelCase 和 snake_case 两种格式(通过 populate_by_name=True
  • 异常响应格式: FastAPI 标准格式,与现有前端错误处理逻辑一致

后续优化

  1. 完成所有 API 文件的标准化迁移
  2. ⚠️ 建议:统一删除旧的 app.schemas.response.ApiResponsesuccess_response
  3. ⚠️ 建议:在 Service 层统一使用自定义异常,避免返回错误码字典

参考

  • 标准实现:server/app/api/v1/ai_conversations.py
  • 异常定义:server/app/core/exceptions.py
  • 响应 Schema:server/app/schemas/common.py