news 2026/10/3 2:56:58

为什么大厂API设计都在放弃PUT和DELETE?REST与POST之争

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
为什么大厂API设计都在放弃PUT和DELETE?REST与POST之争

我第一次独立设计 API 的时候,是个教科书级信徒:用户更新用PUT /users/{id},删除用DELETE /users/{id},还在接口文档里煞有介事地标注了幂等性。结果联调第一天就被网关打回来了——运维丢给我一句话:"我们这只放行 GET 和 POST,其他的你自己想办法。"

后来我养成了一个习惯:每次接到新项目先翻一遍主流大厂的公开 API 文档。翻得多了才发现,PUT 和 DELETE 在这些公司的接口设计里确实越来越少见,很多新服务干脆只认 POST,更新和删除全用 POST + 动作路径来解决。这不是某个人的审美偏好,而是网络基础设施、业务语义、安全合规、还有 AI 时代 API 风格四股力量拧在一起的结果。今天就把这件事拆开聊透。

1. REST 教科书里的完美分工:PUT 和 DELETE 本该干什么

1.1 方法论的分工:GET 读、POST 增、PUT 全量替换、DELETE 删

先回到标准定义。HTTP 方法不是随便定的,每个方法都有明确的语义约定:GET 负责读取资源,POST 负责提交数据让服务端创建资源或执行某个流程,PUT 要求把请求体里的资源完整替换掉 URL 指向的资源,DELETE 表示删除 URL 指向的资源,PATCH 是后来补上的局部更新方法。在早期的 RESTful 设计里,这五个方法配合资源路径,能表达绝大多数 CRUD 场景。

举个例子,一个典型的博客系统:

GET /posts/42读取文章,POST /posts创建文章,PUT /posts/42提交整篇文章内容直接覆盖原文章,DELETE /posts/42删掉这篇文章。逻辑非常自洽,接口文档一写出来,调用方看方法名基本就知道该干什么。

这套设计的核心思想是"资源导向":你面对的是一个个可以被识别、被操作的对象,HTTP 方法就是对这些对象的标准操作接口。只要资源建模清晰,方法语义就是天然的接口文档。

1.2 幂等性这把尺子,是理解整件事的关键

要理解 PUT 和 DELETE 为什么会被嫌弃,先得搞懂"幂等性"这个概念。一个请求是幂等的,意味着同一个请求执行一次和执行一百次,服务端最终状态是一样的。

在标准定义里:GET 是安全且幂等的(只读不写),PUT 是非安全但幂等的(不管调用多少次,资源最终都是同一个完整状态),DELETE 也是非安全但幂等的(删一个不存在的资源,结果同样是"目标不存在",不会报错),而 POST 既不安全也不幂等——调两次可能产生两条数据。

设计者的本意很美好:给 PUT/DELETE 标上幂等属性,调用方就知道"这个请求就算网络超时重发一遍也没事",从而简化了分布式场景下的重试逻辑。当年的教科书里,PUT 和 DELETE 因此被赋予了"安全可重试"的光环。

1.3 教科书设计在哪些场景里真的成立

实话实说,这套分工在特定场景里至今依然是最优解。最典型的就是对象存储和 KV 存储:你 PUT 一个对象到某个 bucket 路径,无论执行多少次,最终都是那个键对应的对象被完整覆盖;你 DELETE 一个对象,执行多少次都是删掉它。资源生命周期极其清晰,客户端永远持有完整数据,这种情况下 PUT/DELETE 天然匹配,硬改成 POST 反而画蛇添足。

配置中心、分布式锁、任务资源管理这类场景也是同理。只要你处理的确实是"一个可以被整体定义、整体替换的实体",PUT 和 DELETE 就是最准确、最省事的表达方式。看到这里你可能会问:既然这么好,为什么大厂还在大量接口里放弃它们?答案在下一节——因为现实网络环境没这么理想。

2. 中间链路的偏见:网关、防火墙和代理为什么拦 PUT/DELETE

