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 质量直接上一个台阶。下一章我们聊聊认证与授权,那又是另一个大话题。