接口到底是跟 HTTP 状态码走,还是全返回 200 自己写个 code?

北侧工程师 · · Field Notes

有天被问到这句话,愣了一下,发现自己也说不利索。

大概就是:失败了是返回 400/500,还是反正都 200,在 JSON 里塞个 code 告诉你成没成?

顺嘴还会问第二句:那请求是不是也别折腾 PUT/DELETE 了,全 POST 得了?

这两问经常捆一块。下面是我后来想清楚一点、但也不敢说标准答案的说法。

先说状态:两套“成功”

常见写法大概三类。

A. HTTP 说了算

  • 2xx:业务成功(或至少“请求被正确处理完”)
  • 4xx:客户端问题(参数、权限、冲突)
  • 5xx:服务端炸了

body 里可以有更细的错误码,但第一眼先看 status。网关、重试、监控、浏览器 devtools 都认这个。

B. HTTP 永远 200,业务看 body

{ "code": 0, "message": "ok", "data": {} }
{ "code": 10001, "message": "余额不足" }

网关层面“全绿”,真实失败藏在 payload 里。对接过老 RPC、某些开放平台的人一定眼熟。

C. 混着用(最容易坑)

有时 400 带着业务码,有时 200 带着 code != 0,还有“业务失败也 500”。 调用方不知道该写 if status == 200 还是 if body.code == 0,重试策略更是一团浆糊。

我自己的偏好比较务实:

  • 传输/协议层问题用 HTTP:连不上、超时、网关拒、鉴权失败、限流,别伪装成 200。
  • 领域业务结果可以有业务码,但别和 HTTP 语义对着干。
    例如“余额不足”更像 409/422 + 明确业务码,而不是 200 + code=10001(除非你对接的生态强制如此)。
  • 最怕双成功定义:HTTP 200 且 code!=0,或 HTTP 4xx 却 code==0。团队里出现这两种,后面全是补丁。

一句话:可以两套码并存,但要约定谁是主信号、谁是细节。别让调用方猜。

再说第二坨:全 POST,还是 REST 动词

这也是万年辩论。

全 POST 的理由(我理解,不全反对)

  • 网关、防火墙、日志链路对 POST 最省心
  • 复杂查询塞 body,不被 URL 长度和缓存语义折磨
  • 对前端/移动端“一个 endpoint 一张表”很直观,像 RPC
  • 某些公司历史包袱:只放行 GET/POST

认真用动词的理由(我也吃过甜头)

  • GET 只读、可缓存、可预取,语义清楚
  • PUT/PATCH 表达幂等更新,重试时心里有数
  • DELETE 一眼能看出破坏性
  • 中间件、权限、审计可以按 method 做策略,而不靠解析 path 猜意图

我见过的翻车

  • 全 POST 之后,所有接口都叫 /api/do,靠 body 里的 action 分发——半年后没人敢重构
  • “删除”用 POST,重试和“再点一次”变成双删/重复副作用
  • GET 却改数据,CDN 或预加载帮你制造事故
  • 口头说 REST,实际 POST 造资源、POST 改资源、POST 删资源,文档和代码两张皮

我现在怎么选

先说我自己的主力习惯,别绕:

从零搭、能说了算的服务——我主要还是 HTTP 状态码当主信号,body 里的 code 当细节;method 也尽量用明白。

具体一点:

  • 自己写的后台 / 资源型接口(最多的情况)
    鉴权挂了就 401,参数烂就 400,冲突 409,限流 429,服务炸了 5xx。业务失败也可以带个业务码方便前端展示文案,但不会成功失败都挤在 200 里让人猜。
    读用 GET,创建用 POST,改用 PATCH/PUT,删用 DELETE。不炫技,图的是网关、日志、重试都好理解。

  • 对接第三方、开放平台、祖传系统
    对方规定全 200 + 自定义 code,我就按对方来,不在对接层硬拗成 REST。自己再包一层,对内翻译成统一错误,别把第三方的脾气散播到全项目。

  • 明显是“动作”不是“资源”的(下单、审批、触发任务、一堆条件的复杂查询)
    POST 到一个说人话的 path 我完全能接受。该幂等的还是要幂等。失败该 4xx/5xx 就别装成功——除非上游就是那套 200 协议。

  • 公司网关只放行 GET/POST,或者规范写死业务码
    先活下去,文档写清楚,别再混出第三种“有时看 status 有时看 code”。

所以不是“永远 REST”或“永远 200”。
我默认选第一种;后几种是入乡随俗和历史包袱。
最烦的是一个项目里两套主信号并存,调用方每次都要问:今天听谁的?

Replies