# 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) ```python 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 更新 ```python 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/tree` → `FolderTreeResponse` - `POST /api/v1/folders/batch/move` → `FolderBatchOperationResponse` - `POST /api/v1/folders/batch/delete` → `FolderBatchOperationResponse` - `DELETE /api/v1/folders/{folder_id}` → `MessageResponse` - `POST /api/v1/folders/{folder_id}/members` → `FolderMemberResponse` - `GET /api/v1/folders/{folder_id}/members` → `PaginatedResponse[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}/clone` → `FolderCloneResponse` - `GET /api/v1/folders/{folder_id}/stats` → `FolderStatsResponse` - `POST /api/v1/folders/{folder_id}/export` → `FolderExportResponse` - `GET /api/v1/folders/export/{job_id}` → `FolderExportResponse` - `POST /api/v1/folders/{folder_id}/share` → ⚠️ **保持 dict**(联合类型:用户分享 vs 链接分享) #### Schema 修复 **FolderMemberResponse** ```python class FolderMemberResponse(BaseModel): # ... role: str # ✅ 从 int 改为 str(匹配实际返回的 "viewer"/"editor"/"owner") ``` ### 4. Projects 模块(9 个接口) #### 修复接口 - `GET /api/v1/projects/trash` → `ProjectTrashListResponse` - `DELETE /api/v1/projects/{project_id}` → `ProjectDeleteResponse` - `POST /api/v1/projects/{project_id}/restore` → `ProjectRestoreResponse` - `DELETE /api/v1/projects/{project_id}/permanent` → `ProjectDeleteResponse` - `POST /api/v1/projects/{project_id}/shares` → `ProjectShareResponse` - `DELETE /api/v1/projects/{project_id}/shares/{share_id}` → `MessageResponse` - `GET /api/v1/projects/{project_id}/members` → `ProjectMemberListResponse` - `POST /api/v1/projects/{project_id}/members` → `ProjectMemberResponse` - `DELETE /api/v1/projects/{project_id}/members/{user_id}` → `MessageResponse` #### Schema 增强 **ProjectTrashResponse** ```python class ProjectTrashResponse(BaseModel): # ... parent_project_id: Optional[UUID] = Field(None, alias="parentProjectId") # ← 新增 ``` ### 5. AI 模块(5 个接口) #### 修复接口 - `GET /api/v1/ai/voices` → `VoiceListResponse` - `GET /api/v1/ai/jobs` → `PaginatedResponse[AIJobListItemResponse]` - `POST /api/v1/ai/jobs/{job_id}/cancel` → `MessageResponse` - `GET /api/v1/ai/statistics` → `AIJobStatisticsResponse` - `GET /api/v1/ai/queue/status` → `QueueStatusResponse` #### 新增 Schema **VoiceListResponse** ```python 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** ```python class QueueStatusResponse(BaseModel): workers: WorkerInfo tasks: TaskInfo jobs: JobQueueInfo ``` ### 6. AI Prompts 模块(9 个接口) #### 修复接口 - `POST /api/v1/admin/ai-prompts` → `AIPromptSystemResponse` - `GET /api/v1/admin/ai-prompts` → `AIPromptSystemListResponse` - `GET /api/v1/admin/ai-prompts/versions` → `List[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-default` → `AIPromptSystemResponse` - `POST /api/v1/admin/ai-prompts/{prompt_id}/versions` → `AIPromptSystemResponse` **说明**:复用已有 Schema,无需创建新 Schema ### 7. AI Conversations 模块(3 个接口) #### 修复接口 - `GET /api/v1/ai-conversations` → `PaginatedResponse[ConversationListItem]` - `GET /api/v1/ai-conversations/{conversation_id}/messages` → `PaginatedResponse[MessageResponse]` - `GET /api/v1/ai-conversations/{conversation_id}/mentionable-resources` → `MentionableResourceResponse` #### 新增 Schema **MentionableResourceResponse** ```python 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/health` → `MessageResponse` - `GET /api/v1/health/db` → `MessageResponse` #### File Storage(2 个接口) - `GET /api/v1/file-storage/presigned-url` → `PresignedUrlResponse` - `POST /api/v1/file-storage/cleanup` → `CleanupResult` **新增 Schema** (server/app/schemas/file_storage.py) ```python 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 ```typescript // ❌ 旧代码 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 ```typescript // ❌ 旧代码 member.role === 1 // VIEWER member.role_name // "Viewer" // ✅ 新代码 member.role === "viewer" // ← 现在是字符串 // role_name 字段已移除,直接使用 role ``` #### 3. Pagination ```typescript // ❌ 旧代码(部分接口返回 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 模块** ```typescript // 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** ```typescript // types/folder.ts interface FolderMember { // ... role: 'viewer' | 'editor' | 'owner'; // ✅ 字符串枚举 // roleNumber 已废弃 } ``` ### Phase 2: 验证响应格式(推荐) ```typescript // 使用 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` 字段名: ```typescript // ❌ 错误 const mockUser = { user_id: '123', created_at: '2024-01-01' }; // ✅ 正确 const mockUser = { userId: '123', createdAt: '2024-01-01' }; ``` --- ## 技术细节 ### 命名规范强制执行 所有 Schema 使用 `alias` 确保前端收到 `camelCase` 字段: ```python 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 自动处理序列化: ```python # ✅ Service 层 return { 'user_id': str(user.user_id), 'created_at': user.created_at } # FastAPI + Pydantic 自动序列化为: { "userId": "...", "createdAt": "..." } ``` --- ## 测试要点 ### 1. 集成测试 ```python # 验证 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 验证 ```python # 确保 Pydantic 验证生效 from app.schemas.user import LoginResponse response_data = service.login_with_phone(...) LoginResponse.model_validate(response_data) # 自动验证字段类型 ``` --- ## Rollback 方案 如需回滚,恢复以下文件即可: ```bash git revert # 影响文件: # - 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 验证测试 --- ## 相关文档 - [API 设计规范](../../.claude/skills/jointo-tech-stack/references/api-design.md) - [后端开发规范](../../.claude/skills/jointo-tech-stack/references/backend.md) - [数据库规范](../../.claude/skills/jointo-tech-stack/references/database.md) --- ## 作者 Claude (via Cursor) - 2026-02-11