RESTful 设计原则:资源、方法、状态码归纳总结
RESTful 架构说简单也简单,说复杂也复杂。很多人天天用,但一到设计接口就翻车。我当年刚接手一个电商项目时,把订单接口写成 /getOrder?id=123,被架构师骂了一顿。嗯,从那以后我才真正理解了 REST 的核心——**资源、方法、状态码**三件套。今天咱们就掰开揉碎讲清楚。
资源:URL 里藏着的世界观
REST 把一切业务实体都看作「资源」。资源用名词表示,比如用户、订单、商品。你想想看,HTTP 的 URL 本质上就是资源的地址。我习惯把资源设计分成三步:
- 确定资源边界:一个业务实体就是一个资源,不要拆得太碎。比如「用户地址」通常作为子资源,而不是独立资源。
- 用名词复数:
/users、/orders,而不是/getUsers、/findOrder。动词只出现在 HTTP 方法里。 - 层级关系用斜杠:
/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 设计铁三角
实战避坑指南
下面这几个问题,是我在代码评审里反复见到的,特此整理出来:
- 不要用 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 规约是条条框框,后来踩坑多了才发现——每条规约背后都是无数工程师的血泪教训。你设计接口时,不妨拿出这张检查清单走一遍,能省下不少沟通和排错的成本。