结构:
- 引言:什么是RESTful API
- REST架构风格的核心原则
- RESTful API的资源设计
- 状态码
- 请求和响应格式
- 实战案例
引言:什么是RESTful API?
在现代 Web 和移动应用的开发中,前后端分离已成为主流开发模式。在这种架构中,前端负责 UI 展示,后端则通过 API 向前端提供数据接口。而提到 API,RESTful API 几乎是最常被提及的关键词之一。
REST,全称 Representational State Transfer,是一种架构风格,而不是具体的协议和技术。它由计算机科学家 Roy Fielding 在他的博士论文中提出,旨在设计更清晰、可扩展、结构化的 Web 服务。一个遵循 REST 原则的 API,通常被称为 “RESTful API”。
那么问题来了:
- RESTful API到底"长什么样"?
- 它为什么会成为主流?
- 在日常开发中,我们该如何设计一个既优雅又实用的RESTful API?
这篇文章将带你从基础概念出发,一步步了解REST的设计理念、资源路由规范、HTTP方法的语义、状态码的使用方式,以及常见的设计技巧和最佳实践。
REST架构风格的核心原则
我们知道REST并不是一种具体的技术和协议,而是一种"架构风格",它使得API更加规范、易用、可维护。下面是 REST 的六大核心原则。
无状态(Stateless) REST 要求每一个请求都必须是自包含的:服务器不会在不同请求之间存储任何客户端的状态信息。换句话说,客户端的每个请求都必须包含完成该请求所需的全部信息(显示传入)。
举例说明:登录后Client发送请求应在Header中附带token,而不是依赖服务器的Session。
那么为什么要这样做呢?
- Scalability:无状态设计让任何一个服务器节点都可以处理任何一个请求,因为请求中自带了所有信息。这为负载均衡和自动扩容提供了天然支持。 举例:如果一个系统部署了三个后端服务节点(A/B/C),在无状态的设计下,负载均衡器可以将任意请求分发到任一节点,完全不需要考虑"这个用户上次是在哪台服务器登录的"
- Easy Deployment:由于请求之间没有状态依赖,服务节点可以随时上下线,不会影响用户体验,适合微服务架构或容器化部署(如 Docker/Kubernetes)。
- 故障恢复更简单:当服务器崩溃时,不需要恢复任何"用户会话"或"中间状态",只需要重新启动实例即可继续处理请求。客户端只需要重新发起请求,不必担心状态丢失。
- 更高的可测试性和可维护性:测试 RESTful 接口时,每个请求都是独立的,可以单独构造和验证,不需要手动维护前置状态,降低了集成测试和自动化测试的复杂度。
统一接口(Uniform Interface) 统一接口是REST最核心的思想之一,它确保了客户端和服务器之间的通信的一致性和可预期性,从而降低系统复杂度。换句话说,不管你访问的是"用户信息"、“文章评论"还是"订单数据”,它们的请求风格应该看起来是类似的。 其中包括:
- 使用标准的 HTTP 方法(GET、POST、PUT、DELETE)
- 使用资源的 URI 来表示数据(如 /users/123)
- 资源应通过URI来唯一标识,而不是通过动词。
- ✅例子:
- 获取所有用户:GET /users
- 获取某个用户:GET /users/123
- 获取用户的订单:GET /users/123/orders
- ❌例子:
- /getUserById?id=123
- /doCreateUser
- 这里需要注意URI中含有 “?”, 不一定代表不符合REST,需要区分:
- 查询参数用于过滤、分页 - ✅ 合理
- 查询参数用于标识资源 - ❌ 不推荐
- 使用标准的响应格式(如 JSON)
- 使用统一的状态码表示处理结果(如 200 OK, 404 Not Found)
可缓存(Cacheable) 在 REST 架构中,服务器响应应该显式地标明是否可以被缓存、缓存多久、缓存的条件等。 💡 REST 的可缓存性意味着客户端或中间代理(如浏览器、CDN、反向代理)可以在某些条件下缓存响应结果,从而减少重复请求,提升性能。 可缓存的优势: 1. 减少服务器负载:当客户端或中间层缓存了响应数据,后续相同请求可以直接返回缓存内容,无需再次访问数据库或计算逻辑。 2. 提高响应速度:缓存一般在本地或边缘节点,响应时间远小于重新生成内容。 3. 带宽优化:对于高频访问的数据(如文章列表、商品页等),缓存可以大幅减少网络传输和 I/O 开销。 REST架构基于HTTP,因此天然可以使用HTTP的缓存机制:
- Cache-Control: public, max-age=3600
- ETag & If-None-Match 实现条件缓存
- Last-Modified 和 If-Modified-Since
客户端-服务端分离(Client-Server)
- 客户端:展示 UI、获取用户输入、发送请求;
- 服务端:处理请求、执行业务逻辑、访问数据库、返回数据;
- 前端通过HTTP请求与后端交互;
- 后端根据请求返回JSON格式数据;
- 前端将数据渲染成页面展示;
分层系统(Layered System) 这一点相较于其他点,有些抽象,它旨在强调系统通过层与层之间的清晰分工与接口隔离,实现解耦、灵活扩展和安全性提升。在实际代码中,体现这一原则主要通过合理的项目结构设计和层间职责划分完成。
层次划分为:
- 客户端层(Client)
- 负责发起请求、接收响应、渲染界面
- 不关心服务端内部实现
- 接口层(API 层)
- 对外暴露统一接口(RESTful API),接收请求
- 处理请求路由、参数校验、响应格式化
- 业务逻辑层(Service层)
- 负责核心业务处理,比如订单管理、用户权限控制。
- 只专注业务流程,不直接操作数据库
- 数据访问层(DAO层)
- 负责与数据库交互,执行 CRUD 操作
- 负责持久化细节封装
- 中间件层(Middleware)
- 处理跨层的功能,如身份验证、限流、日志、缓存
- 对请求或响应进行拦截和处理
- 缓存层、代理层(如CDN、负载均衡)
- 运行在服务器与客户端之间,提升性能和安全
- 客户端层(Client)
按需代码(Code on Demand) 它是REST架构风格中唯一的可选约束,它允许服务器向客户端传输可执行代码来扩展客户端的功能。 核心概念:服务器可以通过发送可执行代码(通常是脚本)来临时增强客户端的处理能力,而不是仅仅发送静态数据。客户端接收到这些代码后可以在本地执行,从而获得新的功能或改变现有行为。 例如:
{
"data": {...},
"script": "function processData(data) { return data.map(item => item.value * 2); }"
}
RESTful API的资源设计
在 REST 架构中,“资源(Resource)“是核心概念。REST 并不是围绕"操作"来设计接口的,而是围绕"资源"的识别、操作、表示和状态转移来组织 API。这种设计方式清晰、直观,也便于扩展和维护
如何设计资源URI?
- 使用名词,复数形式
- GET / users – 所有用户
- GET / users / 123 – 某个用户
- 使用嵌套结构表示资源关系(子资源)
- GET /users/123/posts – 某用户的文章
- GET /posts/456/comments – 某文章的评论
- 不用动词
- GET / getUserById?id=123 ❌
- POST / createNewUser ❌
资源的表示
REST 规定了服务器通过 HTTP 响应返回资源的"表现形式”(如 JSON、XML)
例如:
GET /users/123
响应:
{
"id": 123,
"name": "Jacky",
"email": "jacky@example.com"
}
可以通过 Accept 头来协商使用 JSON 或 XML: Accept: application/json
可过滤、排序、分页的资源设计(Query 参数)
虽然REST URI本身只描述资源,但查询和控制参数可以通过 ?添加,这完全符合REST设计规范。
例如: GET /posts?author=jacky&limit=10&page=2&sort=-createdAt
这表示查询 Jacky 的文章,每页 10 篇,第 2 页,按创建时间倒序排列。
资源状态的变更通过请求体完成
例如:
PATCH /users/123
{
"email": "new@example.com"
}
统一资源路径命名规范
- 使用小写字母
- 资源名使用复数(/users,而不是 /user)
- 使用连字符 - 连接多个词 (/user-profiles)
- 避免使用下划线或驼峰
状态码
状态码用于表示服务器对请求的处理结果。REST API 应该使用语义明确的状态码,帮助客户端理解结果。
| 分类 | 范围 | 说明 |
|---|---|---|
| 成功 | 2xx | 请求成功 |
| 重定向 | 3xx | 客户端需采取额外操作 |
| 客户端错误 | 4xx | 客户端请求错误 |
| 服务器错误 | 5xx | 服务器处理失败 |
常见状态码详解
| 状态码 | 含义 | 用途说明 |
|---|---|---|
| 200 | OK | 请求成功(一般是 GET、PUT) |
| 201 | Created | 创建成功(用于 POST 创建资源) |
| 204 | No Content | 成功但无内容(如 DELETE 成功) |
| 400 | Bad Request | 请求无效,如缺参数、格式错误等 |
| 401 | Unauthorized | 未授权,缺少或无效的认证信息 |
| 403 | Forbidden | 有权限机制,但被禁止访问 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 资源冲突,如重复创建 |
| 422 | Unprocessable Entity | 请求格式正确,但语义错误 |
| 500 | Internal Server Error | 服务器内部错误 |
请求与响应格式
对于REST风格的APIs来说,我们需要遵循特定的request和response格式,这样也便于我们日常的开发(更便于实现前后端交互)。
- 请求格式(Request)
- 通常使用application / json格式
- 必须设置正确的头部:
Content-Type: application/json - 请求体(POST创建用户)
POST /users
Content-Type: application/json
{
"name": "Jacky",
"email": "jacky@example.com"
}
- 响应格式(Response)
- 响应体通常是 application / json
- 建议包括:
- 状态信息(code/message)
- data
- error info(可选)
{
"code": 200,
"message": "Success",
"data": {
"id": 123,
"name": "Jacky"
}
}
实战案例:简易博客系统
我们要为博客系统设计一组 API,满足以下需求:
- 用户注册、登录、查看信息
- 创建、查看、更新、删除文章
- 给文章添加评论
- 支持分页、排序和筛选
- 基于 Token 的用户鉴权
资源建模
| 实体 | 路径 | 描述 |
|---|---|---|
| 用户 users | /users | 管理用户账号 |
| 登录 login | /auth/login | 用户登录,获取 JWT Token |
| 文章 posts | /posts | 博客文章资源 |
| 评论 comments | /posts/:id/comments | 某篇文章的评论 |
API路由设计
- 👤用户相关API
| 方法 | 路径 | 描述 | 状态码 |
|---|---|---|---|
| POST | /users | 注册用户 | 201 |
| GET | /users/:id | 获取用户资料 | 200 |
| POST | /auth/login | 用户登录 | 200/401 |
- 📝 文章相关接口
支持分页、筛选
GET /posts?page=2&limit=10&author=jacky&sort=-createdAt
| 方法 | 路径 | 描述 | 状态码 |
|---|---|---|---|
| GET | /posts | 获取文章列表(分页) | 200 |
| GET | /posts/:id | 获取文章详情 | 200/404 |
| POST | /posts | 创建文章 | 201 |
| PATCH | /posts/:id | 更新文章部分字段 | 200 |
| DELETE | /posts/:id | 删除文章 | 204/404 |
- 💬 评论相关接口(子资源)
| 方法 | 路径 | 描述 | 状态码 |
|---|---|---|---|
| POST | /posts/:id/comments | 给文章添加评论 | 201 |
| GET | /posts/:id/comments | 获取文章评论列表 | 200 |
- 鉴权机制(JWT)
所有需要登录到请求必须携带Token:
Authorization: Bearer <jwt_token>Server校验Token,并将用户信息附加到请求上下文(req.user)
前段时间做了个OA,其中考到REST API的URI设计规范,因此写了这篇博客好好地学习一下。
