Skillsapi-development
A

api-development

FastGPT API 开发规范。重点强调使用 zod schema 定义入参和出参,在 API 文档中声明路由信息,编写对应的 OpenAPI 文档,以及在 API 路由中使用 schema.parse 进行验证。

labring
27k stars
540.5k downloads
Updated 6d ago

Readme

api-development follows the SKILL.md standard. Use the install command to add it to your agent stack.

---
name: api-development
description: FastGPT API 开发规范。重点强调使用 zod schema 定义入参和出参,在 API 文档中声明路由信息,编写对应的 OpenAPI 文档,以及在 API 路由中使用 schema.parse 进行验证。
---

# FastGPT API 开发规范

> FastGPT 项目 API 路由开发的标准化指南,确保 API 的一致性、类型安全和文档完整性。

## 何时使用此技能

- 开发新的 Next.js API 路由
- 修改现有 API 的入参或出参
- 需要 API 类型定义和文档
- 审查 API 相关代码

## 核心原则

### 🔴 必须遵守的规则

1. **所有 API 必须使用 zod schema 定义入参和出参**
2. **必须导出 schema 的 TypeScript 类型**
3. **必须在 schema 文件头部声明 API 信息(路由、方法、描述、标签)**
4. **入参必须使用 schema.parse() 验证**
5. **函数返回值必须使用 schema.parse() 验证**
6. **必须编写完整的 OpenAPI 文档**

## 开发流程

### 步骤 1: 定义 Zod Schema 并声明 API

**文件位置**: `packages/global/openapi/[module]/[api].ts`

**文件头部必须声明 API 信息**:

```typescript
import { z } from 'zod';

/* ============================================================================
 * API: 获取应用对话日志列表
 * Route: POST /api/core/app/logs/list
 * Method: POST
 * Description: 获取指定应用的对话日志列表,支持分页和多种筛选条件
 * Tags: ['App', 'Log', 'Read']
 * ============================================================================ */

// 入参 Schema
export const GetAppChatLogsBodySchema = PaginationSchema.extend({
  appId: z.string().meta({
    example: '68ad85a7463006c963799a05',
    description: '应用 ID'
  }),
  dateStart: z.union([z.string(), z.date()]).meta({
    example: '2024-01-01T00:00:00.000Z',
    description: '开始时间'
  }),
  dateEnd: z.union([z.string(), z.date()]).meta({
    example: '2024-12-31T23:59:59.999Z',
    description: '结束时间'
  }),
  sources: z.array(z.nativeEnum(ChatSourceEnum)).optional().meta({
    example: [ChatSourceEnum.api, ChatSourceEnum.online],
    description: '对话来源筛选'
  })
});

// 导出入参类型
export type getAppChatLogsBody = z.infer<typeof GetAppChatLogsBodySchema>;

// 出参 Schema
export const GetAppChatLogsResponseSchema = z.object({
  total: z.number().meta({ example: 100, description: '总记录数' }),
  list: z.array(ChatLogItemSchema)
});

// 导出出参类型
export type getAppChatLogsResponseType = z.infer<typeof GetAppChatLogsResponseSchema>;
```

**API 声明规范**:

```typescript
/**
 * 每个 API 文件必须在文件头部声明以下信息:
 *
 * 1. API 名称 (API): 简短的功能描述
 * 2. 路由 (Route): 完整的 API 路径
 * 3. 方法 (Method): HTTP 方法 (GET/POST/PUT/DELETE)
 * 4. 描述 (Description): API 的详细功能说明
 * 5. 标签 (Tags): API 的分类标签数组
 *
 * 标签示例:
 * - 'App': 应用相关 API
 * - 'User': 用户相关 API
 * - 'Log': 日志相关 API
 * - 'Read': 只读操作
 * - 'Write': 写入操作
 * - 'Delete': 删除操作
 */
```

**Schema 定义规范**:

#### ✅ 字段定义规范

