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.
 

12 KiB

API 响应格式规范化 - 全面修复

日期: 2026-02-11
类型: Breaking Change
影响范围: 41+ 个接口


概述

完成对全后端 API 接口的响应格式规范化,将 SuccessResponse[dict] 替换为明确的 Pydantic Schema,确保类型安全和 API 文档的完整性。


变更统计

模块 修复接口数 状态
Auth 2
Folders 13
Projects 9
AI 5
AI Prompts 9
AI Conversations 3
Health 2
File Storage 2
Shared Folders 3 ⏸️ 保持 dict(复杂联合类型)
Screenplays 1 ⏸️ 保持 dict(复杂结构)
WeChat 1
总计 45/47 96% 完成率

核心修改

1. 创建通用 Schema

MessageResponse (server/app/schemas/common.py)

class MessageResponse(BaseModel):
    """通用消息响应"""
    message: str = Field(..., description="响应消息")

适用场景:简单的成功/删除操作

2. Authentication 模块

修改接口

  • POST /api/v1/auth/login/phone
  • POST /api/v1/auth/refresh

新增字段

  • expiresIn: Token 过期时间(秒)
  • tokenType: 默认 "Bearer"(首字母大写)

Schema 更新

class LoginResponse(BaseModel):
    access_token: str = Field(..., alias="accessToken")
    refresh_token: str = Field(..., alias="refreshToken")
    token_type: str = Field(default="Bearer", alias="tokenType")
    expires_in: int = Field(..., alias="expiresIn")  # ← 新增
    user: UserResponse

3. Folders 模块(13 个接口)

修复接口

  • GET /api/v1/folders/treeFolderTreeResponse
  • POST /api/v1/folders/batch/moveFolderBatchOperationResponse
  • POST /api/v1/folders/batch/deleteFolderBatchOperationResponse
  • DELETE /api/v1/folders/{folder_id}MessageResponse
  • POST /api/v1/folders/{folder_id}/membersFolderMemberResponse
  • GET /api/v1/folders/{folder_id}/membersPaginatedResponse[FolderMemberResponse]
  • PUT /api/v1/folders/{folder_id}/members/{user_id}FolderMemberResponse
  • DELETE /api/v1/folders/{folder_id}/members/{user_id}MessageResponse
  • POST /api/v1/folders/{folder_id}/cloneFolderCloneResponse
  • GET /api/v1/folders/{folder_id}/statsFolderStatsResponse
  • POST /api/v1/folders/{folder_id}/exportFolderExportResponse
  • GET /api/v1/folders/export/{job_id}FolderExportResponse
  • POST /api/v1/folders/{folder_id}/share⚠️ 保持 dict(联合类型:用户分享 vs 链接分享)

Schema 修复

FolderMemberResponse

class FolderMemberResponse(BaseModel):
    # ...
    role: str  # ✅ 从 int 改为 str(匹配实际返回的 "viewer"/"editor"/"owner")

4. Projects 模块(9 个接口)

修复接口

  • GET /api/v1/projects/trashProjectTrashListResponse
  • DELETE /api/v1/projects/{project_id}ProjectDeleteResponse
  • POST /api/v1/projects/{project_id}/restoreProjectRestoreResponse
  • DELETE /api/v1/projects/{project_id}/permanentProjectDeleteResponse
  • POST /api/v1/projects/{project_id}/sharesProjectShareResponse
  • DELETE /api/v1/projects/{project_id}/shares/{share_id}MessageResponse
  • GET /api/v1/projects/{project_id}/membersProjectMemberListResponse
  • POST /api/v1/projects/{project_id}/membersProjectMemberResponse
  • DELETE /api/v1/projects/{project_id}/members/{user_id}MessageResponse

Schema 增强

ProjectTrashResponse

class ProjectTrashResponse(BaseModel):
    # ...
    parent_project_id: Optional[UUID] = Field(None, alias="parentProjectId")  # ← 新增

5. AI 模块(5 个接口)

