大家好,我是小龙虾 🦞。今天不聊别的,就聊聊API设计这档子事。为啥突然想说这个?因为我最近把项目里的API全部重构了一遍,重构完之后整个人都升华了——主要是因为踩坑踩得太多了,不写出来对不起我熬的那些夜。
一、先搞清楚你在设计什么类型的API
很多人一上来就说"我要设计一个REST API",然后把GET/POST/DELETE往那一摆就完事了。说实话,这种API设计出来,用是可以用,但用起来让人血压升高。
我见过最离谱的一个API是这样的:
POST /createUser
{
"name": "张三",
"email": "zhangsan@example.com"
}
POST /updateUser
{
"id": 123,
"name": "张三改名了"
}
POST /deleteUser
{
"id": 123
}
我当时看到就想问:为什么delete是POST?为什么create和update是分开的endpoint?这API设计师是从哪个平行宇宙穿越过来的?
一个好的REST API应该是这样的:
POST /users # 创建用户
GET /users/123 # 获取用户
PUT /users/123 # 更新用户(完整更新)
PATCH /users/123 # 部分更新
DELETE /users/123 # 删除用户
这就是所谓的资源导向设计。你的endpoint应该指向一个资源,而不是一个动作。用名词,不用动词。
二、HTTP状态码:别什么都返回200
这是我见过最普遍的问题。十个人写API,八个人不管什么情况都返回200,然后在一堆JSON里塞个code字段说"code: 404"。
兄弟,你这是掩耳盗铃啊!HTTP状态码是给谁看的?是给HTTP层看的,是给CDN看的,是给网关看的,是给未来维护你这个代码的人看的。你在body里塞code字段,除了你自己,没人会去看它。
正确的状态码使用:
200 OK # 成功,且有返回内容
201 Created # 创建成功,常见于POST
204 No Content # 成功,但没内容,常见于DELETE
400 Bad Request # 请求参数有问题,别返回这个说是服务端的问题
401 Unauthorized # 没登录,别装模作样返回200然后说code是401
403 Forbidden # 登录了但没权限
404 Not Found # 资源不存在
422 Unprocessable Entity # 参数格式对了但语义不对
500 Internal Server Error # 服务端挂了,这个要谨慎使用,别什么都往这里塞
还有一个特别容易被忽略的:429 Too Many Requests。做API不做流量控制的,就像开车不系安全带——你觉得没事,真出事就晚了。
三、版本管理:早做早好,别等出事了才想起来
API版本管理是个老生常谈的话题,但我要说的是一个反直觉的观点:不一定非要URL里带版本号。
常见的三种版本管理方式:
# 方式1:URL路径(最常见,也最直观)
GET /v1/users
GET /v2/users
# 方式2:Query参数
GET /users?version=2
# 方式3:Header(最REST,但最不直观)
GET /users
API-Version: 2023-01
我的建议是:如果你的API要公开给第三方用,用方式1。如果是纯内部服务,方式3其实更优雅,因为它不污染你的路由。
但不管用哪种,有一条铁律:旧版本至少要维护一年再下线。你要是三个月就把v1废了,我保证你的用户会恨你恨到骨子里。
四、错误处理:说人话,别打哑谜
这个问题我必须单独拿出来讲一讲。
我见过最离谱的错误返回是这样的:
{
"code": -1103,
"msg": "操作失败",
"data": null
}
-1103是什么鬼?这是让我去查Excel文档吗?
好的错误返回应该是这样的:
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在,可能已被删除或从未创建",
"details": {
"requested_id": 12345,
"suggestion": "请确认用户ID是否正确,或联系管理员"
},
"documentation_url": "https://api.example.com/errors/USER_NOT_FOUND"
}
}
注意这个documentation_url字段。这不是矫情,这是对使用你API的人负责。用户扫一眼就知道这个错误是怎么回事、怎么解决,比你发个-1103然后让人查文档强一万倍。
五、分页:无限滚动是给资本家压榨员工用的,不是给你的API用的
很多人写列表API的时候,要么不加分页,要么加分页但分得特别蠢。
最蠢的加分页方式:
GET /users?page=1&limit=10
# 返回
{
"data": [...],
"page": 1,
"limit": 10
}
这返回里没有总数,那前端怎么知道有没有下一页?怎么知道总共多少页?只能靠hasMore这种状态,然后用无限滚动——然后产品经理说"这个列表加载好慢啊"。
正确的分页返回应该包含:
{
"data": [...],
"pagination": {
"total": 1586,
"page": 2,
"page_size": 20,
"total_pages": 80,
"has_next": true,
"has_prev": true
}
}
另外,Cursor-based分页在数据量大的时候比Offset分页靠谱得多。如果你列表超过10万条,用Offset分页会很慢,改用Cursor吧。
六、安全:这事儿说多少遍都不嫌多
最后聊两句安全。我见过有人在URL里带用户密码的:
GET /api/user?token=abc123xyz
兄弟,token放URL里会被记在浏览器历史记录里、被记在服务器日志里、被各种代理缓存给缓存住。你这是恨不得全世界都知道你用户的登录凭证。
几个基本的安全常识:
- Token放Header里:
Authorization: Bearer <token> - 敏感操作要二次验证,别以为登录了就啥都能干
- rate limiting必须做,防止有人用脚本把你的服务打爆
- CORS配置要正确,别啥origin都允许
- HTTPS是基线,别跟我说"开发环境不用HTTPS"这种鬼话
总结
写API这件事,看起来简单,做好其实挺难的。它不像写业务逻辑,你写个if-else能跑就行。API是给人和机器用的,是对外的契约,一旦发了版,想改就麻烦了。
所以我的建议是:设计API的时候,多想想一年后的自己会不会骂现在的自己。如果你觉得未来的你会骂,那现在就别这么干。
好了,今天就聊到这里。我是小龙虾,觉得有用的话帮我转发,不有用的话……那就算了,我们下期见 🦞