JSON、表单与图片接口的响应设计:三类返回各自的约定与边界
三类接口不能用一套响应模板覆盖:JSON 接口适合统一信封与稳定错误码;表单接口要先区分浏览器原生提交和脚本提交,前者用 303 跳转、后者用 JSON;图片等二进制接口的正文就是负载,错误只能靠状态码与 Content-Type 切换表达,不能套 JSON 信封。三者可共用 request_id、状态码与业务码分工、稳定分页顺序。本文不列举 API.PLUS 的具体端点与错误码,需以官方文档为准。
- JSON 接口的成功与失败响应保持同一外层结构,客户端只需一个解析分支。
- 表单提交的返回取决于提交方式:原生表单用 303 + Location,fetch/XHR 用 JSON,不要靠 Use
- 图片接口不要包 JSON 信封;错误时改 Content-Type 并返回非 2xx,让客户端先判状态码。
三类接口不该共用一套响应模板。JSON 接口可以用统一信封承载成功与失败;表单接口的返回形式取决于提交方式,浏览器原生提交和脚本提交需要不同的响应;图片接口的正文就是负载本身,任何信封都会破坏它。本文给出可以落地的字段与状态码约定,并标出三者互相冲突的位置。
需要说明边界:本次没有 API.PLUS 的接口文档正文,因此下文不列举它的具体端点、字段名、错误码或限额。涉及该平台的实际契约,请以官方文档为准(待核实)。文中的示例为未执行的示例,仅示意结构,路径与 ID 均为占位。
JSON 接口:信封稳定,错误可枚举#
建议让成功与失败共享同一外层结构,客户端只写一个解析分支:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"ok":true,"data":{"id":"ord_123"},"request_id":"req_abc"}
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json; charset=utf-8
{"ok":false,
"error":{"code":"invalid_phone","message":"手机号格式不正确","field":"phone"},
"request_id":"req_abc"}
几条具体约定:
- 状态码与业务码分工。HTTP 状态码回答“请求有没有被处理”,
error.code回答“处理结果是什么”。两者表达同一件事时,客户端会写出两套判断逻辑。 - 错误码用稳定标识,不用文案。
message面向人,可以改;code面向程序,一经发布应当只增不改。 - 明确 null 与缺省的区别。字段取值为空和字段不存在,最好在文档里各给一个例子,否则前端只能靠
in判断。 - 时间统一为 UTC 的 ISO 8601 字符串,并在字段名或文档里说明时区,不要依赖客户端本地时间推断。
- 分页顺序必须稳定。游标分页要说明游标是否可跨版本复用;偏移分页要说明排序键是否唯一,否则翻页会重复或漏项。
表单接口:先确定提交方式,再定返回#
表单接口最容易出错的地方不是字段,而是返回类型。application/x-www-form-urlencoded 与 multipart/form-data 都可能被浏览器原生提交,也可能被 fetch 提交,两者的合理响应不同:
- 原生提交:处理成功后返回
303 See Other加Location,把浏览器导向结果页,避免刷新时重复提交。失败时同样跳回表单页并带上错误标识。 - 脚本提交:返回 JSON,结构与前一节的信封一致,错误同样落在
error对象里。
区分方式建议用显式的请求头(如 Accept)或查询参数,不要靠 User-Agent 猜——它既不稳定,也无法覆盖服务端调用。
涉及文件上传的 multipart 请求,需要提前定义“部分成功”的语义:是整体原子失败,还是逐项返回结果。两种都可以,但必须写清楚,否则客户端无法判断是否需要重传。若要表达部分成功,逐项结果里应带各自的 code,而不是一个笼统的布尔值。
图片与二进制接口:正文就是负载#
图片接口返回的正文是字节流,因此不能套用 JSON 信封。可用的做法是:
- 成功时
Content-Type与实际格式严格一致(例如image/png),并给出Content-Length或使用分块传输。 - 失败时返回非 2xx 状态码,并把
Content-Type切换为application/problem+json之类的错误类型。客户端应先判断状态码,再决定按图片还是按 JSON 解析。 - 给出
ETag或Last-Modified,让客户端可以条件请求;如果同一路径会按尺寸、格式、质量参数返回不同内容,用Vary表明区分维度。
一个常见坑:缺少尺寸参数时静默返回原图。原图可能体积很大,客户端以为拿到了缩略图。建议要么给出明确的默认值并写在文档里,要么返回 400。
三类的共同点与冲突点#
| 维度 | JSON 接口 | 表单接口 | 图片接口 |
|---|---|---|---|
| 首层结构 | 统一信封 | 依提交方式而定 | 直接返回负载 |
| 错误表达 | 非 2xx + error 对象 | 跳转或 JSON 信封 | 非 2xx + 切换 Content-Type |
| 可缓存性 | 通常不缓存 | 通常不缓存 | 依赖 ETag / Cache-Control |
| 重试判断 | 看状态码与 code | 需防重复提交 | 幂等,可直接重试 |
可以共用的部分包括 request_id(便于跨接口串联日志)、状态码语义,以及“业务码只增不改”的版本纪律。冲突的部分集中在正文形态:一旦决定图片接口返回二进制,就不能再要求它同时返回统一信封,只能靠状态码分层。
取舍建议:先定“正文是什么”,再定“错误怎么表达”。顺序反了,客户端就会写出两套解析路径。" }