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

Folder API 查询参数格式修复

日期: 2026-01-29
类型: Bug 修复
影响范围: 前端 Folder API 服务

问题描述

前端调用 Folder API 时,查询参数使用了 snake_case 格式(如 folder_category),但后端 API 规范要求使用 camelCase 格式(如 folderCategory),导致请求失败。

错误示例

GET /api/v1/folders?folder_category=1  ❌

错误响应

{
  "success": false,
  "code": 400,
  "message": "查询根文件夹时必须指定 folder_category",
  "data": null
}

根本原因

client/src/services/api/folders.ts 中的 API 调用使用了 keysToSnakeCase() 工具函数转换查询参数,将 camelCase 参数名转换为 snake_case,与后端 API 规范不符。

解决方案

修改文件

  • client/src/services/api/folders.ts

具体变更

1. getAll() 方法

修改前

async getAll(params?: {
  parentId?: string | null;
  folderCategory?: FolderCategory;
  page?: number;
  pageSize?: number;
}): Promise<FolderListResponse> {
  const queryParams = keysToSnakeCase(params || {});  // ❌ 转换为 snake_case
  const response = await apiClient.get<FolderListResponse>('/folders', {
    params: queryParams,
  });
  return keysToCamelCase(response);
}

修改后

async getAll(params?: {
  parentId?: string | null;
  folderCategory?: FolderCategory;
  page?: number;
  pageSize?: number;
}): Promise<FolderListResponse> {
  // 查询参数保持 camelCase,符合 API 规范
  const response = await apiClient.get<FolderListResponse>('/folders', {
    params: params,  // ✅ 保持 camelCase
  });
  return keysToCamelCase(response);
}

2. getTree() 方法

修改前

async getTree(params?: {
  maxDepth?: number;
  includeProjects?: boolean;
}): Promise<{ tree: FolderTreeNode[] }> {
  const queryParams: Record<string, any> = {};
  if (params?.maxDepth) queryParams.max_depth = params.maxDepth;  // ❌ snake_case
  if (params?.includeProjects) queryParams.include_projects = params.includeProjects;  // ❌ snake_case
  
  const response = await apiClient.get<{ tree: FolderTreeNode[] }>('/folders/tree', {
    params: Object.keys(queryParams).length > 0 ? queryParams : undefined,
  });
  return keysToCamelCase(response);
}

修改后

async getTree(params?: {
  maxDepth?: number;
  includeProjects?: boolean;
}): Promise<{ tree: FolderTreeNode[] }> {
  // 查询参数保持 camelCase,符合 API 规范
  const response = await apiClient.get<{ tree: FolderTreeNode[] }>('/folders/tree', {
    params: params,  // ✅ 保持 camelCase
  });
  return keysToCamelCase(response);
}

3. getById() 方法

修改前

async getById(id: string, includeBreadcrumbs = true): Promise<Folder> {
  const response = await apiClient.get<Folder>(`/folders/${id}`, {
    params: { include_breadcrumbs: includeBreadcrumbs },  // ❌ snake_case
  });
  return keysToCamelCase(response);
}

修改后

async getById(id: string, includeBreadcrumbs = true): Promise<Folder> {
  const response = await apiClient.get<Folder>(`/folders/${id}`, {
    params: { includeBreadcrumbs },  // ✅ camelCase
  });
  return keysToCamelCase(response);
}

API 规范说明

根据 docs/requirements/api-design-specification.md.claude/skills/jointo-tech-stack/references/api-design.md

查询参数命名规范

参数命名使用 camelCase

  • folderId
  • pageSize
  • sortBy
  • sortOrder
  • folderCategory
  • parentId

正确的 API 调用示例

GET /api/v1/folders?folderCategory=1&parentId=null&page=1&pageSize=20
GET /api/v1/folders/tree?maxDepth=3&includeProjects=true
GET /api/v1/folders/{id}?includeBreadcrumbs=true

影响范围

修复的功能

获取文件夹列表(根文件夹和子文件夹)
获取文件夹树形结构
获取文件夹详情(包含面包屑)

不受影响的功能

  • 创建文件夹(POST 请求体使用 snake_case,符合后端 Pydantic 模型)
  • 更新文件夹(PUT 请求体使用 snake_case,符合后端 Pydantic 模型)
  • 删除文件夹
  • 移动文件夹

测试验证

手动测试

# 1. 获取根文件夹列表(我的项目)
curl -X GET "http://localhost:6160/api/v1/folders?folderCategory=1" \
  -H "Authorization: Bearer <token>"

# 2. 获取文件夹树
curl -X GET "http://localhost:6160/api/v1/folders/tree?maxDepth=3&includeProjects=true" \
  -H "Authorization: Bearer <token>"

# 3. 获取文件夹详情
curl -X GET "http://localhost:6160/api/v1/folders/{id}?includeBreadcrumbs=true" \
  -H "Authorization: Bearer <token>"

预期结果

所有请求应返回 200 OK 和正确的数据,不再出现 400 Bad Request 错误。

相关文档

  • API 设计规范: docs/requirements/api-design-specification.md
  • Jointo Tech Stack Skill: .claude/skills/jointo-tech-stack/references/api-design.md
  • 后端 Folder API: server/app/api/v1/folders.py

经验教训

  1. 查询参数 vs 请求体

    • 查询参数(GET):使用 camelCase,直接传递,不转换
    • 请求体(POST/PUT):使用 snake_case,需要 keysToSnakeCase() 转换
  2. API 规范一致性

    • 前后端必须遵循统一的命名规范
    • 查询参数应与后端 FastAPI 的 alias 参数保持一致
  3. 工具函数使用场景

    • keysToSnakeCase():仅用于 POST/PUT 请求体
    • keysToCamelCase():用于所有响应数据
    • 查询参数:保持原始 camelCase 格式

后续优化建议

  1. api-utils.ts 中添加注释,明确说明各工具函数的使用场景
  2. 考虑创建专门的查询参数类型,确保类型安全
  3. 添加单元测试验证查询参数格式