RESTful 设计原则:资源、方法、状态码归纳总结

RESTful 架构说简单也简单,说复杂也复杂。很多人天天用,但一到设计接口就翻车。我当年刚接手一个电商项目时,把订单接口写成 /getOrder?id=123,被架构师骂了一顿。嗯,从那以后我才真正理解了 REST 的核心——**资源、方法、状态码**三件套。今天咱们就掰开揉碎讲清楚。

资源:URL 里藏着的世界观

REST 把一切业务实体都看作「资源」。资源用名词表示,比如用户、订单、商品。你想想看,HTTP 的 URL 本质上就是资源的地址。我习惯把资源设计分成三步:

  1. 确定资源边界:一个业务实体就是一个资源,不要拆得太碎。比如「用户地址」通常作为子资源,而不是独立资源。
  2. 用名词复数:/users、/orders,而不是 /getUsers、/findOrder。动词只出现在 HTTP 方法里。
  3. 层级关系用斜杠:/users/{userId}/orders 表示某个用户的订单列表。

核心原则:URL 只描述资源是什么,不描述对资源做什么。做什么的事,交给下一个话题——HTTP 方法。

我曾经碰到过一个团队,把「删除用户」设计成 /deleteUser,把「启用用户」设计成 /enableUser。那叫一个混乱。说白了,这就是把 RPC 思维硬塞进 REST 里。正确的做法应该是 DELETE /users/{userId} 和 PATCH /users/{userId}(带上状态字段)。

HTTP 方法:CRUD 的标准化操作

RESTful 接口利用 HTTP 协议自带的五种方法完成增删改查:

HTTP 方法操作幂等安全典型场景
GET查询资源是是获取用户列表、查看订单详情
POST创建资源否否新增用户、提交订单
PUT全量替换资源是否更新用户全部信息(覆盖)
PATCH局部更新资源否否修改用户邮箱、更新订单状态
DELETE删除资源是否删除用户、清除过期数据

注意:幂等意味着多次调用结果相同。POST 不是幂等的,所以创建订单时如果网络超时客户端重试,可能产生重复订单。解决方案是客户端生成幂等键(Idempotency-Key)。

我踩过一个坑:用 PUT 做局部更新。那时候我以为 PUT 就是更新,后来才发现 PUT 是全量替换。如果你只传了 email 字段,服务端会把其他字段置空或变成默认值。所以,局部更新一定要用 PATCH,或者用 POST 加自定义动作(但这样就不够 RESTful 了)。

状态码:让客户端一眼看懂结果

HTTP 状态码是服务端对客户端请求的「一句话反馈」。我用一个口诀帮团队记忆:

  • 2xx 成功:200 OK(GET/PUT 成功)、201 Created(POST 创建成功)、204 No Content(DELETE 成功,没有返回体)。
  • 3xx 重定向:301/302 常用于资源地址变更,但 REST API 中较少见。
  • 4xx 客户端错误:400 Bad Request(请求格式错误)、401 Unauthorized(未认证)、403 Forbidden(无权限)、404 Not Found(资源不存在)、409 Conflict(数据冲突,比如更新版本号不一致)。
  • 5xx 服务端错误:500 Internal Server Error(代码崩了)、503 Service Unavailable(服务过载或维护)。

实战建议:返回体里也要带一个业务码(如 code: 1001)和人类可读的消息。因为 HTTP 状态码只有几十种,业务异常可能有几百种。比如「密码过期」用 403 不太精确,业务码 PASSWORD_EXPIRED 更清楚。

我以前见过一个接口,不管什么错误都返回 200,然后在 body 里放 {"success": false, "error": "xx"}。这样做看似灵活,但客户端判断起来很痛苦。你想想看,本来 HTTP 状态码就能告诉你「失败」还是「成功」,为什么非要绕一圈?

一张图理解 RESTful 设计铁三角

RESTful 设计铁三角 RESTful 设计原则 资源 (Resource) 名词复数 / 层级关系 方法 (Method) GET/POST/PUT/PATCH/DELETE 状态码 (Status) 2xx / 4xx / 5xx URL 描述资源,方法定义动作 状态码告知结果,业务码辅助诊断

实战避坑指南

下面这几个问题,是我在代码评审里反复见到的,特此整理出来:

  • 不要用 GET 修改资源状态。有人为了省事,用 GET /users/1/activate?status=1 来激活用户。GET 应该是安全的、无副作用的。激活属于状态变更,应该用 PATCH 或 POST。
  • 状态码不要瞎编。404 专指资源不存在,如果请求路径写错了也返回 400,客户端会困惑。我曾接手一个项目,几乎所有错误都返回 500,排查效率极低。
  • 版本号放哪里?我推荐两种方式:URL 路径 /v1/users 或请求头 Accept: application/vnd.myapp.v1+json。不要放 Query 参数 ?version=1,因为不同版本的资源本质上是不同的资源。
  • HATEOAS 要不要做?对于内部服务,通常不需要;对于开放 API(如 GitHub API),加入链接可以提升可发现性。我个人的做法:业务初期不做,等 API 被大量第三方集成时再补。

小技巧:设计资源时,先列出所有的业务实体(名词),再画 ER 图。每个实体对应一个资源根路径。关联关系用子资源或 Query 参数表达,比如 /orders?userId=123 比 /users/123/orders 更灵活(分页、排序更方便)。

规约总结:RESTful 接口检查清单

检查项正确做法反例
URL 命名名词复数,小写字母,中划线/getUser、/userList
查询参数用于过滤、分页、排序传递敏感信息(如密码)
方法选择严格对应 CRUD用 GET 做删除
状态码2xx/4xx/5xx 准确使用全部返回 200
错误返回体含 code + message + 详情只有 "error": true
幂等处理PUT/DELETE 天然幂等,POST 需幂等键重复创建订单无验证

回想我刚入行时,总觉得 RESTful 规约是条条框框,后来踩坑多了才发现——每条规约背后都是无数工程师的血泪教训。你设计接口时,不妨拿出这张检查清单走一遍,能省下不少沟通和排错的成本。