4、HTTP 方法与语义:GET/POST/PUT/DELETE/PATCH 的正确使用场景

我在刚接触 API 设计时,总觉得方法选什么都行。反正能调通,谁在乎是 GET 还是 POST?后来在一个金融项目里,就因为 GET 请求不小心改了数据,导致结算对账出了大问题。

其实,每个 HTTP 方法都对应一个明确的语义。错用了,轻则让同事骂娘,重则挖坑给整个系统埋雷。今天我们就来把这几个方法理清楚。

4.1 方法即契约:为什么要严格遵循语义?

说白了,HTTP 方法就是客户端和服务器之间的一纸契约。

  • 客户端 通过方法告诉服务器:「我要干什么」。
  • 服务器 根据方法判断:「该怎么处理」。
  • 中间件(网关、缓存、代理) 也会看方法来决定:「能不能缓存?」、「要不要重试?」。

你想想看,如果用了 POST 去查询数据,缓存层根本不认,每次都得穿透到后端。明明可以走 CDN 缓存的 GET,硬生生搞成了全链路调用。

我个人习惯把方法选择当作接口设计的第一步。方法选对了,后面的参数、返回码、错误处理都会自然对齐。

核心原则: 方法语义 = 操作意图。别用 POST 做查询,别用 GET 做修改。

4.2 GET:查询资源,别干别的

GET 的语义非常单纯:获取资源。它应该是幂等的、安全的。

  • 幂等:调用 1 次和 100 次,结果一样。
  • 安全:只读,不改变服务端状态。这就是为什么搜索引擎爬虫只发 GET 请求的原因。

我在项目中遇到过有人把删除操作也写成 GET。接口是 GET /users/123/delete。你想想看,搜索引擎来爬一次,用户就被删了。这能不出事吗?

4.3 POST:创建资源,非幂等

POST 是 创建新资源 的标准方法。它没有副作用——不对,它有副作用。每次调用都会在服务器上产生一个新资源。

非幂等是什么意思?

提交两次同样的 POST,会创建两条数据。比如下单接口,用户点了两次「提交」,你以为会报错?实际上生成了两个订单。

我记得在做电商 API 时,后端必须在上层加幂等令牌。否则 POST 本身就是「多发多创建」的语义。这是设计,不是 bug。

注意: 不要用 POST 实现「查询」。有些团队习惯把所有复杂请求都包装成 POST,理由是有请求体方便传参。这相当于把工具箱里的螺丝刀当锤子用。能用 GET 就用 GET,参数可以放 query string。

4.4 PUT:完整替换资源

PUT 的语义是 完整替换。你给我什么,我就把目标资源变成什么。如果你漏了字段,那个字段就会被重置为空或默认值。

幂等性: 提交一次和十次,最终结果一致。

我在项目中遇到过 PUT 误用的坑:前端只传了修改的字段。比如 PUT /users/123 只传了 {name: "张三"},结果用户的手机号被清空了。因为 PUT 的语义是「整个替换」,不是「局部更新」。

// 正确的 PUT:客户端必须提供完整资源
PUT /users/123
{
  "name": "张三",
  "phone": "138xxxx",
  "email": "zhangsan@example.com"
}

// 如果只想改名字,应该用 PATCH
PATCH /users/123
{
  "name": "张三"
}

4.5 DELETE:删除资源,幂等操作

DELETE,字面意思就是删除。它也是幂等的。第一次删除返回 204 No Content,第二次再删同样的资源,应该返回 404 Not Found。

我曾经见过一个实现:首次 DELETE 返回 200,第二次调用返回 400,理由是「资源已删除,不要再删了」。嗯,这其实是理解的偏差。客户端重复发送 DELETE 请求本身就是常见场景(网络重试),服务器应该优雅处理重复请求,而不是报错。

实践建议: DELETE 接口可以返回 204(成功无内容)或 200(带删除的资源快照)。但不要返回 400/500 来表示「已删除」。

4.6 PATCH:局部更新资源

PATCH 是对 PUT 的补充。它只传递需要修改的字段。

  • 非幂等吗? 这要看具体的实现。举例:PATCH /cart/items 传一个 {op: "add", itemId: 1},每次调用都会往购物车加一件商品。这就是非幂等的 PATCH。
  • 设计建议: 大部分「更新用户资料」「修改配置」的场景,PATCH 都可以设计成幂等。

我记得有一次重构老项目,把所有的 PUT 全改成了 PATCH。因为前端只传修改字段,后端却用 PUT 全量替换,空值覆盖导致数据丢失。从那次之后,我对 PUT 和 PATCH 的区分特别敏感。

4.7 方法选择决策表

我把几个核心维度整理成了表格,方便你对照选择。

方法 语义 幂等 安全(只读) 请求体 典型场景
GET 查询资源 是 是 否 列表、详情、搜索
POST 创建资源 否 否 是 下单、注册、上传
PUT 整体替换 是 否 是 更新完整配置
DELETE 删除资源 是 否 可选 用户注销、文档删除
PATCH 局部更新 视实现而定 否 是 修改昵称、调整权限

4.8 实战避坑:我踩过的三个方法坑

  1. 用 POST 做了批量查询:因为查询条件太多,怕 URL 太长。后来发现客户端做了自动重试,同一批数据后台插入了多份「查询日志」,存储爆炸。正确的做法是用 GET 加分页参数,或者明确告诉客户端这是查询。
  2. PUT 更新时漏了版本号:在没有乐观锁的情况下,两个客户端并发 PUT 同一个资源,后覆盖前,数据丢失。从此我习惯在 PUT 或 PATCH 请求中带上 If-Unmodified-Since 或 ETag。
  3. DELETE 返回了 200 和 body:客户端认为成功,但空 body 导致解析异常。其实规范里写得很清楚:DELETE 成功应该返回 204 No Content。如果有附加信息,放响应头里。
一句话总结: GET 查询,POST 新建,PUT 全覆盖,DELETE 彻底移除,PATCH 局部调整。每个方法都有自己的位置,别抢活干。
HTTP 方法请求流程与语义决策 客户端发起请求 选择 HTTP 方法 GET:查询资源 POST:创建资源 PUT:完整替换资源 DELETE:删除资源 PATCH:局部更新 方法核心特性对照 幂等性 GET ✓ PUT ✓ DELETE ✓ 全文检索/缓存 GET 可缓存 POST 不可缓存 常见错误 POST 做查询 PUT 做局部更新 安全方法:GET │ 不安全方法:POST、PUT、DELETE、PATCH