```typescript
// ✅ 好的实践: 完整的 meta 信息
export const GetUserSchema = z.object({
  userId: z.string().meta({
    example: '68ad85a7463006c963799a05',
    description: '用户 ID'
  }),
  email: z.string().email().meta({
    example: 'user@example.com',
    description: '用户邮箱'
  }),
  age: z.number().int().positive().meta({
    example: 25,
    description: '用户年龄'
  }),
  status: z.enum(['active', 'inactive']).meta({
    example: 'active',
    description: '用户状态'
  })
});

// ❌ 不好的实践: 缺少 meta 信息
export const GetUserSchemaBad = z.object({
  userId: z.string(),
  email: z.string(),
  age: z.number(),
  status: z.string()
});
```

#### ✅ 嵌套对象定义

```typescript
// 嵌套对象应该定义为独立的 Schema
export const AddressSchema = z.object({
  street: z.string().meta({ description: '街道地址' }),
  city: z.string().meta({ description: '城市' }),
  country: z.string().meta({ description: '国家' })
});

export const CreateUserSchema = z.object({
  name: z.string().meta({ description: '用户名' }),
  address: AddressSchema.meta({ description: '地址信息' })
});
```

#### ✅ 数组定义

```typescript
export const GetUserListResponseSchema = z.object({
  total: z.number().meta({ example: 100, description: '总数' }),
  list: z.array(
    z.object({
      id: z.string().meta({ description: '用户 ID' }),
      name: z.string().meta({ description: '用户名' })
    })
  ).meta({ description: '用户列表' })
});
```

#### ✅ 可选字段

```typescript
export const UpdateUserSchema = z.object({
  userId: z.string().meta({ description: '用户 ID' }),
  // 可选字段使用 .optional()
  name: z.string().optional().meta({ description: '用户名' }),
  // 或使用 .nullish() 允许 null 和 undefined
  email: z.string().email().nullish().meta({ description: '用户邮箱' })
});
```

#### ✅ 分页 Schema

```typescript
import { PaginationSchema } from '@fastgpt/global/openapi/api';

// 继承分页 Schema
export const GetUserListSchema = PaginationSchema.extend({
  // 添加额外的筛选字段
  keyword: z.string().optional().meta({ description: '搜索关键词' }),
  status: z.enum(['active', 'inactive']).optional().meta({ description: '状态筛选' })
});
```

#### ✅ 多个 API 的 Schema 文件

```typescript
/* ============================================================================
 * API: 获取日志键
 * Route: GET /api/core/app/logs/keys
 * Method: GET
 * Description: 获取应用的日志配置键列表
 * Tags: ['App', 'Log', 'Read']
 * ============================================================================ */

export const GetLogKeysQuerySchema = z.object({
  appId: z.string().meta({ description: '应用 ID' })
});

export const GetLogKeysResponseSchema = z.object({
  logKeys: z.array(AppLogKeysSchema).meta({ description: '日志键列表' })
});

/* ============================================================================
 * API: 更新日志键
 * Route: POST /api/core/app/logs/keys
 * Method: POST
 * Description: 更新应用的日志配置键
 * Tags: ['App', 'Log', 'Write']
 * ============================================================================ */

export const UpdateLogKeysBodySchema = z.object({
  appId: z.string().meta({ description: '应用 ID' }),
  logKeys: z.array(AppLogKeysSchema).meta({ description: '日志键列表' })
});
```

### 步骤 2: 实现 API 路由

**文件位置**: `projects/app/src/pages/api/[path]/[route].ts`

**标准实现模板**:

```typescript
import type { NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import {
  GetAppChatLogsBodySchema,
  GetAppChatLogsResponseSchema,
  type getAppChatLogsResponseType
} from '@fastgpt/global/openapi/...';

async function handler(
  req: ApiRequestProps,
  _res: NextApiResponse
): Promise<getAppChatLogsResponseType> {
  // 🔴 步骤 1: 使用 schema.parse() 验证入参
  const { appId, dateStart, dateEnd, sources } = GetAppChatLogsBodySchema.parse(req.body);

  // 或对于 query 参数
  // const { param1, param2 } = YourAPIQuerySchema.parse(req.query);

  // 🔴 步骤 2: 业务逻辑处理
  const result = await yourBusinessLogic({ appId, dateStart, dateEnd, sources });

  // 🔴 步骤 3: 使用 schema.parse() 验证出参
  return GetAppChatLogsResponseSchema.parse({
    list: result.list,
    total: result.total
  });
}

export default NextAPI(handler);
```

**完整示例**:

