接口改造的时候,最容易遇到一种看起来很奇怪的需求:
“以后所有接口统一改成 POST。”
刚看到这个要求,我第一反应一般不是马上改 Controller。
因为 HTTP 方法不是装饰品,GET、POST、PUT、DELETE 背后都有语义。一个系统突然放弃 REST 风格,所有接口都走 POST,通常不是开发人员偷懒,而是背后有一些现实问题。
我之前也遇到过类似情况。
原来的接口设计挺标准:
GET /user/detail?id=10001
POST /order/create
PUT /user/update
DELETE /user/remove
后来网关层提出要求:
所有业务接口只允许 POST。
刚开始觉得有点反直觉,后来排查下来,原因还真不少。
参数暴露
GET 最大的问题不是不能传复杂参数,而是参数会放在 URL 里面。
比如查询订单:
GET /order/query?userId=10001&status=PAY_SUCCESS&start=2026-01-01
看起来没啥。
但是 URL 会经过很多地方:
- 浏览器历史记录
- Nginx access log
- 网关日志
- 各种链路追踪系统
- 甚至一些监控平台都会采集 URL
如果参数里面带用户手机号、业务编号、token,这些信息很容易留下痕迹。
所以有些公司干脆统一 POST,把参数放到 body 里面。
比如:
@PostMapping("/order/query")
public OrderPage query(@RequestBody OrderQueryRequest request) {
log.info("query order, userId={}", request.getUserId());
return orderService.query(request);
}
请求:
{
"userId": 10001,
"status": "PAY_SUCCESS",
"startTime": "2026-01-01",
"endTime": "2026-01-31"
}
至少从接口层面,不会把业务参数直接暴露在 URL。
当然,这不是说 POST 就绝对安全。
HTTPS 才是真正负责传输加密的。POST 只是减少了一些日志污染。
复杂查询
很多项目早期接口设计比较简单:
GET /product/list?page=1&size=20
后来业务越来越复杂。
筛选条件增加:
最后接口可能变成:
GET /product/list?
category=phone&
tags=a,b,c&
minPrice=1000&
maxPrice=5000&
sort=createTime
URL 越来越长。
有些老系统或者中间代理,对 URL 长度有限制。
比如 Nginx:
large_client_header_buffers 4 8k;
超过限制直接 400。
所以查询类接口很多最后也会改 POST。
Java 里面通常会这么处理:
@PostMapping("/product/search")
public Page<ProductVO> search(@RequestBody ProductSearchDTO dto) {
if (dto.getPageSize() > 100) {
throw new IllegalArgumentException("page size too large");
}
return productService.search(dto);
}
这种接口本质还是查询,只是 HTTP 方法用了 POST。
这个地方很多新人会纠结:
“POST 不是创建数据的吗?”
理论上是。
但是工程实践里面,为了稳定性,POST 被大量用于复杂查询。
网关治理
微服务环境里面,接口前面一般不是直接挂服务。
链路通常是:
客户端
|
网关
|
鉴权服务
|
业务服务
|
数据库
网关需要做很多事情:
如果 GET、POST、PUT、DELETE 全部存在,网关规则会多很多。
比如:
/user/**
GET
POST
PUT
DELETE
每种方法都可能有不同策略。
有些公司为了降低治理复杂度,直接规定:
业务接口全部 POST。
网关只需要处理:
POST /*
规则简单很多。
以前排查过一个接口超时问题。
业务日志看不出来。
最后查网关 access log:
2026-03-12 10:21:32
POST /api/order/pay
status=504
cost=30001ms
traceId=8f72xxx
顺着 trace 查下去,发现不是接口慢,而是网关默认只允许 POST body 重放。
某些 GET 请求经过重试组件时,参数丢失了。
统一 POST 后,这类问题少很多。
幂等处理
还有一个容易被忽略的点:POST 更适合统一处理幂等。
很多业务接口其实不是简单 CRUD。
这些接口即使使用 POST,也必须考虑重复请求。
比如:
用户点击支付按钮。第一次请求成功。网络抖了一下。客户端重新发一次。
如果没有幂等处理,就可能创建两个支付订单。
代码一般会这么做:
@PostMapping("/pay")
public PayResult pay(@RequestBody PayRequest request) {
String requestNo = request.getRequestNo();
PayRecord old = payDao.findByRequestNo(requestNo);
if (old != null) {
return PayResult.success(old.getPayId());
}
PayRecord record = new PayRecord();
record.setRequestNo(requestNo);
record.setStatus("PROCESSING");
payDao.insert(record);
return paymentService.execute(record);
}
关键不是 POST。
关键是业务唯一号、数据库唯一索引、状态控制。这些才是真正防重复。
不是没有代价
不过,所有接口都 POST 也不是没有问题。
最明显的问题就是语义混乱。
比如:
POST /user/delete
看到这个接口,开发人员不知道它到底是不是幂等。是不是修改数据?是不是需要重试?是不是允许缓存?
另外 GET 天然支持缓存。
浏览器、CDN、代理服务器,都可以根据 GET 做缓存。POST 默认不会缓存。
如果一个接口:
GET /config/version
每天调用几十万次。
改成:
POST /config/version
缓存能力直接没了。
小结
所以实际项目里面,我一般不会简单认为“全部 POST 一定更好”。
要看场景:
- 内部管理系统
- 复杂查询系统
- 强网关治理要求
- 统一鉴权体系
这些场景全部 POST 很常见。
但是开放 API、公共 REST 接口,还是建议遵守 HTTP 语义。
查询就是 GET,创建就是 POST,修改就是 PUT,删除就是 DELETE。
技术设计很多时候不是追求最优雅,而是在各种限制里面找一个成本最低的方案。
接口规范也是一样。你看到一个系统所有接口都是 POST,不一定是水平低。有可能它经历过代理限制、网关治理、日志安全、跨语言调用之后,最后选择了一个更容易维护的方案。类似的工程取舍思辨,在云栈社区也常有开发者拿出来讨论。