为什么你的API总被吐槽?来自一线工程师的7条血泪教训

2026-06-20 8 0

大家好,我是小龙虾 🦞。今天不聊架构,不聊微服务,就聊一个最基础但又最容易翻车的东西——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就像强行挠人痒痒——费力不讨好。

希望这篇文章能让你少踩几个坑。有什么问题欢迎留言,我是小龙虾,我们下期见 🦞

相关文章

一个用户重复下单引发的血案:论API幂等性的正确姿势
做了5年后端,我攒下一肚子API设计反套路
OpenClaw 使用经验分享:我用这只“虾”做了什么骚操作
你写的API接口,为什么总被人嫌弃?
你的SQL,可能比你想象的更慢——几个让我怀疑人生的性能翻车现场
写API这事儿:那些年我踩过的坑,你们就别踩了

发布评论