API 设计与文档
问题
如何设计一套好的 API?如何管理 API 文档?幂等性是什么?
答案
RESTful API 设计规范
api-design.ts
// ✅ 好的 API 设计
// GET /api/users 获取用户列表
// GET /api/users/:id 获取单个用户
// POST /api/users 创建用户
// PUT /api/users/:id 全量更新用户
// PATCH /api/users/:id 部分更新用户
// DELETE /api/users/:id 删除用户
// ✅ 嵌套资源
// GET /api/users/:id/orders 获取用户的订单列表
// POST /api/users/:id/orders 为用户创建订单
// ❌ 不好的设计
// POST /api/getUser 用 POST 做查询
// GET /api/deleteUser/123 用 GET 做删除
// POST /api/user/update 动词放在 URL 中
统一响应格式
response-format.ts
// 成功响应
interface ApiResponse<T> {
code: number; // 业务状态码
data: T;
message: string;
}
// 列表响应(分页)
interface PaginatedResponse<T> {
code: number;
data: {
items: T[];
total: number;
page: number;
pageSize: number;
};
message: string;
}
// 错误响应
interface ApiError {
code: number;
message: string;
details?: Record<string, string[]>; // 字段级错误
}
// 示例
// 成功: { code: 0, data: { id: 1, name: "John" }, message: "ok" }
// 错误: { code: 40001, message: "用户名已存在", details: { username: ["已被注册"] } }
幂等性
| HTTP 方法 | 幂等 | 安全 | 说明 |
|---|---|---|---|
| GET | ✅ | ✅ | 多次请求结果相同 |
| PUT | ✅ | ❌ | 全量替换,结果一致 |
| DELETE | ✅ | ❌ | 删除一次和多次效果相同 |
| POST | ❌ | ❌ | 每次可能创建新资源 |
| PATCH | ❌ | ❌ | 取决于实现 |
idempotency.ts
// 实现 POST 幂等:幂等 Key
const createOrder = async (req: Request, res: Response) => {
const idempotencyKey = req.headers['idempotency-key'] as string;
if (!idempotencyKey) {
return res.status(400).json({ message: '需要 Idempotency-Key 头' });
}
// 检查是否已处理
const existing = await redis.get(`idempotent:${idempotencyKey}`);
if (existing) {
return res.json(JSON.parse(existing)); // 返回缓存结果
}
// 处理请求
const order = await db.order.create(req.body);
await redis.set(`idempotent:${idempotencyKey}`, JSON.stringify(order), 'EX', 86400);
return res.json(order);
};
OpenAPI / Swagger 文档
swagger-nestjs.ts
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
@ApiTags('用户')
@Controller('users')
export class UserController {
@Get(':id')
@ApiOperation({ summary: '获取用户详情' })
@ApiResponse({ status: 200, description: '成功', type: UserDto })
@ApiResponse({ status: 404, description: '用户不存在' })
async getUser(@Param('id') id: string): Promise<UserDto> {
return this.userService.findById(id);
}
}
常见面试问题
Q1: API 版本管理怎么做?
答案:
三种方式:
- URL 路径:
/api/v1/users(最常用) - 请求头:
Accept: application/vnd.api+json; version=1 - 查询参数:
/api/users?version=1
Q2: RESTful API 的 PUT 和 PATCH 有什么区别?
答案:
PUT:全量替换,需要传完整对象PATCH:部分更新,只传需要修改的字段
Q3: 如何设计分页 API?
答案:
// 偏移量分页(适合页码跳转)
// GET /api/users?page=2&pageSize=20
// 游标分页(适合无限滚动,性能更好)
// GET /api/users?cursor=abc123&limit=20
游标分页不需要 COUNT(*),深分页性能远优于偏移量分页。
Q4: 接口鉴权放在哪一层?
答案:
通常在 API 网关或中间件层统一处理,业务代码不需要关心认证。鉴权(权限检查)可以在中间件或装饰器中实现。
Q5: 如何保证 API 的向后兼容?
答案:
- 只新增字段,不删除或修改已有字段
- 新增必填字段需提供默认值
- 破坏性变更通过新版本 API(v2)发布
- 旧版本设定废弃时间(Deprecation)
相关链接
- RESTful API 设计 - API 设计原则
- GraphQL - GraphQL 方案