RESTful API 设计有哪些规范?

基础实践约 6 分钟读完

一句话回答

核心思想是一切都是资源:用 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:
JSON
{ "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 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。