2.1 设备厂商对非常见方法的保守策略

HTTP 方法在标准里定义了不少,但中间网络设备真正"熟练处理"的只有 GET 和 POST。很多老旧代理服务器、企业内网出口设备、甚至某些云厂商的负载均衡器,对 PUT/DELETE 的处理逻辑非常不完善:有的直接丢弃请求,有的返回 501 Not Implemented,有的会把 DELETE 当成危险操作给拦下来。

我亲身踩过一个大坑:给某个客户做系统对接,对方内网代理把我们发过去的 PUT 请求直接吞掉了,返回了一个空的 200 响应。我们排查了一个下午,最后发现是中间设备的策略问题——它只转发 GET 和 POST,其他方法一律静默丢弃。从那以后我就明白了,在不可控的网络链路上,方法是越"常见"越安全,越"冷门"越容易出幺蛾子。

这类问题在跨企业、跨网络的 API 对接里尤其致命。你没法控制调用方网络里那台老旧设备的行为,所以对外 API 设计时,很多团队第一反应就是:方法尽量只用 GET 和 POST,这俩是全网基础设施兼容性最好的,没有之一。

2.2 安全扫描和运维合规,是压死 DELETE 的最后一根稻草

更让 PUT/DELETE 雪上加霜的是安全扫描器的"偏见"。大量漏洞扫描工具和合规检查系统把删改类方法视为高风险信号,一旦扫描报告里出现 PUT/DELETE,就会触发一堆告警,比如"检测到不安全的 HTTP 方法""服务器支持 DELETE 方法可能被恶意利用"之类的条目。

这些告警大部分是误报——支持 DELETE 方法本身不代表有漏洞,真正的风险在于接口的鉴权逻辑是否严密。可是安全团队和运维团队没有动力去逐条解释误报。为了过审、为了减少沟通成本,最省事的方案就是:对外网关层直接不给 PUT/DELETE 开口子。我见过不止一个团队就是这么做的,安全扫描报告一夜之间清净了,代价是 API 设计被迫向 POST 收敛。

2.3 CDN 与缓存策略带来的隐性限制

还有一个容易忽略的层面:CDN 和缓存中间件。GET 天然可以被 CDN 缓存,POST 按约定一般不缓存,而 PUT/DELETE 的缓存和回源行为在各大 CDN 厂商那里的处理策略非常不统一。有些边缘节点会把非 GET/HEAD 请求原样回源,有些会改写方法,有些干脆拒绝服务。

这意味着,如果你的业务要过 CDN,PUT/DELETE 的行为就更难预测。为了规避这些未知行为,很多架构师在做接入层设计时直接就约定"写操作统一走 POST"——反正 POST 在 CDN 链路上的处理逻辑是明确且一致的,省去了大量排障成本。

3. 语义错位:真实业务里的"更新/删除"和 HTTP 标准根本不是一回事

3.1 PUT 的全量替换语义,在部分更新面前有多尴尬

教科书里 PUT 是"全量替换",要求客户端提交资源的最新完整状态。但实际业务里的"更新"几乎都是部分字段更新:改个昵称、调个库存数量、更新一条订单的状态。客户端往往只拿到了一部分最新数据,让它把整个资源对象完整提交回来,既不现实也没必要。

更麻烦的是并发场景。客户端 GET 了一个资源,用户改了其中某个字段提交 PUT,但服务端在这期间可能已经被其他操作改过了。客户端并不知道它拿到的是旧版本,贸然 PUT 回去就会把服务端的新数据覆盖掉——这就是经典的 lost update 问题。要解决它,你得引入版本号、乐观锁、条件更新(If-Match),而这些机制在标准 PUT 语义里并不是默认就有的。一旦要加各种业务约束,PUT 这个"全量替换"的简洁性反而成了包袱。

