Skip to content

RESTful API 设计

Web 与框架⭐⭐ 中级🔥🔥🔥 高频

💡 核心要点

REST(Representational State Transfer)是一种基于 HTTP 协议的 API 设计风格,其核心是以资源为中心,用标准 HTTP 方法(GET/POST/PUT/PATCH/DELETE)表达操作,用 HTTP 状态码表达结果。良好的 RESTful 设计让 API 直观、可预测、易维护。

REST 六大核心原则

Roy Fielding 在其博士论文中定义了 REST 架构风格的六条约束:

原则说明
统一接口(Uniform Interface)资源通过统一的 URI 标识,用标准 HTTP 方法操作
无状态(Stateless)每个请求包含所有必要信息,服务端不保存客户端状态(Session 存客户端或 Token 中)
可缓存(Cacheable)响应标明是否可缓存,GET 响应通常可缓存
客户端-服务器分离(Client-Server)前后端解耦,各自演进
分层系统(Layered System)客户端不知道(也不需要知道)是否通过中间层(负载均衡、网关)
按需代码(Code on Demand,可选)服务端可发送可执行代码给客户端(如 JavaScript)

面试重点: 前三条最重要,尤其是无状态是区分 RESTful 与传统 Session 架构的关键。

HTTP 方法的语义

方法操作语义幂等?安全?请求体
GET获取资源✅ 是✅ 是
POST创建资源 / 执行操作❌ 否❌ 否
PUT全量替换资源✅ 是❌ 否
PATCH部分更新资源❌ 否(可设计为幂等)❌ 否
DELETE删除资源✅ 是❌ 否
HEAD获取响应头(不返回 body)✅ 是✅ 是
OPTIONS查询支持的方法(CORS 预检)✅ 是✅ 是

幂等(Idempotent): 执行多次与执行一次的结果相同。DELETE /users/1 执行两次,用户仍然是被删除状态。

安全(Safe): 不修改服务端状态(只读操作)。

URI 设计规范

好的 URI 设计

# 使用名词(资源),不用动词
GET  /users           # 获取用户列表
GET  /users/123       # 获取单个用户
POST /users           # 创建用户
PUT  /users/123       # 全量更新用户
PATCH /users/123      # 部分更新用户
DELETE /users/123     # 删除用户

# 嵌套资源表达从属关系
GET  /users/123/orders        # 用户 123 的所有订单
GET  /users/123/orders/456    # 用户 123 的订单 456

# 过滤、排序、分页用查询参数
GET /users?role=admin&sort=created_at&order=desc&page=2&limit=20

常见错误设计

# 错误:URI 里出现动词
POST /createUser       ← 错误,POST /users 才是 RESTful
GET  /getUser/123      ← 错误,GET /users/123
POST /deleteUser/123   ← 错误,DELETE /users/123

# 错误:URI 包含操作而非资源
GET /user/123/activate ← 可接受,但更 RESTful 是 PATCH /users/123 { status: "active" }

# 错误:大小写和格式不统一
/UserProfile    ← 用 /user-profiles(小写,连字符分隔)
/user_profile   ← 用 /user-profiles(连字符,而非下划线)

HTTP 状态码

合理使用状态码是 RESTful API 设计质量的体现:

状态码含义使用场景
200 OK请求成功GET、PUT、PATCH 成功返回数据
201 Created创建成功POST 创建资源后,附带 Location 头指向新资源
204 No Content成功,无返回体DELETE 或 PUT 不需要返回内容时
400 Bad Request请求参数错误字段格式错误、缺少必填字段
401 Unauthorized未认证未携带 Token 或 Token 无效
403 Forbidden无权限已认证但无权访问该资源
404 Not Found资源不存在用户/订单 ID 不存在
409 Conflict冲突用户名已存在、版本冲突
422 Unprocessable Entity业务规则校验失败账户余额不足等业务逻辑错误
429 Too Many Requests限流API 调用超过速率限制
500 Internal Server Error服务器内部错误未预期的异常
503 Service Unavailable服务不可用服务器维护或过载

请求与响应示例

创建用户

http
POST /api/v1/users
Content-Type: application/json

