RESTful 设计原则 #
概述 #
RESTful API 设计遵循一组核心原则,这些原则确保 API 具有一致性、可理解性和可维护性。本章将详细介绍这些设计原则。
text
┌─────────────────────────────────────────────────────────────┐
│ RESTful 设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 资源导向 │ │ 统一接口 │ │ 无状态 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 名词命名 │ │ 层级清晰 │ │ 版本控制 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
原则一:资源导向设计 #
核心思想 #
RESTful API 的核心是资源,而不是动作。所有的设计都围绕资源展开。
text
┌─────────────────────────────────────────────────────────────┐
│ 资源导向设计 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 错误示例(动作导向): │
│ │
│ GET /getUsers │
│ POST /createUser │
│ POST /deleteUser?id=123 │
│ POST /updateUser?id=123 │
│ │
│ 正确示例(资源导向): │
│ │
│ GET /users 获取用户列表 │
│ POST /users 创建用户 │
│ DELETE /users/123 删除用户 │
│ PUT /users/123 更新用户 │
│ │
│ 关键区别: │
│ - URL 只包含名词(资源) │
│ - 动作由 HTTP 方法表达 │
│ - URL 表示资源,方法表示操作 │
│ │
└─────────────────────────────────────────────────────────────┘
资源识别 #
text
┌─────────────────────────────────────────────────────────────┐
│ 资源识别方法 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 识别资源的步骤: │
│ │
│ 1. 分析业务领域 │
│ - 系统中有哪些实体? │
│ - 实体之间的关系是什么? │
│ - 哪些需要暴露为 API? │
│ │
│ 2. 确定资源类型 │
│ - 集合资源:/users │
│ - 单个资源:/users/123 │
│ - 子资源:/users/123/orders │
│ │
│ 3. 定义资源属性 │
│ - 哪些属性需要暴露? │
│ - 哪些属性只读? │
│ - 哪些属性可修改? │
│ │
└─────────────────────────────────────────────────────────────┘
原则二:统一接口 #
HTTP 方法语义 #
text
┌─────────────────────────────────────────────────────────────┐
│ HTTP 方法语义 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方法 操作 幂等性 安全性 说明 │
│ ───────────────────────────────────────────────────────── │
│ GET 查询 ✅ ✅ 获取资源 │
│ POST 创建 ❌ ❌ 新建资源 │
│ PUT 更新 ✅ ❌ 完整更新 │
│ PATCH 更新 ❌ ❌ 部分更新 │
│ DELETE 删除 ✅ ❌ 删除资源 │
│ HEAD 查询头 ✅ ✅ 获取元数据 │
│ OPTIONS 查询选项 ✅ ✅ 获取支持方法 │
│ │
│ 幂等性:多次执行结果相同 │
│ 安全性:不会修改资源状态 │
│ │
└─────────────────────────────────────────────────────────────┘
方法使用示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 方法使用示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ GET /users │
│ ───────────────────────────────────────────── │
│ 功能:获取用户列表 │
│ 请求体:无 │
│ 响应:200 OK + 用户列表 │
│ │
│ GET /users/123 │
│ ───────────────────────────────────────────── │
│ 功能:获取指定用户 │
│ 请求体:无 │
│ 响应:200 OK + 用户信息 或 404 Not Found │
│ │
│ POST /users │
│ ───────────────────────────────────────────── │
│ 功能:创建用户 │
│ 请求体:用户数据 │
│ 响应:201 Created + Location: /users/124 │
│ │
│ PUT /users/123 │
│ ───────────────────────────────────────────── │
│ 功能:完整更新用户 │
│ 请求体:完整用户数据 │
│ 响应:200 OK 或 204 No Content │
│ │
│ PATCH /users/123 │
│ ───────────────────────────────────────────── │
│ 功能:部分更新用户 │
│ 请求体:部分用户数据 │
│ 响应:200 OK 或 204 No Content │
│ │
│ DELETE /users/123 │
│ ───────────────────────────────────────────── │
│ 功能:删除用户 │
│ 请求体:无 │
│ 响应:204 No Content 或 200 OK │
│ │
└─────────────────────────────────────────────────────────────┘
原则三:资源命名规范 #
命名规则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 资源命名规则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 使用名词,不使用动词 │
│ ✅ /users │
│ ❌ /getUsers │
│ │
│ 2. 使用复数形式 │
│ ✅ /users │
│ ❌ /user │
│ │
│ 3. 使用小写字母 │
│ ✅ /user-profiles │
│ ❌ /UserProfiles │
│ │
│ 4. 使用连字符分隔单词 │
│ ✅ /order-items │
│ ❌ /orderItems │
│ ❌ /order_items │
│ │
│ 5. 避免文件扩展名 │
│ ✅ /users/123 │
│ ❌ /users/123.json │
│ │
│ 6. 使用层级表示关系 │
│ ✅ /users/123/orders │
│ ❌ /users/123-orders │
│ │
└─────────────────────────────────────────────────────────────┘
命名示例对比 #
text
┌─────────────────────────────────────────────────────────────┐
│ 命名示例对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景:用户管理 │
│ │
│ ❌ 错误命名: │
│ /getUser │
│ /user │
│ /Users │
│ /user_info │
│ /userInfo │
│ │
│ ✅ 正确命名: │
│ /users 用户集合 │
│ /users/123 单个用户 │
│ /users/123/profile 用户资料 │
│ /users/123/orders 用户订单 │
│ │
│ ───────────────────────────────────────────── │
│ │
│ 场景:订单管理 │
│ │
│ ❌ 错误命名: │
│ /orderList │
│ /create-order │
│ /orderItems │
│ │
│ ✅ 正确命名: │
│ /orders 订单集合 │
│ /orders/456 单个订单 │
│ /orders/456/items 订单项 │
│ /orders/456/items/1 单个订单项 │
│ │
└─────────────────────────────────────────────────────────────┘
原则四:状态码正确使用 #
状态码分类 #
text
┌─────────────────────────────────────────────────────────────┐
│ HTTP 状态码分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1xx - 信息响应 │
│ 请求已接收,继续处理 │
│ │
│ 2xx - 成功响应 │
│ 请求已成功接收、理解、接受 │
│ │
│ 3xx - 重定向 │
│ 需要进一步操作以完成请求 │
│ │
│ 4xx - 客户端错误 │
│ 请求包含语法错误或无法完成 │
│ │
│ 5xx - 服务端错误 │
│ 服务器无法完成有效请求 │
│ │
└─────────────────────────────────────────────────────────────┘
常用状态码 #
text
┌─────────────────────────────────────────────────────────────┐
│ 常用状态码速查 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 成功响应(2xx): │
│ ┌─────────┬─────────────────────────────────────┐ │
│ │ 200 OK │ 请求成功,返回数据 │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 201 │ 创建成功,返回 Location 头 │ │
│ │ Created │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 204 │ 成功但无返回内容(DELETE/PUT) │ │
│ │ No Cont │ │ │
│ └─────────┴─────────────────────────────────────┘ │
│ │
│ 客户端错误(4xx): │
│ ┌─────────┬─────────────────────────────────────┐ │
│ │ 400 Bad │ 请求格式错误或参数无效 │ │
│ │ Request │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 401 │ 未认证,需要登录 │ │
│ │ Unauth │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 403 │ 已认证但无权限 │ │
│ │ Forbid │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 404 Not │ 资源不存在 │ │
│ │ Found │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 409 │ 请求冲突(如重复创建) │ │
│ │ Conflic │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 422 │ 语义错误,无法处理 │ │
│ │ Unproce │ │ │
│ └─────────┴─────────────────────────────────────┘ │
│ │
│ 服务端错误(5xx): │
│ ┌─────────┬─────────────────────────────────────┐ │
│ │ 500 │ 服务器内部错误 │ │
│ │ Internal│ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 502 Bad │ 网关错误 │ │
│ │ Gateway │ │ │
│ ├─────────┼─────────────────────────────────────┤ │
│ │ 503 │ 服务不可用 │ │
│ │ Service │ │ │
│ └─────────┴─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
原则五:无状态设计 #
无状态原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 无状态设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 定义: │
│ 每个请求必须包含服务端处理所需的所有信息 │
│ 服务端不保存客户端会话状态 │
│ │
│ 实现方式: │
│ │
│ 1. 使用 Token 认证 │
│ Authorization: Bearer eyJhbGciOiJIUzI1NiIs... │
│ │
│ 2. 请求包含必要参数 │
│ GET /users?page=2&limit=10 │
│ │
│ 3. 不依赖服务端 Session │
│ ❌ $_SESSION['user_id'] │
│ ✅ JWT Token 解析用户信息 │
│ │
│ 优势: │
│ ✅ 水平扩展简单 │
│ ✅ 服务器重启不影响用户 │
│ ✅ 便于负载均衡 │
│ │
└─────────────────────────────────────────────────────────────┘
有状态 vs 无状态 #
text
┌─────────────────────────────────────────────────────────────┐
│ 有状态 vs 无状态 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 有状态设计: │
│ │
│ 请求1: POST /login │
│ 服务端: 创建 Session,存储用户信息 │
│ │
│ 请求2: GET /profile │
│ 服务端: 查找 Session,获取用户信息 │
│ │
│ 问题: │
│ ❌ 服务器需要存储 Session │
│ ❌ 分布式环境需要 Session 共享 │
│ ❌ 服务器重启 Session 丢失 │
│ │
│ ───────────────────────────────────────────── │
│ │
│ 无状态设计: │
│ │
│ 请求1: POST /login │
│ 服务端: 验证成功,返回 Token │
│ │
│ 请求2: GET /profile │
│ Header: Authorization: Bearer <token> │
│ 服务端: 验证 Token,解析用户信息 │
│ │
│ 优势: │
│ ✅ 服务端无需存储会话 │
│ ✅ 任何服务器都能处理请求 │
│ ✅ 天然支持分布式 │
│ │
└─────────────────────────────────────────────────────────────┘
原则六:内容协商 #
Accept 头使用 #
text
┌─────────────────────────────────────────────────────────────┐
│ 内容协商 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端通过 Accept 头指定响应格式: │
│ │
│ 请求 JSON 格式: │
│ GET /users/123 │
│ Accept: application/json │
│ │
│ 响应: │
│ HTTP/1.1 200 OK │
│ Content-Type: application/json │
│ │
│ { │
│ "id": 123, │
│ "name": "张三" │
│ } │
│ │
│ ───────────────────────────────────────────── │
│ │
│ 请求 XML 格式: │
│ GET /users/123 │
│ Accept: application/xml │
│ │
│ 响应: │
│ HTTP/1.1 200 OK │
│ Content-Type: application/xml │
│ │
│ <?xml version="1.0"?> │
│ <user> │
│ <id>123</id> │
│ <name>张三</name> │
│ </user> │
│ │
└─────────────────────────────────────────────────────────────┘
原则七:HATEOAS #
超媒体驱动 #
text
┌─────────────────────────────────────────────────────────────┐
│ HATEOAS 原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ HATEOAS(Hypermedia as the Engine of Application State) │
│ 超媒体作为应用状态引擎 │
│ │
│ 原则: │
│ 响应中包含相关链接,引导客户端进行下一步操作 │
│ │
│ 示例响应: │
│ { │
│ "id": 123, │
│ "name": "张三", │
│ "email": "zhangsan@example.com", │
│ "_links": { │
│ "self": { │
│ "href": "/users/123" │
│ }, │
│ "orders": { │
│ "href": "/users/123/orders" │
│ }, │
│ "update": { │
│ "href": "/users/123", │
│ "method": "PUT" │
│ }, │
│ "delete": { │
│ "href": "/users/123", │
│ "method": "DELETE" │
│ } │
│ } │
│ } │
│ │
│ 优势: │
│ ✅ 客户端无需硬编码 URL │
│ ✅ API 可动态演进 │
│ ✅ 自描述能力 │
│ │
└─────────────────────────────────────────────────────────────┘
原则八:版本控制 #
版本策略 #
text
┌─────────────────────────────────────────────────────────────┐
│ 版本控制策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. URL 路径版本(推荐) │
│ /v1/users │
│ /v2/users │
│ │
│ 优点:简单直观,易于缓存 │
│ 缺点:URL 变化 │
│ │
│ 2. 查询参数版本 │
│ /users?version=1 │
│ /users?version=2 │
│ │
│ 优点:URL 不变 │
│ 缺点:可选参数容易被忽略 │
│ │
│ 3. Header 版本 │
│ Accept: application/vnd.api.v1+json │
│ Accept: application/vnd.api.v2+json │
│ │
│ 优点:URL 不变,RESTful │
│ 缺点:不够直观 │
│ │
│ 推荐:使用 URL 路径版本,简单明了 │
│ │
└─────────────────────────────────────────────────────────────┘
原则九:过滤、排序、分页 #
查询参数设计 #
text
┌─────────────────────────────────────────────────────────────┐
│ 查询参数设计 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 过滤(Filtering): │
│ GET /users?status=active │
│ GET /users?role=admin │
│ GET /products?category=electronics&price_min=100 │
│ │
│ 排序(Sorting): │
│ GET /users?sort=created_at 升序 │
│ GET /users?sort=-created_at 降序 │
│ GET /users?sort=name,-created_at 多字段排序 │
│ │
│ 分页(Pagination): │
│ GET /users?page=1&limit=20 页码分页 │
│ GET /users?offset=0&limit=20 偏移分页 │
│ GET /users?cursor=abc123&limit=20 游标分页 │
│ │
│ 字段选择(Field Selection): │
│ GET /users?fields=id,name,email │
│ │
│ 搜索(Search): │
│ GET /users?q=zhang │
│ GET /products?search=iPhone │
│ │
└─────────────────────────────────────────────────────────────┘
原则十:安全性 #
安全设计原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 安全设计原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 始终使用 HTTPS │
│ ✅ https://api.example.com/users │
│ ❌ http://api.example.com/users │
│ │
│ 2. 认证与授权 │
│ - 使用 Token 认证(JWT) │
│ - 实现 RBAC 权限控制 │
│ - 验证用户权限 │
│ │
│ 3. 输入验证 │
│ - 验证所有输入参数 │
│ - 防止 SQL 注入 │
│ - 防止 XSS 攻击 │
│ │
│ 4. 速率限制 │
│ X-RateLimit-Limit: 100 │
│ X-RateLimit-Remaining: 95 │
│ X-RateLimit-Reset: 1640995200 │
│ │
│ 5. 敏感数据保护 │
│ - 不返回敏感字段(密码、密钥) │
│ - 日志脱敏 │
│ - 错误信息不暴露实现细节 │
│ │
└─────────────────────────────────────────────────────────────┘
设计原则总结 #
text
┌─────────────────────────────────────────────────────────────┐
│ 设计原则速查表 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 原则 要点 示例 │
│ ───────────────────────────────────────────────────────── │
│ 资源导向 URL 使用名词 /users │
│ 统一接口 HTTP 方法语义化 GET /users │
│ 命名规范 复数、小写、连字符 /order-items │
│ 状态码 正确使用 HTTP 状态码 201 Created │
│ 无状态 不依赖服务端会话 Token 认证 │
│ 内容协商 Accept 头指定格式 application/json│
│ HATEOAS 响应包含相关链接 _links │
│ 版本控制 URL 路径版本 /v1/users │
│ 查询参数 过滤、排序、分页 ?page=1&limit=20│
│ 安全性 HTTPS、认证、验证 Bearer Token │
│ │
└─────────────────────────────────────────────────────────────┘
设计检查清单 #
text
□ URL 设计
□ 使用名词而非动词
□ 使用复数形式
□ 使用小写字母
□ 使用连字符分隔
□ 层级结构清晰
□ HTTP 方法
□ GET 用于查询
□ POST 用于创建
□ PUT 用于完整更新
□ PATCH 用于部分更新
□ DELETE 用于删除
□ 状态码
□ 成功返回 2xx
□ 客户端错误返回 4xx
□ 服务端错误返回 5xx
□ 创建成功返回 201
□ 删除成功返回 204
□ 安全性
□ 使用 HTTPS
□ 实现认证机制
□ 实现权限控制
□ 输入验证
□ 速率限制
□ 其他
□ 版本控制
□ 分页支持
□ 错误处理
□ 文档完善
下一步 #
现在你已经了解了 RESTful API 的设计原则,接下来学习 资源设计,深入了解如何设计合理的资源结构!
最后更新:2026-03-29