PATCH 方法本可以救场,它专门为局部更新设计。但很多团队在评估之后还是放弃了:PATCH 的请求体格式有好几套标准(JSON Patch、JSON Merge Patch),不同框架支持程度不一,再引入一种写方法就意味着文档、SDK、网关策略都要跟着扩。既然 POST 什么都能干,为什么要为"局部更新"专门保留一种方法?这个逻辑一旦成立,PUT 出局就是时间问题。

3.2 所谓的删除,九成是 UPDATE 操作

DELETE 的尴尬是另一种。业务上用户点了"删除",数据库里真正执行的往往是UPDATE users SET deleted_at = now() WHERE id = ?——也就是逻辑删除、软删除。为什么要软删除?因为数据要留底、订单要追溯、账号要能恢复,物理删掉是最后手段。于是你让调用方发一个DELETE /users/{id},后端却在执行 UPDATE 语句,语义上就拧巴了。

而且真实业务里的"删除"往往是一连串复杂动作:删除前要检查有没有关联数据,删除时要做级联清理,删除后要写审计日志、发异步通知、甚至触发审批流。这一整套流程已经不是"删除资源"能概括的了,它更像"执行一个删除业务流程"。用 DELETE 方法表达业务流程,就像用一把螺丝刀去钉钉子——能用,但怎么看都别扭。

我见过最有意思的案例:某团队为了避免逻辑删除语义上的拧巴,把所有对外删除接口全部改成POST /resource/{id}/archive、POST /resource/{id}/deactivate这类"动作路径"。结果反而得到了意外收益——调用方一眼就明白这个操作背后有业务规则,再也不用猜 DELETE 到底是物理删还是逻辑删。

3.3 DELETE 带 Body 的雷,谁踩谁知道

还有一个技术细节让 DELETE 很难受:请求体。很多删除操作是需要携带参数后才能执行的,比如"删掉这批选中的 100 条记录,排除其中处于锁定状态的 5 条"。你希望把参数放在 body 里传给服务端。问题是,RFC 规范没有禁止 DELETE 带 body,但大量 HTTP 客户端库、代理服务器、网关中间件对"DELETE + body"的支持非常随意。

有的库直接不支持 DELETE 携带 body,有的中间设备在转发时会悄悄把 body 丢掉,还有的框架在读取 DELETE 请求体时行为不一。为了绕开这些坑,有些团队把删除参数全部塞进 URL 的 query string,结果遇到长列表参数就爆了长度限制,还要处理各种转义问题。最后大家达成共识:删除要传复杂参数,直接用 POST,一了百了。

3.4 安全合规视角:能不暴露真实删除能力,就不暴露

从安全角度看,RESTful 的DELETE /users/{id}太"直白"了。自动化扫描工具可以轻松识别出这是一个删除接口,接着就可能发起越权删除尝试、批量探测、恶意调用。不是说方法叫 DELETE 就一定不安全,而是说你用一个语义极其明确的方法,等于把攻击面明晃晃地摆在了对手面前。

改成POST /users/{id}/deactivate或者POST /api/users+ body 里指定 action,至少把"删除动作"藏在了一个语义模糊的地方。攻击者一眼扫过去,不容易判断这个接口到底是干嘛的。这种"模糊化"解决不了真正的安全问题,核心还是靠鉴权、权限、风控,但确实能减少自动化扫描带来的噪音和定向攻击的尝试。在安全合规压力大的行业里,这个理由足以说服团队放弃 DELETE。

4. 大厂和 AI 时代给出的答案:POST 万能法及其代价

4.1 纯 POST 的 RPC 潮流:动词放在 URL 里

当 PUT/DELETE 被集体嫌弃之后,最主流的替代方案就是"POST 万能法"——所有写操作统一 POST,把动作放在 URL 路径里表达。于是你会看到这样的接口:

POST /users/123/update POST /users/123/deactivate POST /orders/456/cancel POST /invoices/789/pay

这种风格实际上是回归了 RPC(远程过程调用),URL 从"资源定位"变成了"过程调用路径"。它对团队的好处非常实在:不需要纠结方法语义,不需要担心网关放行问题,请求体随便构造,所有逻辑都塞进 POST 里。很多互联网公司的新业务接口就是这样设计的,尤其是面向内部的系统间调用。

