你的REST API正在默默杀人:五个让前端想砍死你的设计
我见过太多后端写的API了。有些API,用起来像在吃玻璃渣;有些API,美得像艺术品。前者的作者通常觉得自己在"写接口",后者知道自己是在"设计契约"。
今天不教你背规范,不讲那些网上抄来的人云亦云。就说点大实话,关于那些让前端开发者深夜对着屏幕发呆的烂设计。
一、HTTP状态码?你根本不懂
99%的国内项目,接口返回是这样的:
{
"code": 0,
"message": "success",
"data": [...]
}
然后所有的错误处理都写成:
if (res.code === 0) {
// 好
} else {
alert(res.message);
}
问题在哪? 你把HTTP协议当摆设。201 Created返回code: -1,404 Not Found返回code: 0配message: "用户不存在"。这是什么?这是对HTTP的状态码体系的侮辱
正确做法:
// 用户创建成功 -> 201
POST /users
Location: /users/12345
// 用户不存在 -> 404,Body里给业务细节
HTTP/1.1 404 Not Found
{"error": "user_not_found", "detail": "uid 12345 does not exist"}
// 参数校验失败 -> 422
HTTP/1.1 422 Unprocessable Entity
{"error": "validation_failed", "fields": {"email": "invalid format"}}
这样前端拿到响应,第一时间就能从协议层面知道出了什么问题,而不是傻乎乎地去解析你那个蹩脚的code枚举。
二、分页:重灾区中的重灾区
你见过多少种分页方式?limit/offset、page/pageSize、cursor/token……每种都有人用,每种都有人用错。
最蠢的设计:
GET /articles?page=1&pageSize=20
然后返回:
{
"data": [...],
"total": 1000,
"page": 1,
"pageSize": 20
}
为什么蠢?因为假分页。你让前端传page=1,后端默默count(*)查了全表,然后skip了0条取20条。这在大数据量下是灾难。
更蠢的是cursor分页写成这样:
GET /articles?cursor=eyJpZCI6MTIzfQ&limit=20
返回:
{
"data": [...],
"nextCursor": "eyJpZCI6MTQzfQ", // 这啥?
"hasMore": true
}
nextCursor是个base64 JSON,里面有id、时间戳、排序字段。前端问:这玩意儿怎么解析?后端答:别解析,直接传回来就行。前端内心:……
建议:要么不用cursor,要用就透明。Cursor里有什么字段,在文档里写清楚,或者直接暴露字段:
GET /articles?after_id=123&limit=20
干净利落,前端看得懂,不用在那猜测你base64里藏了什么秘密。
三、嵌套资源:别让你的URL变成文件系统
见过这种URL吗?
GET /orgs/123/depts/456/teams/789/members/101
五层嵌套。这不是REST,这是拍脑袋。
资源嵌套是有道理的,但不是无限制地嵌套。超过两层,就要问自己:我是不是在用URL表达关系,而不是在暴露资源?
更好的做法:
GET /members/101?include=team.dept.org
或者:
GET /members/101?fields=id,name,team{id,name,dept{id,name}}
用查询参数控制返回内容的深度,URL保持扁平。这才是正确的API设计思维。
四、命名:你在侮辱程序员的智商
看看这些真实存在的字段名(我发誓不是我编的):
- userName、username、user_name、UserName——四个接口四种写法
- createTime、addTime、addtime、ctime——猜猜哪个是你的?
- isDelete、isDeleted、deleted、deleteFlag——删了还是没删?
- status状态枚举值:0/1、"正常"/"禁用"/"pending"/"PENDING"——全靠蒙
命名不一致是API的癌症。它不会立刻杀死你,但它会在整个项目生命周期里慢慢放血。
我的原则:蛇形还是驼峰,提前定好,全局执行。 推荐用蛇形(user_name),因为JSON里标准就是下划线,JavaScript那边转驼峰(userName)只是一行代码的事。
枚举值用字符串,不要用数字。数字写进数据库没人看得懂,字符串写进日志一眼就知道是什么状态。
五、版本管理:你的v1可能杀死你
大多数项目的版本管理是这样的:
GET /api/v1/users
GET /api/v2/users
v1和v2有什么区别?不知道。什么时候升v2?不知道。v1什么时候下线?不知道。
结果就是:团队永远不敢动v1,因为不知道哪个客户端在用。v2变成了另一个独立的接口,和v1共享0%的代码,维护两套完全等价的逻辑。
我的建议:
- 不要用URL版本(/v1/、/v2/),用Header:
Accept: application/vnd.myapi.v2+json - 有明确的废弃政策:v1至少维护X年,提前Y个月通知下线
- 小改动不升版本:加了字段不算breaking change,不用新版本
URL版本是最偷懒的版本管理方案,代价是长期的维护噩梦。
写在最后
好的API设计不是"遵循规范"那么简单。规范是底线,不是天花板。真正的好的API是让调用者用得舒服、看得明白、改得放心。
每次你写一个新接口,问自己三个问题:
- 我需要查文档才能调用这个接口吗?(如果需要,说明设计失败了)
- 这个接口的响应,以后能加字段吗?(如果不能,说明你的设计缺乏扩展性)
- 一年后我自己来维护这个接口,会骂自己吗?
如果第三个问题的答案是"会",那你今天就该重构它。
别让你的API成为团队最想删掉的代码。