# 修复文件夹 API Schema 驼峰命名支持 **日期**: 2026-01-29 **类型**: Bug 修复 **影响范围**: 文件夹 API、Schema 定义 ## 问题描述 文件夹 API 测试失败,返回 400 错误: ``` POST /api/v1/folders completed with status 400 ``` ### 根本原因 1. **Schema 字段命名不一致**: - 前端/测试使用驼峰命名:`folderCategory`, `parentFolderId`, `coverImageId` - 后端 Schema 定义使用蛇形命名:`folder_category`, `parent_folder_id`, `cover_image_id` - Pydantic 没有配置 `alias` 和 `populate_by_name`,导致无法识别驼峰命名 2. **API 响应序列化错误**: - UUID 对象未转换为字符串 - 缺少 `folderCategoryName` 字段 ## 解决方案 ### 1. 修复 Schema 定义 为所有文件夹相关 Schema 添加 `alias` 和 `populate_by_name` 配置: #### FolderCreate ```python class FolderCreate(BaseModel): """创建文件夹请求""" name: str = Field(..., min_length=1, max_length=255, description="文件夹名称") description: Optional[str] = Field(None, description="文件夹描述") parent_folder_id: Optional[UUID] = Field(None, alias="parentFolderId", description="父文件夹ID") folder_category: Optional[int] = Field( None, alias="folderCategory", ge=1, le=2, description="文件夹分类: 1=我的项目, 2=协作项目" ) color: Optional[str] = Field( None, pattern=r"^#[0-9A-Fa-f]{6}$", description="文件夹颜色(十六进制)" ) icon: Optional[str] = Field(None, max_length=50, description="文件夹图标") cover_image_id: Optional[UUID] = Field(None, alias="coverImageId", description="封面图片ID") model_config = { "populate_by_name": True # 支持驼峰和蛇形命名 } ``` #### FolderUpdate ```python class FolderUpdate(BaseModel): """更新文件夹请求""" name: Optional[str] = Field(None, min_length=1, max_length=255) description: Optional[str] = None color: Optional[str] = Field(None, pattern=r"^#[0-9A-Fa-f]{6}$") icon: Optional[str] = Field(None, max_length=50) cover_image_id: Optional[UUID] = Field(None, alias="coverImageId") sort_order: Optional[int] = Field(None, alias="sortOrder") model_config = { "populate_by_name": True } ``` #### FolderMove ```python class FolderMove(BaseModel): """移动文件夹请求""" parent_folder_id: Optional[UUID] = Field(None, alias="parentFolderId", description="目标父文件夹ID") model_config = { "populate_by_name": True } ``` ### 2. 修复 API 响应 修改 `POST /api/v1/folders` 端点,确保: - UUID 转换为字符串 - 添加 `folderCategoryName` 字段 ```python @router.post("", response_model=ApiResponse[FolderResponse], summary="创建文件夹") async def create_folder( folder_data: FolderCreate, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session) ): service = FolderService(session) folder = await service.create_folder(current_user.user_id, folder_data) return success_response(data={ "id": str(folder.id), # UUID → 字符串 "name": folder.name, "description": folder.description, "parentFolderId": str(folder.parent_folder_id) if folder.parent_folder_id else None, "ownerId": str(folder.owner_id), # UUID → 字符串 "path": folder.path, "level": folder.level, "folderCategory": folder.folder_category, "folderCategoryName": FolderCategory.get_display_name(folder.folder_category), # 新增 "sortOrder": folder.sort_order, "color": folder.color, "icon": folder.icon, "isShared": folder.is_shared, "createdAt": folder.created_at, "updatedAt": folder.updated_at, }) ``` ### 3. 添加导入 ```python from app.models.folder import FolderCategory ``` ## 修改文件 ### 修改文件 - `server/app/schemas/folder.py` - 为 `FolderCreate`, `FolderUpdate`, `FolderMove` 添加 `alias` 和 `model_config` - `server/app/api/v1/folders.py` - 修复 `create_folder` 响应序列化 - 添加 `FolderCategory` 导入 ## 验证结果 ### 测试结果 ```bash $ docker exec jointo-server-app pytest tests/integration/test_folder_api.py::TestFolderCRUD::test_create_root_folder -v PASSED ✅ ``` ### API 请求示例 **请求**(驼峰命名): ```json POST /api/v1/folders { "name": "我的项目", "description": "根文件夹", "folderCategory": 1 } ``` **响应**: ```json { "code": 200, "message": "Success", "data": { "id": "019c07ec-72a7-7152-a888-cfe3159ce317", "name": "我的项目", "description": "根文件夹", "parentFolderId": null, "ownerId": "019c07eb-70d8-7191-be3e-ba0e6099ef70", "path": "/我的项目", "level": 0, "folderCategory": 1, "folderCategoryName": "我的项目", "sortOrder": 0, "color": null, "icon": null, "isShared": false, "createdAt": "2026-01-29T04:04:22.350207+00:00", "updatedAt": "2026-01-29T04:04:22.350214+00:00" } } ``` ## 影响 ### 正面影响 - ✅ 支持前端使用驼峰命名调用 API - ✅ 保持后端代码使用 Python 蛇形命名规范 - ✅ 提高 API 的易用性和一致性 ### API 兼容性 - ✅ 同时支持驼峰和蛇形命名(`populate_by_name=True`) - ✅ 向后兼容,不影响现有代码 ## 最佳实践 ### Pydantic Schema 命名规范 1. **字段定义使用蛇形命名**(Python 规范): ```python folder_category: Optional[int] parent_folder_id: Optional[UUID] ``` 2. **添加驼峰命名 alias**(前端友好): ```python folder_category: Optional[int] = Field(None, alias="folderCategory") parent_folder_id: Optional[UUID] = Field(None, alias="parentFolderId") ``` 3. **启用双向命名支持**: ```python model_config = { "populate_by_name": True # 同时接受 alias 和原始字段名 } ``` ### API 响应规范 1. **UUID 必须转换为字符串**: ```python "id": str(folder.id) ``` 2. **枚举值提供显示名称**: ```python "folderCategory": folder.folder_category, "folderCategoryName": FolderCategory.get_display_name(folder.folder_category) ``` ## 相关文档 - [文件夹服务实现](./2026-01-29-folder-service-complete-implementation.md) - [文件夹迁移修复](./2026-01-29-folder-migration-duplicate-fix.md) - [API 设计规范](../../requirements/api-design-specification.md) ## 后续工作 - [ ] 为其他文件夹 API 端点添加相同的响应格式 - [ ] 补充缺失的测试 fixture(`test_folder_id`, `test_other_user_id` 等) - [ ] 实现完整的文件夹 CRUD、移动、共享、导出功能 - [ ] 运行完整的集成测试套件