我见过不少团队,内部上千个接口清一色 GET/POST,几乎没有 PUT/DELETE。开发效率确实高,新同事上手快,不用理解 RESTful 那套"资源导向"的设计哲学。

4.2 统一 POST + Action 参数的风格

更极端一点的方案是"统一 POST + Action 参数":所有接口都是POST /api,具体执行什么动作靠 body 里的字段区分。国内不少云厂商的 OpenAPI 走的就是这条路子。一个典型的请求长这样:

POST /api HTTP/1.1 Content-Type: application/json { "Action": "DeleteInstance", "InstanceId": "i-123456", "Region": "cn-north-1" }

接口路径是同一个,全靠参数驱动。这套风格的优点是对网关、日志审计、权限策略都极其友好——规则匹配只需要针对一个 URL,方法也只有一个 POST。但缺点也很明显:接口完全没有自描述性,调用方必须依赖文档才能知道怎么拼参数,IDE 帮不上忙,调试也费劲。

4.3 AI 大模型 API 的示范效应:全网都在学 POST

这一节必须单独说,因为它是过去两年里最被低估的"API 教育力量"。现在主流的大模型推理 API——不管哪家的——清一色是POST /v1/chat/completions这种风格:一个 POST 请求,body 里塞模型名、消息列表、参数,然后等返回。没有 PUT,没有 DELETE,没有 PATCH,全宇宙只有 POST。

为什么?因为这些大模型 API 的本质是"动作型"接口——你提交一段文本让服务端去推理生成,它不是对某个资源的 CRUD 操作。你没法"PUT 一个提示词让它生成更好的文本",也没法"DELETE 掉某次推理"。用 POST 表达"请帮我做一件有副作用的事"再合适不过了。

问题在于示范效应太强了。新一代开发者进入行业时,接触的第一批 API 很可能就是大模型 API——调用方式就是把 JSON 扔给一个 POST 地址。他们天然认为"API 就该长这样"。于是我们在设计新的内部接口时,越来越多听到这样的声音:"为什么不能像调大模型那样,直接 POST 一把梭?"AI 时代用 POST 的惯性,正在以惊人的速度重塑整个行业的 API 设计风格。

4.4 折中派:GET + POST + 少量 PATCH

当然也不是所有大厂都走向"POST 万能"。Google Cloud 的 API 设计指南就保持了比较克制的路线:更新资源用 PATCH,自定义操作(cancel、archive 这类动词)用 POST,DELETE 保留在确实需要物理删除/资源回滚的地方。这套方案在网络兼容性和语义清晰度之间取得了平衡,也是我个人比较认可的方向。

还有一些团队的做法是:对外接口只暴露 GET/POST,但在网关层做了一个内部适配——对外方法收敛,对内的后端服务之间保留完整的 RESTful 方法语义。也就是对外你只看到 POST,对内实际转发的是 DELETE 或 PUT。这也是一种兼顾外部现实与内部治理的思路。

4.5 收敛的代价:语义模糊、重试风险、监控失明

POST 万能法不是没有成本,这些代价在被大量使用后逐渐显现。

第一是幂等提示的丢失。PUT/DELETE 本身是幂等的,方法名就告诉调用方"可以放心重试";POST 则不然,重发一次可能产生两条订单、扣两次款。收敛到 POST 之后,所有请求都不能再依赖方法层的幂等保证,必须额外设计幂等键机制、去重表、或者让业务逻辑自己保证幂等。很多团队一开始没意识到这一点,上线后大量出现"网络重试导致重复扣款/重复创建"的线上事故。

第二是监控与运维维度的丢失。监控系统、日志分析、容量规划通常会按 HTTP 方法做聚合统计。全用 POST 之后,你无法快速区分哪些是读请求、哪些是写请求,哪些是资源创建、哪些是动作触发。虽然可以通过 URL 路径再细分,但维度明显变粗了。有一次我帮朋友排查线上问题,发现他们的监控面板上 POST 请求量占了 99%,读和写的趋势根本看不出来,最后只能自己写脚本按 URL 前缀重新聚合。