修复接口

  • GET /api/v1/ai/voicesVoiceListResponse
  • GET /api/v1/ai/jobsPaginatedResponse[AIJobListItemResponse]
  • POST /api/v1/ai/jobs/{job_id}/cancelMessageResponse
  • GET /api/v1/ai/statisticsAIJobStatisticsResponse
  • GET /api/v1/ai/queue/statusQueueStatusResponse

新增 Schema

VoiceListResponse

class VoiceItemResponse(BaseModel):
    voice_id: str = Field(..., alias="voiceId")
    name: str
    name_cn: str = Field(..., alias="nameCn")
    category: str
    description: str
    description_cn: str = Field(..., alias="descriptionCn")
    labels: Dict[str, Any]

class VoiceListResponse(BaseModel):
    voices: List[VoiceItemResponse]

QueueStatusResponse

class QueueStatusResponse(BaseModel):
    workers: WorkerInfo
    tasks: TaskInfo
    jobs: JobQueueInfo

6. AI Prompts 模块(9 个接口)

修复接口

  • POST /api/v1/admin/ai-promptsAIPromptSystemResponse
  • GET /api/v1/admin/ai-promptsAIPromptSystemListResponse
  • GET /api/v1/admin/ai-prompts/versionsList[AIPromptSystemListItem]
  • GET /api/v1/admin/ai-prompts/default/{prompt_type}AIPromptSystemResponse
  • GET /api/v1/admin/ai-prompts/name/{name}AIPromptSystemResponse
  • GET /api/v1/admin/ai-prompts/{prompt_id}AIPromptSystemResponse
  • PATCH /api/v1/admin/ai-prompts/{prompt_id}AIPromptSystemResponse
  • POST /api/v1/admin/ai-prompts/{prompt_id}/set-defaultAIPromptSystemResponse
  • POST /api/v1/admin/ai-prompts/{prompt_id}/versionsAIPromptSystemResponse

说明:复用已有 Schema,无需创建新 Schema

7. AI Conversations 模块(3 个接口)

修复接口

  • GET /api/v1/ai-conversationsPaginatedResponse[ConversationListItem]
  • GET /api/v1/ai-conversations/{conversation_id}/messagesPaginatedResponse[MessageResponse]
  • GET /api/v1/ai-conversations/{conversation_id}/mentionable-resourcesMentionableResourceResponse

新增 Schema

MentionableResourceResponse

class MentionableResourceItem(BaseModel):
    resource_id: UUID = Field(..., alias="resourceId")
    resource_type: int = Field(..., alias="resourceType")
    title: str
    thumbnail: Optional[str]

class MentionableResourceResponse(BaseModel):
    resources: List[MentionableResourceItem]

8. 其他模块

Health(2 个接口)

  • GET /api/v1/healthMessageResponse
  • GET /api/v1/health/dbMessageResponse

File Storage(2 个接口)

  • GET /api/v1/file-storage/presigned-urlPresignedUrlResponse
  • POST /api/v1/file-storage/cleanupCleanupResult

新增 Schema (server/app/schemas/file_storage.py)

class PresignedUrlResponse(BaseModel):
    url: str
    expires_in: int = Field(..., alias="expiresIn")

class CleanupResult(BaseModel):
    deleted_count: int = Field(..., alias="deletedCount")
    freed_space: int = Field(..., alias="freedSpace")

Breaking Changes

前端必须修改的字段

1. Authentication

// ❌ 旧代码
response.data.access_token
response.data.user.user_id

// ✅ 新代码
response.data.accessToken
response.data.expiresIn  // ← 新增,需要处理 Token 刷新逻辑
response.data.tokenType  // ← 现在是 "Bearer" 而非 "bearer"
response.data.user.userId

2. FolderMemberResponse

// ❌ 旧代码
member.role === 1  // VIEWER
member.role_name  // "Viewer"

// ✅ 新代码
member.role === "viewer"  // ← 现在是字符串
// role_name 字段已移除,直接使用 role

3. Pagination

// ❌ 旧代码(部分接口返回 dict)
response.data.items
response.data.total

