主题
RESTful API 设计
REST 架构原则
REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中提出。它不是一个标准,而是一组设计约束和原则,用于构建分布式超媒体系统。
REST 核心约束
┌───────────────────────────────────────────────────┐
│ REST 约束 │
├───────────────────┬───────────────────────────────┤
│ 1. 客户端-服务器 │ 分离关注点,提高可移植性 │
│ 2. 无状态 │ 每个请求包含所有必要信息 │
│ 3. 缓存 │ 可缓存响应,提高性能 │
│ 4. 分层系统 │ 中间层处理请求,隐藏复杂性 │
│ 5. 统一接口 │ 资源识别、操作标准化 │
│ 6. 按需编码(可选)│ 服务端可向客户端发送代码 │
└───────────────────┴───────────────────────────────┘资源与表示
- 资源:REST 架构的核心概念,任何可以被标识的实体都可以是资源(用户、文章、订单等)
- 资源标识符:URI(Uniform Resource Identifier),如
/users/123 - 表示:资源的具体表现形式,如 JSON、XML、HTML 等
- 状态转移:通过 HTTP 方法对资源进行操作,实现状态的转换
HTTP 方法语义
| HTTP 方法 | 语义 | 幂等性 | 安全性 | 适用场景 |
|---|---|---|---|---|
| GET | 读取资源 | ✓ | ✓ | 获取用户列表、文章详情 |
| POST | 创建资源 | ✗ | ✗ | 注册用户、创建订单 |
| PUT | 替换资源 | ✓ | ✗ | 更新用户信息、替换文章 |
| PATCH | 修改资源 | ✗ | ✗ | 部分更新用户信息 |
| DELETE | 删除资源 | ✓ | ✗ | 删除用户、删除订单 |
| HEAD | 只获取响应头 | ✓ | ✓ | 检查资源是否存在 |
| OPTIONS | 获取支持的方法 | ✓ | ✓ | CORS 预检请求 |
URL 设计规范
资源命名原则
- 使用复数名词:资源是集合,如
/users而不是/user - 使用 kebab-case:如
/user-profiles而不是/userProfiles或/user_profiles - 使用具体名称:如
/articles而不是/items - 避免动词:资源用名词表示,操作通过 HTTP 方法体现
路径结构
┌───────────────────────────────────────────────────┐
│ URL 路径结构 │
├───────────────────┬───────────────────────────────┤
│ /resources │ 资源集合 │
│ /resources/{id} │ 单个资源 │
│ /resources/{id}/subresources | 嵌套资源 │
└───────────────────┴───────────────────────────────┘路径参数 vs 查询参数
- 路径参数:用于标识资源,如
/users/{id}中的id - 查询参数:用于过滤、排序、分页等,如
/users?page=1&limit=10&sort=created_at:desc
嵌套资源
- 推荐:使用扁平结构,如
/articles和/comments分开 - 可选:使用嵌套结构表示层级关系,如
/articles/{id}/comments
HTTP 状态码
状态码分类
| 分类 | 范围 | 含义 | 示例 |
|---|---|---|---|
| 信息性 | 100-199 | 服务器收到请求,需要继续处理 | 100 Continue |
| 成功 | 200-299 | 请求成功 | 200 OK, 201 Created, 204 No Content |
| 重定向 | 300-399 | 需要进一步操作才能完成请求 | 301 Moved Permanently, 302 Found, 304 Not Modified |
| 客户端错误 | 400-499 | 客户端请求有问题 | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found |
| 服务器错误 | 500-599 | 服务器处理请求时出错 | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
常见状态码使用场景
- 200 OK:请求成功,返回资源
- 201 Created:创建资源成功,返回新资源的 URI
- 204 No Content:请求成功,但无需返回内容(如删除操作)
- 400 Bad Request:请求参数错误
- 401 Unauthorized:未提供认证信息或认证失败
- 403 Forbidden:认证成功,但无权访问
- 404 Not Found:资源不存在
- 405 Method Not Allowed:不支持的 HTTP 方法
- 429 Too Many Requests:请求过于频繁,超出限流
- 500 Internal Server Error:服务器内部错误
请求与响应设计
请求体格式
- 推荐使用 JSON:
Content-Type: application/json - 请求体示例:
json
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}统一响应结构
json
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
}错误响应格式
json
{
"code": 400,
"message": "Invalid request",
"errors": [
{
"field": "email",
"message": "Email is required"
}
]
}内容协商
- Accept:客户端期望的响应格式,如
Accept: application/json - Content-Type:客户端发送的请求体格式,如
Content-Type: application/json
API 版本控制
版本控制策略
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径版本 | /v1/users | 清晰可见,易于缓存 | URL 变更频繁 |
| 请求头版本 | X-API-Version: 1 | URL 保持不变 | 不易于缓存,调试复杂 |
| 查询参数版本 | /users?version=1 | 实现简单 | 易于被忽略,缓存困难 |
版本迁移策略
- 向后兼容:新版本支持旧版本的请求格式
- 废弃通知:在响应头中添加
X-API-Deprecated标识 - 过渡期:旧版本和新版本并行运行一段时间
- 终止支持:明确旧版本的终止日期
认证与授权
认证方式
| 方式 | 描述 | 适用场景 |
|---|---|---|
| API Key | 在请求头或查询参数中传递 API Key | 服务器间通信 |
| Basic Auth | 用户名和密码 Base64 编码 | 内部系统,开发环境 |
| JWT | 无状态令牌,包含用户信息 | 前后端分离,移动应用 |
| OAuth 2.0 | 授权框架,支持第三方登录 | 第三方应用集成 |
授权模型
- RBAC(基于角色的访问控制):用户 → 角色 → 权限
- ABAC(基于属性的访问控制):基于用户、资源、环境等属性的规则
安全最佳实践
传输安全
- 使用 HTTPS:加密传输数据
- HTTP 严格传输安全(HSTS):强制使用 HTTPS
输入验证
- 使用验证库:Joi、Zod、class-validator
- 验证所有输入:路径参数、查询参数、请求体
CORS 配置
javascript
const cors = require('cors');
app.use(cors({
origin: ['https://example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));CSRF 防护
- 使用 CSRF Token:在表单中包含 CSRF Token
- 验证 Origin/Referer:检查请求来源
请求限速
- 使用 Redis 实现:滑动窗口或令牌桶算法
- 设置合理的限速规则:如每分钟 60 次请求
API 文档
OpenAPI/Swagger
- OpenAPI 3.0:API 描述规范
- Swagger UI:交互式 API 文档
文档生成工具
- swagger-jsdoc:从 JSDoc 注释生成文档
- @nestjs/swagger:Nest.js 集成 Swagger
- Postman:手动创建和测试 API 文档
文档示例
yaml
openapi: 3.0.0
info:
title: User API
version: 1.0.0
paths:
/users:
get:
summary: Get all users
responses:
'200':
description: A list of users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
properties:
id:
type: integer
name:
type: string
email:
type: string性能优化
缓存策略
- HTTP 缓存:设置
Cache-Control头 - 服务器缓存:使用 Redis 缓存热点数据
- 客户端缓存:浏览器本地存储
批量操作
- 批量获取:如
/users?ids=1,2,3 - 批量创建:一次请求创建多个资源
字段选择
- fields 参数:如
/users?fields=id,name,email - 减少响应数据:只返回必要的字段
分页优化
- 偏移分页:
/users?page=1&limit=10 - 游标分页:
/users?cursor=abc&limit=10(适用于大数据集)
GraphQL 简介
与 REST 对比
| 特性 | REST | GraphQL |
|---|---|---|
| 请求方式 | 多个端点,多个请求 | 单一端点,一次请求 |
| 响应结构 | 固定结构 | 客户端定义结构 |
| 过度获取 | 常见 | 避免 |
| 不足获取 | 常见 | 避免 |
| 文档 | 需额外维护 | 内建 |
| 缓存 | 简单(HTTP 缓存) | 复杂(需要客户端实现) |
核心概念
- Schema:定义 API 的类型和操作
- Query:获取数据(类似 GET)
- Mutation:修改数据(类似 POST/PUT/DELETE)
- Subscription:实时数据(类似 WebSocket)
示例
graphql
# Schema
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Query {
users: [User!]!
user(id: ID!): User!
posts: [Post!]!
}
# Query
query {
users {
id
name
email
}
}面试高频问题
1. 什么是 REST?REST 有哪些核心约束?
回答思路:
- REST 是一种软件架构风格,不是标准
- 核心约束包括:客户端-服务器、无状态、缓存、分层系统、统一接口、按需编码
- 统一接口是最核心的约束,包括资源识别、操作标准化、自描述消息、超媒体作为应用状态的引擎(HATEOAS)
2. HTTP 方法的幂等性和安全性是什么意思?
回答思路:
- 幂等性:多次相同的请求产生相同的结果,如 GET、PUT、DELETE
- 安全性:请求不会修改服务器状态,如 GET、HEAD、OPTIONS
- POST 和 PATCH 既不是幂等的也不是安全的
3. 如何设计 RESTful API 的 URL?
回答思路:
- 使用复数名词:/users 而不是 /user
- 使用 kebab-case:/user-profiles 而不是 /userProfiles
- 避免动词:用 HTTP 方法表示操作
- 路径参数用于标识资源,查询参数用于过滤、排序、分页
- 合理使用嵌套资源,但不要过深
4. 如何处理 API 版本控制?
回答思路:
- URL 路径版本:/v1/users(最常用)
- 请求头版本:X-API-Version: 1
- 查询参数版本:/users?version=1
- 建议使用 URL 路径版本,清晰可见,易于缓存
- 版本迁移策略:向后兼容、废弃通知、过渡期、终止支持
5. 如何设计统一的响应格式?
回答思路:
- 成功响应:包含 code(状态码)、message(消息)、data(数据)
- 错误响应:包含 code(状态码)、message(错误消息)、errors(详细错误信息)
- 保持响应格式一致,便于客户端处理
6. API 安全有哪些最佳实践?
回答思路:
- 使用 HTTPS 加密传输
- 实现认证和授权(JWT、OAuth 2.0)
- 输入验证(使用 Joi、Zod 等库)
- CORS 配置(限制允许的来源)
- CSRF 防护(使用 CSRF Token)
- 请求限速(防止暴力攻击)
- 敏感数据处理(加密存储)
7. 如何优化 API 性能?
回答思路:
- 缓存策略(HTTP 缓存、服务器缓存、客户端缓存)
- 批量操作(减少请求次数)
- 字段选择(只返回必要字段)
- 分页优化(偏移分页、游标分页)
- 数据库优化(索引、查询优化)
- 异步处理(处理耗时操作)
8. REST 和 GraphQL 有什么区别?
回答思路:
- 请求方式:REST 多个端点,GraphQL 单一端点
- 响应结构:REST 固定结构,GraphQL 客户端定义结构
- 过度获取/不足获取:REST 常见,GraphQL 避免
- 文档:REST 需额外维护,GraphQL 内建
- 缓存:REST 简单(HTTP 缓存),GraphQL 复杂
- 适用场景:REST 适用于简单 API,GraphQL 适用于复杂 API
延伸阅读
官方文档
书籍
- 《RESTful Web APIs》by Leonard Richardson
- 《API Design Patterns》by JJ Geewax
- 《GraphQL in Action》by Samer Buna