4、请求与响应设计:请求体格式(JSON/XML)、响应体结构设计、分页与排序设计、错误响应格式

好,咱们接着聊。前面我们把 URL 和 HTTP 方法定好了,但光有这些还不够。你想想看,客户端怎么把数据发给服务端?服务端又该怎么把结果和状态告诉客户端?这就是请求体和响应体要解决的问题。

我个人习惯,在设计 API 时,会把请求和响应看作一次「对话」。请求是「我要做什么,这是数据」,响应是「我做了,这是结果,或者这是错误原因」。这个对话要清晰、完整,还不能啰嗦。

4.1 请求体格式:JSON 还是 XML?

先说结论:现代 RESTful API 几乎清一色用 JSON。XML 不是不能用,但真的没必要跟自己过不去。

JSON 的优势很明显:

  • 轻量,解析快,带宽占用小
  • 与 JavaScript 天然亲和,前端处理起来极其方便
  • 可读性好,结构清晰

XML 呢?它也有自己的场景,比如 SOAP 协议、某些银行或政府系统的老接口。但如果你在做一个全新的 REST API,我建议你直接选 JSON。

核心原则:请求体只放业务数据,不要放元数据(比如版本号、签名等)。元数据应该放在 Header 里。

来看一个典型的 JSON 请求体:

{
  "username": "zhangsan",
  "email": "zhangsan@example.com",
  "age": 28,
  "tags": ["developer", "backend"]
}

简单、直接。字段名用驼峰命名(camelCase),这是 JavaScript 世界的惯例,前端后端都舒服。

小技巧:我习惯在请求体里避免使用 null。如果一个字段没有值,要么不传,要么用空字符串或空数组。null 在 JSON 解析时容易引发一些隐晦的 bug。

XML 的写法我就不多展示了,你大概知道长这样就行:

<user>
  <username>zhangsan</username>
  <email>zhangsan@example.com</email>
  <age>28</age>
</user>

嗯,看着就累。除非你的团队有强制要求,否则别碰它。

4.2 响应体结构设计:统一信封

这是很多团队容易忽略的地方。响应体如果没有统一结构,前端每次接数据都得猜:「这次返回的是数组还是对象?错误信息在哪个字段?」

我建议的做法是:设计一个统一的「信封」。所有响应都套在这个信封里。

一个典型的成功响应:

{
  "code": 0,
  "message": "success",
  "data": {
    "id": 123,
    "username": "zhangsan",
    "email": "zhangsan@example.com"
  }
}

一个典型的失败响应:

{
  "code": 1001,
  "message": "用户不存在",
  "data": null
}

这里有几个关键点:

  • code:业务状态码,0 表示成功,非 0 表示具体错误。不要跟 HTTP 状态码混为一谈。
  • message:人类可读的描述信息。成功时是 "success",失败时是具体的错误原因。
  • data:真正的业务数据。可以是对象、数组,也可以是 null。

注意:千万不要把 data 直接暴露在最外层。比如直接返回一个数组。一旦你后面需要加个分页信息,或者加个 traceId,你就得改接口结构,前端也得跟着改。统一信封的好处就是「向后兼容」。

我在项目中遇到过这样的情况:早期接口直接返回数组,后来要加个 total 字段,不得不改成对象,结果前端所有调用方都得改一遍。嗯,从那以后我再也不偷懒了。

4.3 分页与排序设计

列表接口几乎都要分页。不分页?数据量一上来,接口直接超时,或者前端页面卡死。

分页参数我推荐用 page + size 模式:

  • page:页码,从 1 开始
  • size:每页条数,建议设置默认值(比如 20),并限制最大值(比如 100)

请求示例:

GET /api/users?page=1&size=20

响应示例:

{
  "code": 0,
  "message": "success",
  "data": {
    "list": [
      { "id": 1, "username": "zhangsan" },
      { "id": 2, "username": "lisi" }
    ],
    "pagination": {
      "page": 1,
      "size": 20,
      "total": 156,
      "total_pages": 8
    }
  }
}

分页信息单独放在 pagination 字段里,不要跟 list 混在一起。这样前端拿到数据后,既知道当前页有哪些数据,也知道总共有多少页。

避坑指南:我曾经见过有人用 offset + limit 做分页。offset 在数据量大时性能很差,尤其是 MySQL 的 limit offset 实现,越往后翻越慢。page + size 虽然底层也是 offset,但更容易做优化,比如缓存、游标分页等。

排序设计也很简单:

GET /api/users?sort=created_at&order=desc
  • sort:排序字段,比如 created_at、username
  • order:排序方向,asc 或 desc

如果支持多字段排序,可以用逗号分隔:

GET /api/users?sort=created_at,username&order=desc,asc

嗯,这里要注意:排序字段一定要做白名单校验。不能让用户传一个不存在的字段,更不能让用户传一个 SQL 注入的字符串。

4.4 错误响应格式:让调用方少猜

错误响应是 API 设计中最容易被忽视的部分。很多接口报错就返回一个 500,连个错误信息都没有。前端只能猜:「是网络问题?还是参数错了?」

我建议的错误响应格式:

{
  "code": 4001,
  "message": "参数校验失败",
  "errors": [
    {
      "field": "email",
      "message": "邮箱格式不正确"
    },
    {
      "field": "age",
      "message": "年龄必须在 0-150 之间"
    }
  ],
  "trace_id": "abc-123-def-456"
}

这里多了几个字段:

  • errors:字段级别的错误详情。适合表单校验场景,前端可以直接把错误信息展示到对应输入框下面。
  • trace_id:请求追踪 ID。服务端内部可以用来查日志,排查问题。

核心原则:错误信息要「对人友好,对机可用」。message 是给人看的,errors 是给程序用的。

HTTP 状态码也要用对:

场景 HTTP 状态码 说明
成功 200 正常返回数据
创建成功 201 POST 请求创建资源后返回
参数错误 400 请求体格式不对、字段校验失败
未授权 401 没有登录或 token 过期
无权限 403 登录了但没权限操作
资源不存在 404 URL 或资源 ID 不对
服务端错误 500 服务端内部异常,不要暴露具体细节

注意:永远不要在错误响应里暴露堆栈信息、数据库表名、文件路径等内部细节。这不仅是安全问题,也是职业素养问题。我曾经见过一个接口报错直接返回了 SQL 语句,嗯,那场面……

好了,这一章的内容就这些。请求体、响应体、分页排序、错误格式,这四个点看似基础,但做好了,你的 API 质量直接上一个台阶。下一章我们聊聊认证与授权,那又是另一个大话题。