{
  "username": "alice",
  "email": "alice@example.com",
  "role": "admin"
}
http
HTTP/1.1 201 Created
Location: /api/v1/users/789
Content-Type: application/json

{
  "id": 789,
  "username": "alice",
  "email": "alice@example.com",
  "role": "admin",
  "createdAt": "2024-01-15T10:30:00Z"
}

错误响应统一格式

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request body contains invalid data",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address"
      }
    ]
  },
  "requestId": "req_abc123",
  "timestamp": "2024-01-15T10:30:00Z"
}

API 版本管理

当 API 需要破坏性变更时,需要进行版本控制:

方式一:URI 路径版本号(最常用)

/api/v1/users
/api/v2/users

方式二:请求头版本号

http
GET /api/users
Accept: application/vnd.myapp.v2+json

方式三:查询参数

GET /api/users?version=2

推荐 URI 版本号:最直观,缓存友好,浏览器/抓包工具直接可见。

PUT vs PATCH 的关键区别

json
// 当前用户数据
{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com",
  "age": 25
}
http
// PUT:全量替换,未提供的字段可能被清空
PUT /users/1
{ "name": "Alice Smith" }

// 结果(email 和 age 被删除!)
{ "id": 1, "name": "Alice Smith" }
http
// PATCH:部分更新,只修改提供的字段
PATCH /users/1
{ "name": "Alice Smith" }

// 结果(email 和 age 保留)
{ "id": 1, "name": "Alice Smith", "email": "alice@example.com", "age": 25 }

RESTful API 认证

Bearer Token(最常用)

http
GET /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

JWT(JSON Web Token) 是 Bearer Token 的常见实现:

Header.Payload.Signature
{alg, typ} . {user_id, role, exp} . HMAC_SHA256(header+payload, secret)

优点:无状态,服务器不存 Session;缺点:Token 下发后无法主动撤销(需配合黑名单或 Redis)。

认证 vs 授权

  • 认证(Authentication): 你是谁?验证身份(用户名+密码 / OAuth)
  • 授权(Authorization): 你能做什么?验证权限(RBAC 角色权限、资源所有权)

常见误区

易错点

  1. GET 请求不应有副作用(不改变服务器状态),满足"安全性";但有些框架允许 GET 带请求体,标准上不推荐
  2. 401 vs 403 的区别: 401 表示"谁都不知道"(未认证),403 表示"知道你是谁,但不允许你做这件事"(无权限)
  3. POST 不幂等:重复提交会创建多个资源。前端需要防重提(按钮 disable、幂等 Token)
  4. PATCH 的幂等性: PATCH 是否幂等取决于实现。绝对值修改({ "name": "Alice" })是幂等的;增量修改({ "balance": "+100" })不是幂等的
📝 面试真题3 道高频
1. RESTful API 的设计原则是什么?什么是无状态?中等
2. PUT 和 PATCH 有什么区别?什么是幂等性?中等
3. HTTP 状态码 401 和 403 有什么区别?简单

Q1: RESTful 设计原则

核心三条(必背):

  1. 以资源为中心:URI 标识资源(名词),用 HTTP 方法(动词)表操作
  2. 无状态:每个请求包含所有必要信息,服务端不维护 Session(适合水平扩展)
  3. 统一接口:使用标准 HTTP 方法,用状态码表达结果

无状态的意义: 任意节点都能处理请求,便于水平扩展(加机器)和故障恢复,不需要 Session 粘性路由。

Q2: PUT vs PATCH

  • PUT:全量替换,请求体必须包含完整的资源描述,缺少的字段会被清空或默认值覆盖
  • PATCH:部分更新,只传需要修改的字段,其余保持不变

幂等性: 执行多次和执行一次结果相同。PUT 是幂等的(每次都是同一个完整替换),PATCH 是否幂等取决于操作语义。

Q3: 401 vs 403

  • 401 Unauthorized(实为 Unauthenticated): 请求未携带身份凭证(Token 缺失/过期),服务器不知道你是谁(需要登录)
  • 403 Forbidden: 服务器知道你是谁,但你没有权限执行该操作(账号权限不足)

记忆技巧:401 是"门卫不认识你(需要先证明身份)",403 是"门卫认识你但不放你进去(你没有通行证)"。

延伸阅读