干这行这么多年,我见过无数API。有些API像五星级酒店的门童——礼貌、高效、让你如沐春风;而有些API呢?像你那个永远不接电话的房东,沟通起来能把你逼疯。
今天我不打算教你怎么设计「还不错」的API,那太无聊了。我想聊聊那些让人血压飙升的反人类设计,以及怎么避开它们。毕竟,看别人踩坑比自己踩坑有趣多了。
一、RESTful?那是个「建议」,不是法律
有些人把RESTful奉为圭臬,恨不得把所有URL都整成名词复数形式。我见过一个团队,为了追求「纯粹的RESTful」,硬是把一个简单的用户状态更新做成了:
PUT /users/123/status
Body: { "status": "active" }
看起来挺标准对吧?但问题是,他们同时还有:
POST /users/123/activate
POST /users/123/deactivate
POST /users/123/suspend
GET /users/123/status
兄弟,你这叫「过度RESTful综合症」。URL里塞满了动词和名词的混合物,GraphQL都能笑出声来。
我的观点是:RESTful是个好风格,但它是为你的业务服务的,不是你业务的统治者。当一个简单的POST请求能解决问题时,别为了「符合规范」硬生生拆成四个endpoint。代码是给人看的,不是给lint工具看的。
二、错误处理:崩溃不是选项,是惊喜
让我问你一个问题:你的API报错了,返回什么?
如果你的答案是「500 Internal Server Error」,那我要恭喜你——你正在把用户的血压往死里整。
一个体面的错误响应应该是这样的:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "邮箱格式不正确",
"details": [
{
"field": "email",
"message": "缺少@符号"
}
],
"request_id": "req_abc123"
}
}
注意这个request_id。这玩意儿太重要了。用户报障的时候,你不需要让他截屏、录屏、描述复现步骤——一个request_id就能让你在日志里直接定位到问题。你知道这能省下多少扯皮的时间吗?
我曾经对接过一个第三方支付API,他们的错误永远只有两种:200成功,其他全TM是「系统繁忙」。我TM怎么知道是参数错了还是服务器炸了?
三、版本管理:别让你的用户集体升天
你的API v1还在跑,突然产品经理过来说要大改架构。换你怎么办?
正确答案:不要动v1,新开v2,让用户慢慢迁移。
但总有些「勇士」选择原地爆炸——直接改v1的逻辑,导致所有现有用户在一夜之间集体故障。那场面,比《釜山行》还刺激。
版本管理的正确姿势:
# URL版本(最直观)
GET /api/v1/users
GET /api/v2/users
# Header版本(更RESTful,但调试的时候能把你逼疯)
GET /api/users
Accept: application/vnd.myapi.v2+json
URL版本虽然「不纯粹」,但它有个巨大的优点:可以直接在浏览器里测试。当你的用户在调试环境里ctrl+C/V的时候,他们会感谢你的。
四、分页:没有分页的API都是耍流氓
你写了个查询用户的API,返回所有用户。测试的时候挺好,部署到生产——服务器OOM了。
恭喜,你被「数据规模」这个老六阴了。
分页不是可选项,是生存技能。常见的三种分页方式:
// -offset分页(简单但有坑)
GET /users?offset=20&limit=10
// Cursor分页(性能好,但不支持随机跳页)
GET /users?cursor=eyJpZCI6MTAwfQ==&limit=10
// 简单页码分页(最常见,但数据量大的话COUNT很慢)
GET /users?page=3&per_page=10
如果你要做下拉刷新、无限滚动这种场景,必须用Cursor分页。offset分页在数据量大了之后会慢得像蜗牛爬楼梯,而且如果数据在分页过程中被插入或删除,你还会拿到重复或缺失的数据。
五、幂等性:重试是把双刃剑
你有没有遇到过这种情况:订单支付接口,你点了一次,没反应,再点一次——好家伙,扣了两次钱。
这就是幂等性缺失的经典案例。
所谓幂等,就是「同一个请求执行一次和执行多次,结果一样」。支付场景下,常见做法是:
// 客户端生成一个唯一的幂等Key
POST /payments
Idempotency-Key: your-unique-request-id-12345
// 服务器端根据Key做去重
// 第一次请求:正常处理,返回200
// 第二次请求:直接返回第一次的结果,不重复扣款
这个Idempotency-Key通常用UUID,客户端生成,服务器端存储(可以用Redis,设置个过期时间)。
所有涉及资金、库存、外部状态变更的操作,必须实现幂等性。不然你就等着被用户投诉淹没吧。
六、文档:没有文档的API等于没有API
这个道理所有人都知道,但做的人有多少?
我见过最离谱的文档是这样的:「接口文档请联系技术支持获取」。我去,这都什么年代了?
一个合格的API文档应该包含:
- 每个endpoint的功能描述
- 请求参数说明(类型、是否必填、取值范围)
- 响应格式和错误码说明
- 完整的请求/响应示例
- 认证方式和示例
工具方面,Swagger/OpenAPI是行业标准,用它生成的文档不仅美观,还能直接在浏览器里测试接口。别让你的用户对着Word文档猜参数类型了。
写在最后
API设计本质上是一种承诺——你告诉用户「这样调用,我保证给你那个结果」。但很多人写着写着,就把承诺变成了「免责声明」:各种边界情况不处理、错误信息语焉不详、文档和实际行为不一致。
好的API设计不是炫技,是让调用者用最少的脑细胞完成任务。每次设计一个新接口的时候,问问自己:如果我是对接这个接口的人,我会骂娘吗?
如果会,改它。
如果不会,那这篇文章没白写。