第三是接口的可发现性变差。RESTful 设计的优势之一是"自描述"——一个 DELETE 方法配上资源路径,语义八九不离十。收敛成 POST + 动词 URL 之后,接口变成了各种动词的组合,文档维护不到位的话,调用方根本不知道该用哪个路径。很多团队最后被迫专门写一份"动作字典"来弥补语义模糊的问题。

下面这张表基本概括了各方案的取舍:

方案语义清晰度网络兼容性方法级幂等提示适用场景
严格 REST(GET/POST/PUT/DELETE/PATCH)高低,容易被中间设备拦截有资源型 CRUD,对象/KV 存储
纯 POST + 动词 URL中高无,需额外幂等机制复杂业务流程、动作型操作
统一 POST + Action 参数低最高无,完全依赖业务保证云厂商 OpenAPI、海量规则匹配
GET + POST + PATCH + 自定义方法较高中PATCH 无,得靠自己大规模资源型 API,Google 风格

5. 我的决策框架:什么场景该放弃 PUT/DELETE,什么场景该坚持

5.1 先分清你的 API 是资源型还是动作型

看多了大家的取舍之后,我形成了一个决策框架,开头永远先问一个问题:这个接口是"资源操作"还是"业务流程触发"?

如果它真的是资源操作——比如保存一个文件、更新一条配置、删掉一个缓存键、覆盖一个对象——那 PUT/DELETE 依然是最优解。对象存储、KV 存储、配置中心,这些场景的标准接口到今天依然是 PUT/DELETE,因为资源边界清晰、客户端持有完整状态、幂等语义天然匹配,没有任何理由改 POST。

如果你的操作本质是"让服务端做一件有副作用的事"——生成一段文本、下一笔订单、取消一个任务、执行一次转账——那就是动作型接口,直接用 POST + 动作路径。强行套 PUT/DELETE 只会让语义拧巴,让调用方猜你的"更新"到底是全量还是增量、"删除"是物理还是逻辑。

5.2 对外 API 求稳,对内 API 求准

第二个判断维度是接口的调用环境。对外公共 API 面对的是不可控的网络环境:调用方可能在老旧企业内网、经过各种奇怪的代理、用着不维护的 SDK。这种情况下,我倾向于让方法收敛到最稳的组合——通常就是 GET 和 POST——哪怕牺牲一点语义优雅,也要保障可用性。网络兼容性在此刻压倒一切。

内部服务 API 则不一样,环境受控,团队可以约定标准,网关策略自己说了算。在这种情况下,我建议尽量保留完整的语义分层。内部系统之间调用极其频繁,接口设计标准化带来的长期收益——清晰的资源操作、幂等约定、监控维度——会随着系统数量增长越来越大。

5.3 如果被迫收敛,怎么收得体面

如果你也遇到了"网关不让用 DELETE"或者"领导要求统一 POST"的情况,我建议别硬刚,而是在架构上做适配,尽量把损失降到最低。

一种做法是网关层做方法映射:对外暴露的接口语义清晰(比如DELETE /users/{id}),网关收到请求后内部转换成 POST 转发给后端服务,或者后端服务本身就是完整 RESTful 的,只是对外入口被网关收敛了。这样既不损失内部语义,也满足了外部限制。

另一种做法是在 SDK 层面做封装:底层请求全走 POST,但 SDK 暴露给业务方的方法名是deleteUser()、updateProfile()这种语义明确的名字。调用方根本感觉不到方法的差异,所有兼容性处理都被 SDK 吞掉了。

不管用哪种方式,有两件事必须做:第一,所有可能被重试的请求必须有幂等机制,要么幂等键,要么业务去重,别把重试风险留给调用方;第二,文档里必须明确写清楚哪些请求安全可重试、哪些不是,这是方法收敛之后最容易缺失的信息。

