RESTful API 设计有哪些规范?
一句话回答
核心思想是一切都是资源:用 URL 表示资源,用 HTTP 方法表示对资源的操作,用状态码表示结果。在此基础上统一查询参数(过滤、排序、分页)、版本管理和错误格式,接口保持无状态,并利用好方法的幂等性。很难用增删改查表达的业务动作,可以折中设计成子资源或动作。
详细解析
URL 设计
URL 用名词复数表示资源,不用动词;用层级表示从属关系:
GET /users 查询用户列表
GET /users/123 查询单个用户
POST /users 创建用户
PATCH /users/123 修改用户的部分字段
DELETE /users/123 删除用户
GET /users/123/orders 查询某个用户的订单
- 反例:
/getUsers、/user/delete?id=123,动作应该由 HTTP 方法来表达 - 一般用小写字母,单词之间用连字符,如
/order-items;层级不要太深
方法与操作
| 方法 | 操作 | 幂等 | 成功时常用的状态码 |
|---|---|---|---|
| GET | 查询 | 是 | 200 |
| POST | 创建 | 否 | 201,并用 Location 头指向新资源 |
| PUT | 整体替换(不存在时可以创建) | 是 | 200 或 204;新建时 201 |
| PATCH | 部分修改 | 不保证 | 200 |
| DELETE | 删除 | 是 | 204 |
安全和幂等的含义见 GET 和 POST 的区别。
用状态码表达结果
常用的状态码如下,细节见 HTTP 常见状态码:
- 2xx 成功:200、201、204
- 4xx 客户端错误:400 参数错误、401 未认证、403 无权限、404 资源不存在、409 冲突(如重复创建)、422 参数校验不通过、429 请求太频繁
- 5xx 服务端错误:500 内部错误、502 网关错误、503 服务暂时不可用
查询参数:过滤、排序、分页
例如 GET /orders?status=paid&sort=-created_at&page=2&page_size=20:status=paid 用来过滤,sort=-created_at 表示按创建时间排序(- 表示降序),page 和 page_size 用来分页。分页有两种方式:
| 方式 | 写法 | 优点 | 缺点 |
|---|---|---|---|
| 偏移分页 | ?page=2&page_size=20,对应 SQL 的 offset / limit |
简单,可以跳到任意页 | 深分页慢,数据库要扫描并丢弃前面所有的行;翻页期间有数据插入或删除时,会出现重复或遗漏 |
| 游标分页 | ?cursor=xxx&limit=20,游标记录上一页最后一条的位置(如 id) |
性能稳定,数据变化时不重不漏 | 不能跳页,只能一页一页往后翻 |
游标分页对应的 SQL 大致是 WHERE id < :last_id ORDER BY id DESC LIMIT 20,可以直接利用索引定位。
版本和错误格式
- 版本管理:在 URL 中带上版本号,如
/v1/users,直观,也最常用;也可以放在请求头中,URL 更干净,但调试不太方便 - 统一的错误格式:包含业务错误码、错误信息,以及方便排查问题的请求 ID:
{ "code": "ORDER_NOT_FOUND", "message": "订单不存在", "request_id": "7f3c9a1e2b" }
也可以采用 RFC 9457 定义的 Problem Details 格式(application/problem+json)。
无状态和幂等
- 无状态:每个请求都携带处理所需的全部信息(比如鉴权令牌),服务端不依赖上一次请求留下的上下文,方便水平扩展
- 幂等:GET、PUT、DELETE 重复执行的效果一样,可以放心重试;POST 不幂等,超时重试可能导致重复下单。常见做法是客户端为每次操作生成一个唯一的幂等键(如放在
Idempotency-Key请求头里),服务端记录处理结果,遇到重复的请求直接返回第一次的结果
实践中的折中
有些业务动作很难用增删改查表达,可以设计成动作或子资源,比如用 POST /orders/123/cancel 取消订单,用 POST /orders/123/payments 创建一条支付记录来表示支付。
和 GraphQL、gRPC 对比
| REST | GraphQL | gRPC | |
|---|---|---|---|
| 接口形式 | 多个资源 URL + HTTP 方法 | 通常只有一个端点,客户端用查询语句指定要哪些字段 | 调用远程方法,用 Protocol Buffers 定义接口 |
| 数据格式 | 通常是 JSON | JSON | Protobuf 二进制,基于 HTTP/2 传输 |
| 优点 | 简单通用,能利用 HTTP 缓存 | 按需获取字段,一次请求拿到关联数据 | 性能高、强类型,支持流式调用 |
| 适合场景 | 对外开放的 API、常规业务接口 | 前端需求多变、数据关联复杂 | 内部微服务之间的调用 |
用 OpenAPI 描述接口
用 OpenAPI 规范(前身是 Swagger 规范)描述接口的路径、参数和响应结构,可以自动生成文档(如 Swagger UI)、客户端代码和 Mock 数据,也能用来校验请求参数。
面试官可能追问
分页用 offset 还是游标?
看场景。需要跳转到任意页、数据量不大的场景(如后台管理的表格)用 offset;数据量大、无限滚动、数据实时变化的场景(如信息流、聊天记录)用游标。如果必须用 offset 又担心深分页的性能,可以限制最大页码。
HTTP 状态码和业务错误码怎么配合?
状态码表达大类(成功、未认证、客户端错误、服务端错误),给网关、监控、浏览器和重试逻辑使用;业务错误码放在响应体里,区分具体原因(如"余额不足""库存不足"),给前端展示和处理。
有的团队让所有接口都返回 200,只在响应体里放错误码。这样做的代价是监控统计不到错误率,网关和客户端的重试、缓存逻辑也可能误判。
RESTful 和 RPC 怎么选?
- REST 面向资源,语义统一,适合对外开放的 API 和以增删改查为主的业务
- RPC 面向动作(如
createOrder),调用直观;gRPC 这类框架性能高、强类型,适合内部服务之间的调用 - 常见的组合是:对外提供 REST,内部服务之间用 gRPC
易错点
- PUT 是整体替换,请求里没传的字段按语义会被清空;只改部分字段用 PATCH
- 不要用 GET 做修改操作:GET 请求可能被缓存、被预加载,或者被爬虫访问
- REST 是一种设计风格,不是强制标准,团队内部保持一致比追求"纯粹"更重要
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。