图 1:本文封面,点明「参数位置不是本质区别」。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料,与任何公司的当前招聘无关。「GET 和 POST 有什么区别」大概是网络面试出场率最高的题,也是最容易答浅的题——「参数一个在 URL 一个在请求体」说完这一句,面试官的表情通常不会有任何变化。
想象面试现场,面试官的问题就这一句:说说 GET 和 POST 的区别。
第一步,先定调:区别在语义,不在参数位置。
参数放哪只是表面现象。HTTP 语义标准 RFC 9110 给两个方法的定义是:GET 是「请求获取目标资源的当前表示」,本质是只读的取数操作;POST 是「让服务端按自身语义处理请求里携带的数据」,本质是提交动作。一个语义上的差异,才派生出后面所有的工程差异。
第二步,五个维度把差异讲透。
语义与安全性。RFC 9110 把 GET 定义为安全(safe)方法:只读取、不改变资源状态——当然,服务端记日志这类附带动作不算违反安全性。POST 没有这个承诺,它的使命就是触发处理:创建订单、提交表单、调用生成接口。
幂等性。GET 是幂等的:同一请求执行一次和执行 N 次,效果相同。POST 不幂等:重复提交可能重复创建资源。这一条是工程上最值钱的差异——网络超时后客户端要不要自动重试?GET 放心重试,POST 直接重试就可能重复扣费、重复下单。
缓存。RFC 9110 写明 GET 的响应是可缓存的,浏览器、CDN 都可以存下来复用;POST 的响应默认不缓存,除非响应里显式带了缓存指令。这就是为什么静态资源、搜索接口走 GET 能吃到整个缓存体系的红利。
参数位置。GET 的参数习惯放在 URL 查询串里:会进浏览器历史、服务器访问日志、代理日志,且受 URL 长度限制。POST 的参数在请求体里:格式灵活,JSON、表单、二进制都行。注意这只是约定,GET 也能带请求体、POST 也能带查询串,只是实践与基础设施都按约定优化。
安全性错觉。「POST 比 GET 安全」是经典误解:两者都是明文,保密性一模一样,真正的保密靠 HTTPS。POST 只是参数不出现在 URL 里,避免了进历史和日志的尴尬,仅此而已。
图 2:GET 与 POST 的五维对比与重试工程要点。
第三步,把幂等差异变成可运行的实验。
超时重试 POST 会怎样?下面这份代码模拟一个扣款接口:客户端请求后「超时」,于是重试一次——对比无幂等键与有幂等键两种实现下账户被扣几次,用 Node.js 22.15.0 直接运行(node idempotent-retry.js)即可:
'use strict';
// 模拟服务端账户
const account = { balance: 1000, charged: 0 };
const processed = new Set(); // 已处理过的幂等键
// 无幂等保护:每次请求都扣款
function chargeNaive(amount) {
account.balance -= amount;
account.charged++;
return { ok: true, balance: account.balance };
}
// 有幂等键:同一笔请求重复到达,直接返回首次结果
function chargeIdempotent(amount, idempotencyKey) {
if (processed.has(idempotencyKey)) {
return { ok: true, balance: account.balance, duplicate: true };
}
processed.add(idempotencyKey);
return chargeNaive(amount);
}
// 模拟客户端:请求后超时,于是重试一次
function clientPays(chargeFn, times) {
for (let i = 0; i < times; i++) chargeFn();
}
account.balance = 1000; account.charged = 0; processed.clear();
clientPays(() => chargeNaive(100), 2); // 用户只下一单,超时重试变成两次请求
console.log(`无幂等键:下 1 单重试 1 次,实际扣款 ${account.charged} 次,余额 ${account.balance}`);
account.balance = 1000; account.charged = 0; processed.clear();
const key = 'order-20260930-001';
clientPays(() => chargeIdempotent(100, key), 2);
console.log(`有幂等键:下 1 单重试 1 次,实际扣款 ${account.charged} 次,余额 ${account.balance}`);
实测输出如下:
无幂等键:下 1 单重试 1 次,实际扣款 2 次,余额 800
有幂等键:下 1 单重试 1 次,实际扣款 1 次,余额 900
同一笔订单,超时重试在无保护时实打实扣了两次款;带上幂等键后,第二次请求被服务端识别为重复,原样返回首次结果。POST 天生不幂等,所以幂等性要由应用层自己补——客户端生成幂等键、服务端按键去重,是支付、下单、AI 生成类接口的标配。反过来,GET 幂等又安全,客户端、浏览器、代理都可以放心重试,这也是大模型 API 的查询类端点适合 GET、生成类端点必须处理重试语义的原因。
易错点清单。
一,只会背参数位置,讲不出语义、幂等、缓存三个本质差异。二,把「POST 更安全」当结论——两者都明文,保密靠 HTTPS。三,认为 GET 绝对不能带请求体——规范并未禁止,只是实践不友好。四,给 POST 接口加重试却不上幂等键,线上就是重复扣费事故。
回到面试现场,建议的答题节奏:先一句话把参数位置降级为「表面现象」,再按语义、幂等、缓存、参数、安全五个维度展开,最后落到工程要点——重试 GET 天然安全,重试 POST 必须配幂等键。有原理有落点,这题就答出了区分度。
你设计接口时有没有为幂等性专门做过方案?用的是幂等键、唯一约束还是状态机?欢迎分享你的做法。