做了这么多年后端,我有一个不太敢当面说的观点:大多数后端程序员写的API,本质上是一种对前端同事的心理攻击。
你可能不服气。"我写的接口功能齐全,文档完善,返回结构清晰,怎么就成心理攻击了?"
别急,听我说完。
一、你的"统一响应结构"可能是最大的谎言
打开任何一个后端项目的代码,十有八九能看到这样的响应结构:
{
"code": 200,
"message": "success",
"data": {
// 实际数据
}
}
看起来很标准对吧?大错特错。
这个结构的致命问题在于:它把业务错误和HTTP状态码的语义混淆了。
当你的接口返回{ "code": 401, "message": "未登录" },同时HTTP状态码是200的时候,前端程序员必须同时解析两个地方才能判断请求是否成功。这不是"统一",这是双重标准。
真正的统一应该是:
- 业务成功 + HTTP 200
- 未登录 + HTTP 401 + 专门的error body
- 参数错误 + HTTP 400 + 详细的校验信息
- 服务器错误 + HTTP 500 + error_id(方便排查)
不要再用code: 200表示成功了。HTTP状态码本来就是干这个用的,你不用,它就变成了摆设。
二、分页接口:你知道前端拿到的是什么吗?
90%的分页接口是这样的:
{
"data": [...],
"total": 1234,
"page": 1,
"pageSize": 20
}
看起来很完整。但问题来了:前端其实需要的是渲染分页器,而你只给了总数和当前页。
这意味着前端要自己算:
- 总页数 = Math.ceil(total / pageSize)
- 要不要显示"省略号"
- 当前页是不是第一页/最后一页
- 如果page=3,总共10页,应该显示哪些页码
这些计算逻辑在前端代码里散落得到处都是,而且很容易出bug。更糟糕的是,如果哪天产品说"要显示前五页和后五页",你得改多少个前端页面?
正确的做法是:后端直接返回前端需要渲染的分页器数据。
{
"items": [...],
"pagination": {
"currentPage": 3,
"totalPages": 62,
"hasPrev": true,
"hasNext": true,
"pages": [1, "...", 2, 3, 4, 5, "...", 62]
}
}
这才是真正的服务前端。后端多做一点,前端少崩一点。
三、时间字段:你的格式暴露了你的不专业
我见过最离谱的时间格式:
"createTime": "2024-03-15 16:23:45"
"update_time": "2024/03/15 16:23"
"timestamp": 1710499425
同一个项目里,三种不同格式。这是对前端的酷刑。
标准做法只有两种:
- ISO 8601:
"2024-03-15T08:23:45Z"——给需要可读性的场景 - Unix时间戳(毫秒):
1710499425000——给需要计算的内部场景
不要用字符串时间戳,不要用yyyy-MM-dd HH:mm:ss,更不要用秒级时间戳然后假装是毫秒级。
还有,所有时间字段必须明确时区。UTC还是本地时间,必须在文档里写清楚。如果你的系统有时区bug,十有八九是从这个坑开始的。
四、列表接口不返回ID:你让前端怎么玩?
这个问题我遇到太多次了:
// 返回的用户列表
[
{ "name": "张三", "age": 25 },
{ "name": "李四", "age": 30 }
]
// 前端想点进详情页,需要用户的ID...
// 不好意思,没返回
// 不好意思,这个接口不管这个
// 你去调另一个接口查
// 或者,你传name过去,后端模糊匹配?
// ???
列表接口必须返回主键ID。这是常识,但几乎每做一个新项目都会遇到这个问题。
原因往往是后端觉得"列表不需要ID,详情才有",或者是"怕ID泄露"。前者是设计失误,后者是安全感作祟——一个数据库自增ID而已,有什么好藏的?
五、错误信息:请给你的用户留点尊严
最经典的错误返回:
{
"code": 500,
"message": "系统异常"
}
或者更离谱的:
{
"code": -1,
"message": "操作失败请联系管理员"
}
// 管理员OS: 又是哪个接口报错了?不知道...
// 开发: 哪个接口?
// 管理员: 不知道,错误信息就写了"操作失败"
// 开发: ...
好的错误响应应该包含:
- 错误码(业务层面的,区别于HTTP状态码)
- 用户友好的中文提示(这个提示是给普通用户看的)
- 技术层面的详细信息(只有开启debug模式才返回)
- 请求追踪ID(方便定位日志)
{
"error": {
"code": "VALIDATION_FAILED",
"message": "手机号格式不正确",
"details": [
{ "field": "phone", "issue": "invalid_format" }
],
"requestId": "req_abc123",
"docs": "https://api.example.com/docs/errors/VALIDATION_FAILED"
}
}
六、写在最后
我发现一个规律:后端API的质量,直接反映了这个团队前后端的协作关系。
如果API总是被前端吐槽,大概率是因为后端在设计接口时只考虑了自己的数据模型,完全没想过前端怎么用。如果一个接口设计能让前端用起来舒服,那它大概率也经过了后端的认真思考。
所以,下次写接口之前,问自己三个问题:
- 这个接口是为调用方设计的,还是为自己图方便设计的?
- 如果我要写前端调这个接口,我会骂人吗?
- 这个错误信息,能让前端定位到问题吗?
如果你能诚实地回答这三个问题,你的API质量至少能超过50%的同行。
好的API不是功能的堆砌,而是让调用者用得爽的设计。
共勉。