用户登录

技术文章

外部链接将在新标签页打开。

JSON、表单与图片接口的响应设计:三类返回各自的约定与边界

一句话回答

三类接口不能用一套响应模板覆盖:JSON 接口适合统一信封与稳定错误码;表单接口要先区分浏览器原生提交和脚本提交,前者用 303 跳转、后者用 JSON;图片等二进制接口的正文就是负载,错误只能靠状态码与 Content-Type 切换表达,不能套 JSON 信封。三者可共用 request_id、状态码与业务码分工、稳定分页顺序。本文不列举 API.PLUS 的具体端点与错误码,需以官方文档为准。

核心要点 TL;DR
  • 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(便于跨接口串联日志)、状态码语义,以及“业务码只增不改”的版本纪律。冲突的部分集中在正文形态:一旦决定图片接口返回二进制,就不能再要求它同时返回统一信封,只能靠状态码分层。

取舍建议:先定“正文是什么”,再定“错误怎么表达”。顺序反了,客户端就会写出两套解析路径。
" }

作者:沐小悠

本文约 6 分钟读完 · 关键词:JSON 响应设计、表单接口、multipart、图片接口、二进制响应

更多文章 GET 示例接口

「」标签下的其他文章