做后端开发这么多年,我最怕听到的一句话就是"这个接口很简单,先这样吧"。
每当这种时候,我就知道,完蛋了,过不了三个月,这个"简单"的接口就会变成一团乱麻,连当初写它的人都不敢动。本文就是我的血泪史,记录那些年我在API设计上犯过的蠢,以及怎么避坑。
坑一:HTTP方法乱入
刚入行那会儿,我的API是这样的:
POST /api/updateUser
POST /api/deleteUser
POST /api/getUserInfo
POST /api/listAllUsers
没错,全部POST,万事皆POST。我当时觉得这样简单啊,客户端只管发数据,服务端只管收数据,完美!
结果呢?半年后产品说要在APP上给用户加个筛选功能,后端说"简单,加个参数",前端说"这参数放body还是query?",然后就是无休止的扯皮。更要命的是,这种接口没法被CDN缓存,没法被搜索引擎索引,调试的时候抓包看一堆POST请求根本分不清哪个是哪个。
正确姿势:
GET /users # 获取用户列表
GET /users/{id} # 获取单个用户
POST /users # 创建用户
PUT /users/{id} # 更新用户(完整更新)
PATCH /users/{id} # 部分更新
DELETE /users/{id} # 删除用户
RESTful不是银弹,但它至少给你一套约定,让你的API自解释。
坑二:状态码玄学
有一段时间,我们接口返回的数据是这样:
// 成功
{"code": 200, "message": "success", "data": {...}}
// 失败
{"code": 500, "message": "服务器开小差了", "data": null}
// 参数错误
{"code": 0, "message": "用户名不能为空", "data": null}
看起来很标准对吧?但这套设计的灾难在于:HTTP状态码是200,但业务状态码是0。这意味着你的CDN、负载均衡器、监控报警系统全部失效——它们看到200就认为请求成功了,完全不知道背后还有个"业务code"。
更离谱的是,前端同事拿到{"code":0,"message":"用户名不能为空"}之后,直接alert(message),用户体验炸裂。他以为后端已经做了国际化处理,结果后端返回的中文错误信息直接展示给了海外用户。
正确姿势:
HTTP 200 OK
{"code": 0, "message": "success", "data": {...}}
HTTP 400 Bad Request
{"code": 40001, "message": "Username cannot be empty", "data": null}
HTTP 500 Internal Server Error
{"code": 50001, "message": "Database connection failed", "data": null}
记住:HTTP状态码是给基础设施看的,业务状态码是给业务逻辑看的,别把两者混为一谈。
坑三:命名随心所欲
我见过最离谱的命名是这样的:
/api/get_user_info_by_id
/api/queryUser
/api/fetchUsers
/api/loadUserData
/api/userList
同一个项目,五种不同的命名风格。这就是"先这样吧"的代价。
命名这个问题,表面上是风格不一致,实际上反映的是团队没有共识。RESTful其实给了我们很好的命名规范:资源用名词,复数形式,动作交给HTTP方法。
GET /users?status=active&page=1
GET /users/{id}/orders
POST /users
PATCH /users/{id}
很多人觉得这种规范是教条,但当你接手一个百人团队的项目时,你就知道约定大于配置这句话的分量了。
坑四:分页摸奖
早期的我处理分页是这样写的:
{"users": [...], "count": 100}
// 客户端:下一页?好,我多拿10条
GET /users?offset=10&limit=10
这套设计的问题在于:游标分页和偏移分页的适用场景完全不同。
如果你在做后台管理页面,用户可能需要跳到第100页,偏移分页没问题。但如果你的列表是实时的社交信息流,用偏移分页就会出现幻影数据——用户在第5页浏览的时候,第1页的数据被删了,他再点下一页就可能看到重复或缺失的内容。
正确姿势:
// 社交信息流,用游标分页
GET /feed?cursor=eyJpZCI6MTAwfQ==&limit=20
Response:
{
"data": [...],
"next_cursor": "eyJpZCI6ODB9",
"has_more": true
}
// 后台管理列表,用偏移分页
GET /users?page=5&per_page=20
Response:
{
"data": [...],
"pagination": {
"total": 1250,
"page": 5,
"per_page": 20,
"total_pages": 63
}
}
坑五:版本控制佛系
很多项目一开始是这样的:
/api/users
/api/user
/api/v1/users
/api/v2/users
/api/users/v2
对,五种写法同时存在于一个项目里,没人知道该用哪个,也没人敢删旧的,怕影响线上老版本。
我的教训是:API版本控制要在第一天就定好规则,不要等到线上跑了三四个版本才开始规范。
// 建议用URL路径版本,好处是明确、可控、可灰度
/api/v1/users
/api/v2/users
// Header版本适合内部服务,不适合对外开放
GET /users
API-Version: 2023-01-01
写在最后
API设计这件事,说到底是在跟未来的自己以及未来的同事对话。你今天偷的懒,明天都会变成坑。我现在的原则是:写接口之前先想这个接口会怎么变化,会被谁调用,会被怎么扩展。
如果只能给一个建议,那就是:遵守HTTP语义。它不是什么神秘力量,而是几十年来web开发经验的结晶。用GET做查询,用POST做创建,用PUT做完整更新,用PATCH做部分更新,用DELETE做删除。这些看似简单的规则,帮你规避了大多数的坑。
至于那些"简单"的接口——信我,要么它真的简单不需要规范,要么它会在某个深夜给你一个surprise。