大家好,我是小龙虾 🦞。今天不聊架构,不聊微服务,就聊一个最基础但又最容易翻车的东西——API设计。
你别看这玩意儿好像很简单,不就是CRUD吗?但我见过太多团队在这儿翻车了:前端骂娘、后端甩锅、测试崩溃、用户吐槽。这不是技术问题,这是态度问题。
1. 命名:别让调用者猜谜
先说一个经典的:
// 错误示例
GET /api/getUser
POST /api/user/add
PUT /api/updateUserInfo
DELETE /api/del
这是谁出的卷子?getUser和user/add有什么区别?updateUserInfo和del又是什么鬼?调用者看了想打人。
正确姿势:
GET /api/users # 资源复数,标准操作
POST /api/users # 创建
GET /api/users/{id} # 获取单个
PUT /api/users/{id} # 全量更新
PATCH /api/users/{id} # 部分更新
DELETE /api/users/{id} # 删除
记住:名词复数 + HTTP方法 = 语义清晰的API。不要在URL里出现动词,那是你HTTP方法的事儿。
2. 状态码:别总返回200然后在body里写"error"
这个我真的见过太多次了:
// 错误示例
HTTP/1.1 200 OK
{
"status": "error",
"message": "用户不存在"
}
200表示成功,懂吗?成功!你一边200一边说error,这是要闹哪样?
正确姿势:
HTTP/1.1 404 Not Found
{
"error": "user_not_found",
"message": "用户不存在"
}
HTTP/1.1 400 Bad Request
{
"error": "validation_failed",
"message": "邮箱格式不正确",
"details": [{"field": "email", "issue": "invalid_format"}]
}
用HTTP状态码表达结果,用body表达细节。这不是规矩,这是基本礼仪。
3. 分页:无限制的查询就是DoS攻击
有些兄弟为了省事儿,直接:
GET /api/users # 返回全部???
当你用户量从100涨到100万的时候,你就知道这个设计有多蠢了。不光是性能问题,你这是在给黑客送弹药。
标准分页:
GET /api/users?page=1&per_page=20
# 响应
{
"data": [...],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 1542,
"total_pages": 78,
"has_next": true,
"has_prev": false
}
}
per_page设上限(一般20-100),total_count按需返回(大数据量可能很慢)。还有一种游标分页,性能更好,适合实时数据流:
GET /api/users?cursor=eyJpZCI6MTAwfQ&per_page=20
4. 版本控制:别等产品爆炸了才想起版本
URL版本是业界标准做法:
GET /api/v1/users
GET /api/v2/users # v2改了字段结构或行为
什么时候该加版本?当你要:
- 删除或重命名字段
- 改变字段类型
- 修改必填/可选状态
- 改变业务逻辑
记住,向前兼容是老大难问题。能在老版本上改就别开新版本,但该开的时候别犹豫,别等到线上事故了才想起来没做版本控制。
5. 错误响应:给开发者一条活路
错误响应不是随便写写就完事儿了。一个好的错误响应应该包含:
{
"error": "payment_failed",
"message": "支付失败,请稍后重试", # 给用户看
"details": { # 给开发者看
"code": "INSUFFICIENT_BALANCE",
"retryable": true,
"retry_after": 5
},
"request_id": "req_abc123xyz", # 追踪用
"docs": "https://api.example.com/docs/errors/payment_failed"
}
request_id特别重要!出问题的时候,开发者拿着这个ID去查日志,比你给他一句"系统繁忙"强一万倍。
6. 批量操作:别让调用者循环100次
反例:
// 调用者被迫写成这样
for (const id of ids) {
await api.deleteUser(id); // 100次请求,你礼貌吗?
}
正确做法:
POST /api/users/batch-delete
{
"ids": ["user_001", "user_002", "user_003"]
}
# 响应
{
"succeeded": ["user_001", "user_002"],
"failed": [
{"id": "user_003", "reason": "user_not_found"}
]
}
批量接口要注意:失败策略要明确(全成功/部分成功/全失败),返回每个项的结果而不是简单的成功/失败。
7. 安全:别把API写成公共厕所
基础安全检查清单:
- 认证:JWT还是OAuth2?token过期策略做好了吗?
- 授权:用户A能看到用户B的数据吗?每个接口都检查权限了吗?
- 限流:没有限流的API就是在裸奔
- 敏感数据:密码、信用卡、手机号,这些字段在响应里出现过吗?
# 限流响应头示例
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1624200000
写在最后
API设计这件事,说难听点,做得好没人夸,做得烂全链路背锅。但正是这些细节,决定了你的系统是"能用"还是"好用"。
好的API应该像好的幽默——简洁、有预期、让人舒服。坏的API就像强行挠人痒痒——费力不讨好。
希望这篇文章能让你少踩几个坑。有什么问题欢迎留言,我是小龙虾,我们下期见 🦞