做后端开发这些年,我见过最离谱的事情之一,就是一个团队花了三个月时间设计了一套「RESTful API」,结果上线第一天就被前端开发者骂了个狗血淋头。
不是功能实现不了,是接口设计得太反人类了。
今天不聊概念,就聊实战。我把这些年见过的坑、踩过的雷、悟出的道理,全部分享出来。
坑一:把HTTP方法当摆设
这是最常见的问题。很多人写接口,GET和POST混用,POST和PUT混用,DELETE根本不存在。
举个例子:
// 错误示范
POST /api/deleteUser?id=123
POST /api/updateUser
GET /api/createUser?name=zhangsan
这是把HTTP方法当空气啊。正确的做法应该是:
// 正确示范
DELETE /api/users/123
PUT /api/users/123
POST /api/users
GET /api/users/123
记住:HTTP方法是有语义的,不要为了省事就全用POST。RESTful的核心之一就是正确使用HTTP语义。
坑二:URL命名全靠拼音首字母
我曾经见过这样的接口:
/api/ckjl
/api/qtgl
/api/grxx
我不知道你什么感觉,反正我当时看到这套接口,内心是崩溃的。
URL应该是清晰的、自我描述的。拼音缩写是给中国人自己添堵。正确的做法:
/api/orders
/api/customer-care
/api/user-profile
如果你的URL需要靠注释才能看懂,那这个URL设计就是失败的。
坑三:状态码返回200,错误信息写在body里
这个问题简直是灾难级别的存在。
// 错误示范:接口明明出错了,还返回200
HTTP/1.1 200 OK
{
"code": -1,
"message": "用户不存在",
"data": null
}
200表示成功,这点毋庸置疑。如果你用了200但实际是错误,前端开发者的错误处理逻辑就会变成一坨屎。
正确的做法:
HTTP/1.1 404 Not Found
{
"code": 40401,
"message": "用户不存在"
}
或者:
HTTP/1.1 400 Bad Request
{
"code": 40001,
"message": "手机号格式不正确"
}
用正确的HTTP状态码,前端可以非常方便地做统一错误处理,不需要自己去解析你那套魔幻的code体系。
坑四:分页参数全靠约定
有些接口的分页参数是这样的:
GET /api/users?page=1&limit=20
然后另一个接口又是:
GET /api/orders?offset=0&size=10
同一个系统,两套分页约定。前端开发者每次对接新接口都要去翻文档确认参数名。
建议统一使用:
GET /api/users?page=1&page_size=20
并且在响应中明确返回总数:
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 156,
"total_pages": 8
}
}
这样前端可以统一封装一个分页组件,不用每次都重复写同样的逻辑。
坑五:版本号写URL里是耻辱
很多人喜欢这样:
/api/v1/users
/api/v2/users
不是说这种方式不行,而是它会带来很多维护上的麻烦。v1和v2并存的时候,你要同时维护两套代码。
更好的做法是:
- 接口设计初期尽量考虑周全,减少版本迭代
- 通过Header进行版本协商
- 如果必须URL版本,至少做好长期规划
坑六:过度设计,嵌套层级像俄罗斯套娃
有些程序员受HATEOAS毒害太深,写出来的接口返回数据结构是这样的:
{
"id": 1,
"name": "张三",
"orders": [
{
"id": 101,
"items": [
{
"product": {
"id": 1001,
"name": "iPhone",
"price": 9999
}
}
]
}
]
}
三层嵌套,看起来很「规范」,实际上前端拿到数据根本没法用。要么得写一堆空值判断,要么直接崩溃。
正确的做法是按需返回,或者提供fields参数让调用方指定要哪些字段:
GET /api/users/1?fields=id,name,email
GET /api/users/1?include=orders:limit(5)
最佳实践总结
说了这么多坑,总结一下我认为最重要的几个原则:
- 语义明确:HTTP方法、状态码、URL都要有明确的语义,不要让调用者猜
- 命名一致:同一套系统,命名风格要统一,参数格式要统一
- 文档先行:先写文档,再写代码。写文档的过程就是发现设计问题的过程
- 考虑调用方:接口是给前端用的,不是给自己炫技用的。多想想调用方的体验
- 错误处理要友好:错误信息要有价值,能让调用者快速定位问题
最后说两句
API设计这件事,说简单也简单,说复杂也复杂。简单在于,HTTP协议已经给你定义好了规则,你照着做就行。复杂在于,很多人连HTTP协议都没搞清楚就开始设计接口了。
下次当你准备新增一个接口的时候,先问自己三个问题:
- 这个接口的语义是什么?(查询、创建、更新、删除)
- 调用者能猜到这个接口是干什么的吗?
- 出错了,调用者能快速定位问题吗?
如果三个问题的答案都是肯定的,那这个接口设计大概率不会太差。
好了,吐槽完毕。如果觉得有用,欢迎转发。如果觉得我在胡说八道,那很正常,毕竟技术这东西,仁者见仁智者见智。