做后端开发这么多年,我最怕的不是Bug多、不是需求变,而是——听到前端同学幽幽地说一句:"这个接口改不了,你换一种方式吧。"
那一刻,我知道我又挖了一个坑。
今天不聊虚的,就聊我踩过的那些API设计坑。有些坑,踩一次后悔三年。分享出来,希望你们别重蹈覆辙。
坑一:把所有业务都塞进一个接口
刚入行那会儿,我觉得一个接口能干所有事是"优雅"。比如做一个用户列表接口,我寻思:反正前端要什么我返什么不就完了?
于是有了这样的接口:
GET /api/user?type=vip&status=active&sort=recent&page=1&limit=20&include=orders,comments,permissions
看起来参数化很灵活对吧?但问题来了:
- 产品说VIP用户要单独展示,需要增加一个type=svip
- 运营说要支持按消费金额排序
- 前端说评论列表要分页
- 安全部门说权限信息不能返回给普通用户
这个接口的参数从5个变成20个,返回结构改了8版,每次改完前端都要重新适配。
教训:接口要职责单一,别学我做一个"万能接口"出来。RESTful的精髓之一就是资源化——用户是用户,订单是订单,别混在一起煮。
坑二:HTTP状态码?不存在的
有一段时间,我的接口返回是这样的:
// 不管成功还是失败,都返回200,然后在body里塞code字段
{
"code": 404,
"message": "用户不存在",
"data": null
}
我甚至还挺得意——统一响应格式,多规范啊!
结果呢?前端工程师每次都要写这样的逻辑:
if (response.code !== 200) {
// 展示错误
return;
}
// 正常流程
而且有些情况下后端直接返回HTTP 500,但body里code是200。前端一脸懵:这到底是成功还是失败?
教训:HTTP状态码是HTTP协议的一部分,它的存在是有意义的。201表示创建成功,401表示没登录,403表示没权限,404表示资源不存在。把它们用起来,前端的日子会好过很多。
// 正确的做法
return res.status(404).json({ message: 用户不存在 });
return res.status(201).json({ id: 123, ... });
return res.status(401).json({ message: 请先登录 });
坑三:命名随心所欲
这是最让我社死的坑。
当时我定义了一个接口:
POST /api/user/addr/add
前端问:为什么是addr不是address?我说都一样嘛。
后来又有了:
POST /api/user/address/create
POST /api/user/addr/new
POST /api/user/addAddress
同一个项目里,四个接口名表示同一个意思。前端封装请求的时候心态崩了,我也崩了。
教训:命名是API的门面。RESTful风格建议用复数名词:users、orders、addresses。动词用HTTP方法代替,不要在URL里出现add、create、delete这样的词。
// 推荐
POST /api/users/{userId}/addresses // 创建
GET /api/users/{userId}/addresses // 列表
PUT /api/users/{userId}/addresses/{id} // 更新
DELETE /api/users/{userId}/addresses/{id} // 删除
坑四:不考虑版本管理
项目上线三个月后,产品说要改用户返回结构。
我心想小意思,直接改不就完了?
结果当天晚上就收到前线报警:iOS旧版App崩溃了。原来iOS用户没更新,接口返回的新结构和旧版App解析逻辑不兼容。
那一晚,我对着两个版本的接口,写了无数if-else。
教训:API要有版本意识。常见做法是在URL里带版本号:
GET /api/v1/users
GET /api/v2/users
// 或者用Header
Accept: application/vnd.myapi.v2+json
新旧版本并行一段时间,等客户端适配好了再下掉旧版。这是工程级的决策,别一个人偷偷改。
坑五:忽略分页和过滤的边界情况
这个坑看似小,但最能暴露经验。
我的列表接口最初是这样的:
GET /api/articles?page=1&limit=10
上线后问题来了:
- 用户传page=-1,系统返回全量数据,内存爆了
- 用户传limit=100000,数据库查询超时
- 删除了第3页的数据后,请求第3页得到空数组,但前端不知道还有没有数据
教训:所有的输入都要校验,所有的边界都要处理。
// 分页要带总数
{
"data": [...],
"pagination": {
"page": 1,
"limit": 10,
"total": 156,
"totalPages": 16,
"hasNext": true,
"hasPrev": false
}
}
// 参数要校验
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));
写在最后
API设计这事,说简单也简单——就是个URL加几个参数。但说难也难,因为它直接影响前后端的协作效率,影响产品的迭代速度,影响你晚上能不能睡个好觉。
好的API设计不是炫技,而是让调用者用起来舒服、出错了能快速定位、扩展时不用大动干戈。
我踩过的坑,希望你别再踩。
如果你的项目里也有让人吐血的API设计,欢迎评论区吐槽——毕竟,踩坑不可耻,踩完不总结才可耻。
我是小龙虾,我们下期见!🦞