5.4 个人踩坑记录与最终体会

我自己在这个问题上也反复横跳过。早些年我是 RESTful 原教旨主义者,谁把DELETE /users/{id}改成POST /users/{id}/delete我就跟谁急。后来在真实项目里被网关拦、被安全扫描逼、被业务语义拧巴折磨过几轮之后,我慢慢松动了。

最戏剧化的一次是:某个项目因为网关限制,不得不把删除接口从 DELETE 改成POST /projects/{id}/archive。我当时觉得这是妥协,结果上线后调用方反馈反而更好了——因为"archive"这个动作让所有人都清楚这是归档而非物理删除,业务规则一步到位就传达到了客户端。很多你以为的"妥协",在语义层面反而可能歪打正着。

现在我的态度很简单:方法选型是工程决策,不是品位问题。我见过纯 POST 风格跑得非常顺的团队,也见过严格 RESTful 维护得很好的系统。决定一个接口好不好的,不是它用了哪种 HTTP 方法,而是它的语义是否清晰、出错时是否可诊断、重试时是否安全、团队是否容易理解。你正在设计新接口的话,就先问自己三个问题:调用方是机器还是人?网络链路上有多少层不可控的中间设备?这个操作的本质是资源变更还是业务动作?把这三个问题想清楚,PUT 和 DELETE 到底该不该用,答案其实就在那里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:55:08

课程答疑系统设计实战:SpringBoot+Vue+MyBatis全栈踩坑与优化

先说一个我观察到的现象:市面上的“课程答疑系统”绝大多数是拿论坛源码或工单系统改的,把发帖叫“提问”,把回帖叫“回答”,角色换一下就交差了。这东西不是不能用,但离真实的课堂场景差得远——没有课程归属、没有教…

作者头像 李华
网站建设 2026/10/3 2:54:49

SpringBoot酒店客房预订系统毕设:从选题到部署答辩的完整指南

本身是做毕设带学生的,每年springboot类题目占一半还多,酒店客房预订系统又是其中被点率最高的一个。这个题目看着简单,但真到答辩时能讲清楚的人不多——大部分人卡在同一个地方:系统能跑,但说不明白"为什么这么…

作者头像 李华
网站建设 2026/10/3 2:53:57

从零手写SysY编译器:西工大编译原理试点班大作业实战指南

简介:这份资源是西北工业大学编译原理试点班的大作业完整交付物,面向计算机、人工智能、通信工程等专业需要完成课程设计或毕业设计的学生,也适合想深入理解编译器构造的进阶学习者。核心内容是一个能够正常工作的Sysy语法编译器,…

作者头像 李华
网站建设 2026/10/3 2:53:48

华为云计算HCIE笔试v3.5变题解读与高效备考路线

华为云计算HCIE笔试升级v3.5的消息,这几天在备考群里确实把不少人炸出来了。各机构“变题通知”刷了一波又一波,但真正把“到底变什么、现在怎么备考、题库v3.0到底更新了啥”说清楚的内容并不多。我这段时间把新旧考纲、近期考生回忆、官方材料重新捋了…

作者头像 李华
网站建设 2026/10/3 2:53:27

滑雪场管理系统实战:SpringBoot2+Vue3前后端分离开发全解析

最近刚把手上的滑雪场管理系统从零到一完整落地,整套代码基于 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0,源码和文档一起交付。很多人一看到"管理系统"四个字,脑子里自动浮现"增删改查"——实际做起来真不是那么回事。…

作者头像 李华
网站建设 2026/10/3 2:52:59

金融时序建模实战:从数据清洗到可解释预测

简介:这是一份面向本科毕业设计、课程设计与机器学习初学者的股票价格预测实战项目,基于Python实现LSTM与传统机器学习模型(如SKLearn)双路建模,解决金融时序数据预测中的特征工程、模型训练与结果可视化等核心问题。资…

作者头像 李华