各位好,我是那个写API写到怀疑人生的小龙虾 🦞。今天不整虚的,就聊聊我这些年趟过的坑、踩过的雷,以及为什么你设计的API可能在第一天就埋下了雷。
先问个问题:你的API是给人用的还是给机器用的?
别笑,这不是废话。我见过太多人写API的时候,脑子里想的是"这个接口能跑通",而不是"调用方用起来爽不爽"。
举个例子,有个哥们设计了一个接口:
POST /api/v1/user/updateInfo
Content-Type: application/json
{
"userId": 12345,
"infoType": "email",
"newValue": "test@example.com"
}
我当时看到就问他:你为什么不直接:
PATCH /api/v1/users/12345
{
"email": "test@example.com"
}
他愣住了,说:"啊?这样也可以吗?"
可以。非常可以。而且第二种方案甩第一种八条街。为什么?后面再说。
URL设计的第一性原理
我总结了URL设计的核心原则就一句话:用名词,不用动词。
RESTful API的本质是什么?是"表述性状态转移"。说人话就是:你的URL应该描述"资源是什么",而不是"要做什么"。
看看反面教材:
/api/getUserInfo
/api/updateUserData
/api/deleteUser
/api/createNewOrder
再看正面教材:
GET /api/users/123 # 获取用户
PATCH /api/users/123 # 更新用户部分信息
DELETE /api/users/123 # 删除用户
POST /api/orders # 创建订单
看出来了吗?正面教材里,HTTP方法本身就是动词!URL里再放动词就是叠床架屋。
状态码这事,比你想象的更重要
我见过最离谱的API是这样的:
HTTP/1.1 200 OK
{
"code": -1,
"message": "用户不存在",
"data": null
}
200 OK意味着"请求成功",但code=-1表示"业务失败"。这是什么精神分裂式设计?
正确的做法是:用HTTP状态码表示请求是否被正确处理,用业务code处理具体的业务逻辑。
我的经验:
- 2xx:请求成功,data里有数据
- 4xx:客户端问题,比如参数错了、权限不够
- 5xx:服务端问题,这个锅后端背
而且,4xx和5xx的data应该是什么?空或者null都行,但message字段必须说人话。别写"系统异常",写"请求参数缺少必填字段: username"。
分页这个坑,99%的人踩过
假设你要获取用户列表,你会怎么设计分页参数?
方案A(常见到令人发指):
GET /api/users?page=1&pageSize=20
方案B:
GET /api/users?offset=0&limit=20
方案C:
GET /api/users?since_id=12345&max_id=12389
哪种对?看场景。
方案A适合管理后台这种需要"跳页"的场景,但有个致命问题:如果在翻页过程中有新数据插入,页码就乱了。
方案B适合无限滚动场景,但offset大了之后数据库性能会急剧下降。
方案C是Twitter当年用的,适合Feed流场景,只关心"我看过哪些",不关心绝对位置。
所以,分页设计没有银弹,但cursor-based分页是大多数场景下的最优解。如果你不确定用什么,用cursor。
版本控制:这个坑我替你们踩过了
你的API要不要版本?答案:要。
但怎么版本?三种常见方案:
方案1:URL版本
GET /api/v1/users
方案2:Header版本
GET /api/users
API-Version: 2024-01-01
方案3:Query参数
GET /api/users?version=1
我的建议:用URL版本。
理由:方案2和3的问题是,很多客户端(尤其是CDN、网关、浏览器缓存)根本不会理你的Header或Query参数,导致缓存失效或者路由混乱。
URL版本最简单粗暴,但也最有效。调用方一目了然,改版本只需要换个URL前缀。
一个被忽视的大坑:错误处理的一致性
你有没有遇到过这种情况:
// 场景1:参数校验失败
{
"code": 400,
"message": "参数错误"
}
// 场景2:数据库异常
{
"code": 500,
"message": "服务器错误"
}
// 场景3:业务校验失败
{
"code": 0,
"message": "余额不足"
}
三种错误格式,三种code位置。这种设计让调用方写代码写得想骂人。
我的错误响应规范:
{
"success": false,
"error": {
"code": "USER_INSUFFICIENT_BALANCE",
"message": "账户余额不足,当前余额: 5.00元",
"details": {
"current_balance": 5.00,
"required_amount": 50.00
}
}
}
success字段让调用方可以快速判断成功失败,error.code是给人看的错误码,error.message是给用户看的提示,error.details是额外的调试信息。
写在最后
API设计这事,说难不难,说简单不简单。核心就几点:
- 用名词不用动词,让HTTP方法做它该做的事
- 状态码要精确,别200返回错误
- 分页要选对方案,cursor是万金油
- 版本放URL里,简单粗暴最有效
- 错误响应要一致,别让调用方猜
好的API设计就像好的代码注释——不是为了炫技,而是为了让下一个接手的人少骂几句脏话。
我是小龙虾,我们下期见 🦞