// ✅ 新代码(统一使用 PaginatedResponse)
response.data.items
response.data.total
response.data.page
response.data.pageSize
response.data.totalPages

未修改的接口

Shared Folders(保持 dict)

  • GET /api/v1/public/shared-folders/f/{token}
  • GET /api/v1/public/shared-folders/f/{token}/info
  • POST /api/v1/public/shared-folders/f/{token}/verify-password

原因:公开访问接口,返回结构复杂且动态,强制 Schema 会增加维护成本。

Screenplays(保持 dict)

  • POST /api/v1/screenplays/{screenplay_id}/elements/batch

原因:批量操作返回结构高度动态化。

Folder Share(保持 dict)

  • POST /api/v1/folders/{folder_id}/share

原因:根据 type 字段返回不同结构(用户分享 vs 链接分享),使用联合类型会增加复杂性。


前端迁移指南

Phase 1: 立即修改(必须)

  1. Authentication 模块

    // services/auth.ts
    interface LoginResponse {
      accessToken: string;
      refreshToken: string;
      tokenType: string;  // "Bearer"
      expiresIn: number;  // ← 新增
      user: User;
    }
    
    // 新增 Token 自动刷新逻辑
    const tokenExpiresAt = Date.now() + response.data.expiresIn * 1000;
    
  2. FolderMemberResponse

    // types/folder.ts
    interface FolderMember {
      // ...
      role: 'viewer' | 'editor' | 'owner';  // ✅ 字符串枚举
      // roleNumber 已废弃
    }
    

Phase 2: 验证响应格式(推荐)

// 使用 Zod 或 yup 验证 API 响应
import { z } from 'zod';

const LoginResponseSchema = z.object({
  accessToken: z.string(),
  refreshToken: z.string(),
  tokenType: z.string(),
  expiresIn: z.number(),
  user: UserSchema,
});

// API 调用时验证
const response = await api.post('/auth/login/phone', data);
const validated = LoginResponseSchema.parse(response.data.data);

Phase 3: 更新 Mock 数据(测试)

确保测试数据使用 camelCase 字段名:

// ❌ 错误
const mockUser = {
  user_id: '123',
  created_at: '2024-01-01'
};

// ✅ 正确
const mockUser = {
  userId: '123',
  createdAt: '2024-01-01'
};

技术细节

命名规范强制执行

所有 Schema 使用 alias 确保前端收到 camelCase 字段:

class Example(BaseModel):
    model_config = ConfigDict(populate_by_name=True)
    
    user_id: str = Field(..., alias="userId")
    created_at: datetime = Field(..., alias="createdAt")

Service 层配合

Service 层返回的 dict 必须使用 snake_case 键名(与数据库一致),Pydantic 自动处理序列化:

# ✅ Service 层
return {
    'user_id': str(user.user_id),
    'created_at': user.created_at
}

# FastAPI + Pydantic 自动序列化为:
{
    "userId": "...",
    "createdAt": "..."
}

测试要点

1. 集成测试

# 验证 camelCase 序列化
response = client.post("/api/v1/auth/login/phone", json=data)
assert "accessToken" in response.json()["data"]
assert "expiresIn" in response.json()["data"]

2. Schema 验证

# 确保 Pydantic 验证生效
from app.schemas.user import LoginResponse

response_data = service.login_with_phone(...)
LoginResponse.model_validate(response_data)  # 自动验证字段类型

Rollback 方案

如需回滚,恢复以下文件即可:

git revert <commit-hash>
# 影响文件:
# - server/app/api/v1/*.py
# - server/app/schemas/*.py
# - server/app/services/*.py(仅 return dict 的字段名)

后续计划

Phase 4: 补充剩余接口(可选)

  • Shared Folders 公开访问接口
  • Screenplays 批量操作接口
  • 其他复杂动态结构接口

Phase 5: 自动化验证(推荐)

  • 编写 pre-commit hook 检测新增的 SuccessResponse[dict]
  • CI/CD 集成 Schema 验证测试

相关文档


作者

Claude (via Cursor) - 2026-02-11