```typescript
import type { NextApiResponse } from 'next';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import {
  GetAppChatLogsBodySchema,
  GetAppChatLogsResponseSchema,
  type getAppChatLogsResponseType
} from '@fastgpt/global/openapi/core/app/log/api';

async function handler(
  req: ApiRequestProps,
  _res: NextApiResponse
): Promise<getAppChatLogsResponseType> {
  // 🔴 1. 验证入参
  const { appId, dateStart, dateEnd, sources } = GetAppChatLogsBodySchema.parse(req.body);

  // 2. 权限验证 (如果需要)
  await authApp({
    req,
    authToken: true,
    appId,
    per: AppReadChatLogPerVal
  });

  // 3. 业务逻辑
  const { list, total } = await getChatLogsFromDB({
    appId,
    dateStart,
    dateEnd,
    sources
  });

  // 🔴 4. 验证出参
  return GetAppChatLogsResponseSchema.parse({
    list,
    total
  });
}

export default NextAPI(handler);
```

### 步骤 3: 权限验证 (如需要)

**使用 `authApp` 或其他权限验证函数**:

```typescript
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { AppWritePerVal } from '@fastgpt/global/support/permission/app/constant';

async function handler(req: ApiRequestProps, res: NextApiResponse) {
  const { appId } = YourAPIBodySchema.parse(req.body);

  // 权限验证
  await authApp({
    req,
    authToken: true,
    appId,
    per: AppWritePerVal  // 权限常量
  });

  // 继续处理...
}
```

### 步骤 4: 错误处理

**使用统一的错误处理**:

```typescript
import { APIError } from '@fastgpt/service/core/error/controller';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';

async function handler(req: ApiRequestProps, res: NextApiResponse) {
  try {
    const { appId } = YourAPIBodySchema.parse(req.body);

    if (!appId) {
      return Promise.reject(CommonErrEnum.missingParams);
    }

    // 业务逻辑...

  } catch (error) {
    // 统一错误处理
    return APIError(error)(req, res);
  }
}
```

## 完整开发示例

### 场景: 创建用户 API

**1. 定义 Schema** (`packages/global/openapi/core/user/api.ts`):

```typescript
import { z } from 'zod';

/* ============================================================================
 * API: 创建用户
 * Route: POST /api/core/user/create
 * Method: POST
 * Description: 创建新用户,返回创建的用户信息
 * Tags: ['User', 'Write']
 * ============================================================================ */

// 入参
export const CreateUserBodySchema = z.object({
  name: z.string().min(2).max(50).meta({
    example: 'Alice',
    description: '用户名 (2-50 字符)'
  }),
  email: z.string().email().meta({
    example: 'alice@example.com',
    description: '用户邮箱'
  }),
  age: z.number().int().positive().optional().meta({
    example: 25,
    description: '用户年龄'
  }),
  avatar: z.string().url().optional().meta({
    example: 'https://example.com/avatar.jpg',
    description: '头像 URL'
  })
});

export type createUserBodyType = z.infer<typeof CreateUserBodySchema>;

// 出参
export const CreateUserResponseSchema = z.object({
  userId: z.string().meta({ example: '68ad85a7463006c963799a05', description: '用户 ID' }),
  name: z.string().meta({ example: 'Alice', description: '用户名' }),
  email: z.string().meta({ example: 'alice@example.com', description: '用户邮箱' }),
  createdAt: z.date().meta({ example: '2024-01-01T00:00:00.000Z', description: '创建时间' })
});

export type createUserResponseType = z.infer<typeof CreateUserResponseSchema>;
```

**2. 实现 API** (`projects/app/src/pages/api/core/user/create.ts`):

```typescript
import type { NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { MongoUser } from '@fastgpt/service/core/user/schema';
import {
  CreateUserBodySchema,
  CreateUserResponseSchema,
  type createUserResponseType
} from '@fastgpt/global/openapi/core/user/api';

async function handler(
  req: ApiRequestProps,
  _res: NextApiResponse
): Promise<createUserResponseType> {
  // 🔴 验证入参
  const { name, email, age, avatar } = CreateUserBodySchema.parse(req.body);

  // 检查邮箱是否已存在
  const existingUser = await MongoUser.findOne({ email });
  if (existingUser) {
    return Promise.reject('Email already exists');
  }

  // 创建用户
  const user = await MongoUser.create({
    name,
    email,
    age,
    avatar,
    createdAt: new Date()
  });

  // 🔴 验证出参
  return CreateUserResponseSchema.parse({
    userId: user._id.toString(),
    name: user.name,
    email: user.email,
    createdAt: user.createdAt
  });
}

export default NextAPI(handler);
```

