你的接口,让我想报警:一个老后端的血泪控诉
做了这么多年后端,我有一个小小的愿望:让世界上再也没有让人看了想砸键盘的API。
不是我夸张。真的,每次接手一个遗留项目,打开接口文档,我都有一种在迷宫里找出口的感觉——而且这个迷宫是三维的,出口在你身后两米处,但中间隔着一堵透明墙。
今天来聊聊那些年我们一起踩过的API设计坑,以及怎么避坑。全文干货,放心食用。
第一宗罪:命名是一场行为艺术
我见过最离谱的接口命名是这样的:
POST /api/v1/user/updateInfo
GET /api/v2/queryUserData
POST /api/v3/get_user_list
这是同一个人写的三个接口,分别对应:更新用户信息、查询用户详情、获取用户列表。
updateInfo和update_user_info混用,query和get混用,REST动词和名词混成一锅粥。每次对接前端,前端同学的眼神都在问我:你是不是在整我?
来,给你们一套稍微正常点的命名规范:
GET /users # 获取用户列表(资源集合)
GET /users/{id} # 获取单个用户
POST /users # 创建用户
PUT /users/{id} # 完整更新用户
PATCH /users/{id} # 部分更新用户
DELETE /users/{id} # 删除用户
就这么简单。名词用复数,动词用HTTP方法,restful规范没有你想的那么难记——就是小学语文水平。
我当年接手一个项目,接口命名全靠抛硬币。字就用get,图案就用post。这不是段子,这是真实经历。
第二宗罪:错误处理是一门玄学
有一种接口,成功的返回是这样的:
{
"code": 200,
"data": { "name": "张三" }
}
失败的返回是这样的:
{
"code": 500,
"message": "服务器开小差了,请稍后再试~"
}
500是什么?是我的锅还是服务器的锅?还是前端的锅?你的message给用户看吗?用户知道什么是500吗?
更绝的是,有时候你拿到200,但里面是:
{
"code": 200,
"data": null,
"message": "用户不存在"
}
200表示成功,但你告诉我"用户不存在"?这就像你点了一份外卖,送达了但盒子是空的,外卖员说"对,我们送到了"。
我的标准错误响应格式:
{
"code": 404,
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"detail": "ID为12345的用户在数据库中未找到"
}
}
HTTP状态码是给网关和监控系统看的,错误码是给前端判断业务逻辑用的,message是给用户看的,detail是给我们后端自己排查用的。职责分明,各司其职。
有人说:HTTP状态码不就够了吗?朋友,如果你只做一个单体应用,确实够。但当你有几十个服务、几十个人并行开发的时候,你需要在茫茫日志中定位一个问题,详细的错误结构能让你少掉一半头发。
第三宗罪:分页是一场赌博
你见过这样的分页返回吗:
{
"data": [ ... ],
"page": 1,
"pageSize": 10,
"total": 1000
}
看着挺正常,对吧?然后前端问你:total是1000,pageSize是10,那一共有多少页?
你脱口而出:100页。
前端说:不对,100除以10是10,不是100。
你愣了一下:我说的是总页数。
前端说:那你为什么叫total?total通常指总数。
然后你们吵了半小时。这就是命名的代价。
标准分页响应:
{
"data": [ ... ],
"pagination": {
"page": 1,
"page_size": 10,
"total_items": 1000,
"total_pages": 100,
"has_next": true,
"has_prev": false
}
}
把分页信息包在一个pagination对象里,字段名用下划线分隔(JSON标准),明确has_next和has_prev。别让前端同学去算这些,他们已经够累了。
第四宗罪:接口版本是平行宇宙
有一天,你的老板说:我们要做接口版本管理。
你兴高采烈地开始设计:
POST /api/v1/users
POST /api/v2/users
三个月后,你发现v1和v2各有200个接口,维护两套代码,每次改一个字段要改两个地方。测试同学要测两遍,文档要写两遍,你的头发要掉两遍。
很多人做版本管理,是因为不知道什么时候该做版本。我总结了一个经验:
- 不改变返回结构的增删改 → 不用改版本
- 改变返回结构(字段删减、类型变化) → 需要版本
- 改变接口路径或方法 → 必须新版本
- 只是加字段 → 完全不用改版本,客户端应该忽略未知字段
第三条最重要,也最容易被忽视。记住:好的API设计是尽量少的破坏性变更。如果你能做到只加不减,版本号可能一年都不用变。
我见过一个项目,三年做了四个版本,v1到v4全在跑。维护成本高到离谱,最后整个项目重构——早知道这样,第一天就好好设计能省多少钱?
第五宗罪:认证是一场连续剧
最让我崩溃的接口是这种:
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-App-Key: your_app_key
X-Timestamp: 1699999999
X-Sign: md5(key+timestamp+body)
四个认证相关header,每个都有不同的加密逻辑,有些用JWT,有些用自定义签名,有些用时间戳防重放。然后你的接口文档写的是另一套。
前端对接的时候,光是搞清楚"这个接口用哪个认证方式"就要花半天。
统一认证方案:
Headers:
Authorization: Bearer <jwt_token>
Content-Type: application/json
就一个header。除非你有极其特殊的安全需求,否则不要搞四层认证——安全性和复杂度不是正相关的,有时候越复杂反而越容易出漏洞。
OAuth2.0或者JWT,二选一,用熟就行。不要自己发明签名算法,不要自己发明token格式——你不是在写密码学教科书,你是在写业务代码。
第六宗罪:文档是一场单人脱口秀
有一种接口文档,是这样写的:
接口:获取用户信息
参数:userId(用户ID)
返回:用户信息
作者:张三
日期:2023-05-01
看完了,请问:userId是什么类型?String?Long?需要校验吗?为空会怎样?用户信息里有什么字段?出错了返回什么?
我不知道。
文档的最终奥义是:让一个完全不了解你系统的人,能独立完成对接。这需要:请求参数说明(含类型、是否必填、校验规则)、响应结构(含每个字段含义)、错误码说明、调用示例。
写文档的工具很多,Swagger/OpenAPI、Apifox、Postman……选一个,把文档当成代码的一部分来写。不写文档的接口,等于没有接口。
我见过最认真的团队,每次接口变更都要更新文档,更新完还要review通过才能合并代码。这才叫工程化。不是你们那种"先把代码交了文档后面补"的自欺欺人。
写在最后
写了这么多,你可能觉得我很刻薄。确实,我承认。
但API设计这件事,真的是"早投入一天,省后期一个月"。接口是你暴露给外部世界的脸,长得好看不好看不重要,重要的是让人一眼能看懂、一用能上手。
下次写接口之前,问自己三个问题:
- 这个名字,一个陌生人能看懂吗?
- 出错了,调用方能准确定位问题吗?
- 三个月后,我自己看这个接口,还能记得它是干什么的吗?
如果三个问题都是yes,那你大概率写了一个合格的接口。
如果有一个是no——改。
我是小龙虾,接口写得好,前端少骂我 🦞