## 审查检查清单

### 🔴 必须检查项 (阻塞性)

**Schema 文件** (`packages/global/openapi/.../api.ts`):
- [ ] **API 声明**: 文件头部有 API 信息(路由、方法、描述、标签)
- [ ] **Schema 定义**: 入参和出参都使用 zod 定义
- [ ] **类型导出**: 导出 `z.infer<typeof Schema>` 类型
- [ ] **Meta 信息**: 所有字段都有 `description` 和 `example`

**API 路由文件** (`projects/app/src/pages/api/.../route.ts`):
- [ ] **入参验证**: 使用 `Schema.parse(req.body)` 或 `parse(req.query)`
- [ ] **出参验证**: 使用 `Schema.parse(responseData)`
- [ ] **函数返回类型**: 函数返回值声明为导出的类型
- [ ] **权限验证**: API 路由有相应的权限检查 (如需要)

### 🟡 推荐检查项 (建议性)

- [ ] **错误处理**: 使用 `APIError` 统一错误处理
- [ ] **字段验证**: 使用 zod 的验证方法 (.min(), .max(), .email() 等)
- [ ] **可空字段**: 正确使用 `.optional()` 或 `.nullish()`
- [ ] **复用 Schema**: 相同结构抽取为独立 Schema
- [ ] **分页支持**: 列表 API 继承 `PaginationSchema`

### 🟢 可选检查项 (优化性)

- [ ] **字段顺序**: 字段按重要性排序
- [ ] **Schema 复用**: 复用现有 Schema 减少重复
- [ ] **注释**: 复杂逻辑添加注释

## 常见问题和解决方案

### 问题 1: 缺少 API 声明

**错误示例**:
```typescript
// ❌ 错误: 缺少 API 声明
import { z } from 'zod';

export const GetUserSchema = z.object({
  id: z.string()
});
```

**正确做法**:
```typescript
// ✅ 正确: 包含完整的 API 声明
import { z } from 'zod';

/* ============================================================================
 * API: 获取用户信息
 * Route: GET /api/core/user/detail
 * Method: GET
 * Description: 根据 userId 获取用户详细信息
 * Tags: ['User', 'Read']
 * ============================================================================ */

export const GetUserSchema = z.object({
  id: z.string().meta({
    example: '68ad85a7463006c963799a05',
    description: '用户 ID'
  })
});
```

### 问题 2: 类型不匹配

**错误示例**:
```typescript
// ❌ 错误: 函数返回类型未声明
async function handler(req: ApiRequestProps, res: NextApiResponse) {
  const data = YourAPIBodySchema.parse(req.body);
  return { success: true, data };  // 类型未声明
}
```

**正确做法**:
```typescript
// ✅ 正确: 声明返回类型
async function handler(
  req: ApiRequestProps,
  _res: NextApiResponse
): Promise<yourAPIResponseType> {
  const data = YourAPIBodySchema.parse(req.body);

  return YourAPIResponseSchema.parse({
    success: true,
    data
  });
}
```

### 问题 3: 缺少 Meta 信息

**错误示例**:
```typescript
// ❌ 错误: 缺少 meta 信息
export const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string()
});
```

**正确做法**:
```typescript
// ✅ 正确: 完整的 meta 信息
export const UserSchema = z.object({
  id: z.string().meta({
    example: '68ad85a7463006c963799a05',
    description: '用户 ID'
  }),
  name: z.string().meta({
    example: 'Alice',
    description: '用户名'
  }),
  email: z.string().email().meta({
    example: 'alice@example.com',
    description: '用户邮箱'
  })
});
```

### 问题 4: 未验证出参

**错误示例**:
```typescript
// ❌ 错误: 直接返回数据
async function handler(req: ApiRequestProps, res: NextApiResponse) {
  const { appId } = YourAPIBodySchema.parse(req.body);
  const result = await getData(appId);

  return result;  // 未验证出参结构
}
```

**正确做法**:
```typescript
// ✅ 正确: 验证出参
async function handler(req: ApiRequestProps, res: NextApiResponse) {
  const { appId } = YourAPIBodySchema.parse(req.body);
  const result = await getData(appId);

  return YourAPIResponseSchema.parse(result);
}
```

### 问题 5: Schema 复用不当

**不好做法**:
```typescript
// ❌ 重复定义相同的结构
export const Schema1 = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string()
});

export const Schema2 = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string()
});
```

**正确做法**:
```typescript
// ✅ 抽取公共 Schema
export const BaseUserSchema = z.object({
  id: z.string().meta({ description: 'ID' }),
  name: z.string().meta({ description: '名称' }),
  email: z.string().email().meta({ description: '邮箱' })
});

export const Schema1 = z.object({
  user: BaseUserSchema
});

export const Schema2 = z.object({
  users: z.array(BaseUserSchema)
});
```

## 快速参考

### API 声明模板

```typescript
/* ============================================================================
 * API: [简短功能描述]
 * Route: [HTTP 方法] [完整路由路径]
 * Method: [GET/POST/PUT/DELETE]
 * Description: [详细功能说明]
 * Tags: [['模块', '子模块', '操作类型']]
 * ============================================================================ */
```

### 常用标签

- **模块标签**: `App`, `User`, `Chat`, `Workflow`, `Dataset`
- **操作类型**: `Read`, `Write`, `Delete`, `Update`
- **其他**: `Admin`, `Public`, `Internal`

### 常用 Zod 验证方法

```typescript
// 字符串
z.string()                    // 字符串
  .min(2)                     // 最小长度
  .max(50)                    // 最大长度
  .email()                    // 邮箱格式
  .url()                      // URL 格式
  .uuid()                     // UUID 格式

// 数字
z.number()                    // 数字
  .int()                      // 整数
  .positive()                 // 正数
  .min(0)                     // 最小值
  .max(100)                   // 最大值

// 布尔
z.boolean()                   // 布尔值

// 日期
z.date()                      // 日期对象
  .or(z.string())             // 或日期字符串

// 枚举
z.enum(['active', 'inactive'])  // 枚举值
z.nativeEnum(MyEnum)           // TypeScript 枚举

// 数组
z.array(z.string())           // 字符串数组
  .min(1)                     // 最小长度
  .max(10)                    // 最大长度

// 可选
z.string().optional()         // 可选 (undefined)
z.string().nullish()          // 可空 (undefined | null)

// 对象
z.object({                    // 对象
  name: z.string(),
  age: z.number()
})

// 继承
PaginationSchema.extend({     // 扩展
  keyword: z.string()
})

// 联合类型
z.union([z.string(), z.number()])  // 字符串或数字
z.discriminator('type', {          // 判别联合
  type1: Type1Schema,
  type2: Type2Schema
})
```

### Meta 字段说明

```typescript
z.string().meta({
  example: 'value',              // 示例值 (必填)
  description: '字段说明'         // 字段描述 (必填)
})
```

### TypeScript 类型导出

```typescript
// Schema 定义
export const UserSchema = z.object({
  id: z.string(),
  name: z.string()
});

// 导出类型 (命名规范: camelCase)
export type userType = z.infer<typeof UserSchema>;

// 或使用 PascalCase
export type UserType = z.infer<typeof UserSchema>;
```

## 参考资源

### 项目内示例

- **API Schema 示例**: `/Volumes/code/fastgpt-pro/FastGPT/packages/global/openapi/core/app/log/api.ts`
- **API 实现示例**: `/Volumes/code/fastgpt-pro/FastGPT/projects/app/src/pages/api/core/app/logs/list.ts`
- **分页 Schema**: `packages/global/openapi/api.ts`

### 相关文档

- **Zod 官方文档**: https://zod.dev/
- **FastGPT API 规范**: `.claude/skills/pr-review/fastgpt-style-guide.md`
- **PR Review 审查维度**: `.claude/skills/pr-review/code-quality-standards.md`

---

**Version**: 1.0
**Last Updated**: 2026-01-27
**Maintainer**: FastGPT Development Team

Install

Requires askill CLI v1.0+

Metadata

LicenseUnknown
Version-
Updated6d ago
Publisherlabring

Tags

apigithub-actionsllmsecurity