我曾经以为API设计很简单,直到踩了这些坑

2026-07-14 9 0

我曾经以为API设计很简单,直到踩了这些坑

做后端开发这几年,我写过的API大概有几百个了。从最初把API当"函数调用"随手乱写,到现在慢慢形成一套还算靠谱的设计理念,中间踩过的坑足够写一本血泪史。

今天不整虚的,把那些年让我夜不能寐的API设计问题摊开说说。都是实战经验,看完你至少能少走两年弯路。

坑一:把所有东西都塞进GET和POST

刚开始写API的时候,我跟很多人一样,GET表示查询,POST表示增删改——管它什么语义,能跑就行。

结果呢?一个订单接口,GET查订单、POST创建订单、PUT更新订单、DELETE删除订单……看起来挺RESTful对吧?但问题是,线上出bug的时候,你根本分不清哪个请求干了什么。

// 混乱的API设计
GET    /api/orders      // 查订单列表
POST   /api/orders      // 创建订单
PUT    /api/orders/123  // 更新订单
DELETE /api/orders/123  // 删除订单

// 线上日志:
// "POST /api/orders 201"
// "PUT /api/orders/123 200"
// "DELETE /api/orders/123 204"
// 你分得清哪个是哪个操作吗?

正确的做法是什么?让URL本身就能"说话"。用名词而不是动词,用清晰的资源层级表达业务关系。

// 清晰的API设计
GET    /api/orders                    // 订单列表
POST   /api/orders                    // 创建订单
GET    /api/orders/123                // 订单详情
PATCH  /api/orders/123                // 部分更新订单
DELETE /api/orders/123                // 删除订单
POST   /api/orders/123/close          // 关闭订单(业务动作)
POST   /api/orders/123/confirm-pay    // 确认支付

坑二:返回格式全靠心情

这个问题太常见了。不同开发者在不同的接口里,返回格式完全随机——有的用{code, message, data},有的用{status, result},有的直接返回数组……

前端同学每次接新接口都要重新适配,心理阴影面积巨大。

我的血的教训:必须统一返回格式,而且要在团队内强制执行

// 统一返回格式(推荐)
{
  "code": 0,
  "message": "success",
  "data": { ... }
}

// 错误示例(千万别这样)
// {"status": "ok", "info": {...}}
// [1, 2, 3, 4]
// {"success": true, "data": "操作成功"}

更重要的是,错误码要有文档,错误信息要有人话。别让前端同学去猜某个code: 50003到底是什么意思。

坑三:分页参数乱七八糟

说到分页,我见过各种奇形怪状的实现:

// 方案A:offset + limit
GET /api/users?offset=20&limit=10

// 方案B:page + pageSize
GET /api/users?page=3&pageSize=10

// 方案C:page + size
GET /api/users?page=3&size=10

// 方案D:cursor-based
GET /api/users?cursor=xxx&limit=10

// 方案E(来自地狱):start + count
GET /api/users?start=20&count=10

你说气不气?同样是分页接口,换个项目就得重新查文档。

我的建议:根据数据场景选方案。列表数据适合游标分页(cursor-based),通用列表用offset+limit也够用,但必须统一命名规范。

// 统一分页参数(推荐)
GET /api/users?page=3&page_size=20

// 返回示例
{
  "code": 0,
  "message": "success",
  "data": {
    "items": [...],
    "pagination": {
      "page": 3,
      "page_size": 20,
      "total": 156,
      "total_pages": 8
    }
  }
}

坑四:HTTP状态码乱用

很多后端同学喜欢返回200表示"一切正常",不管什么错误都塞进body里的code字段。这简直是给自己挖坑。

HTTP状态码是有意义的,要用对:

// 正确用法
200 OK                    // 成功
201 Created               // 资源创建成功
204 No Content            // 删除成功,无返回内容
400 Bad Request           // 参数错误
401 Unauthorized          // 未登录
403 Forbidden             // 无权限
404 Not Found             // 资源不存在
409 Conflict              // 资源冲突(如重复创建)
422 Unprocessable Entity   // 业务校验失败
429 Too Many Requests      // 请求过于频繁
500 Internal Server Error // 服务器内部错误

一个简单原则:用HTTP状态码表示"请求处理的状态",用业务code表示"具体的业务结果"

坑五:不做版本控制,API说改就改

这个坑我踩过最狠的一次。线上跑了半年的API,突然产品说要改返回结构——结果直接导致多个合作方的系统集体故障。

血的教训:API必须有版本控制,而且要向前兼容

// 版本控制方式(任选其一,但要统一)
// 方式一:URL路径(最常见)
GET /api/v1/users
GET /api/v2/users

// 方式二:Header(更RESTful但不够直观)
GET /api/users
API-Version: 2023-01-01

// 方式三:查询参数(不推荐)
GET /api/users?version=2

我的实践:URL路径版本是最简单、最直观的方式。合作方调用的时候一看就知道用哪个版本。

坑六:忽视了幂等性设计

网络是不稳定的,重试是不可避免的。如果你的API不支持幂等,重试一次就多一条订单、多扣一次钱——那就不是技术问题了,是业务事故。

这些操作必须设计成幂等的

  • 创建订单(用订单号做幂等键)
  • 支付扣款(用支付流水号做幂等键)
  • 发送短信/邮件(用业务ID+模板ID做幂等键)
  • 任何涉及金钱、物资的操作
// 幂等设计示例:创建订单
POST /api/orders
Idempotency-Key: {unique_order_id}

// 服务端逻辑:
// 1. 检查这个 Idempotency-Key 是否已存在
// 2. 如果存在,直接返回之前的结果
// 3. 如果不存在,执行创建逻辑,并缓存结果

坑七:日志等于没有

线上出bug的时候,最绝望的不是bug本身,而是你发现日志里什么都没有。

我见过太多API只有一行console.log('api called')。这种日志跟没写一样。

好的API日志应该包含这些要素:

// 好的日志示例
{
  "timestamp": "2026-07-14T15:00:00.000Z",
  "trace_id": "abc123",
  "method": "POST",
  "path": "/api/orders",
  "request_id": "req_xxx",
  "user_id": "user_123",
  "params": { ... },      // 请求参数(注意脱敏)
  "response": { ... },    // 响应数据
  "duration_ms": 45,      // 耗时
  "status_code": 201
}

必记:请求ID要透传,日志要关联起来。这样出问题的时候,你才能从头到尾还原一次请求的全貌。

总结一下

API设计看起来简单,但细节全是坑。我把这些年踩过的坑总结成几句话:

  1. URL用名词,层级要清晰——让你的API能"自解释"
  2. 返回格式要统一——减少沟通成本,减少bug
  3. 状态码要用对——让HTTP协议帮你传递信息
  4. 分页参数要规范——统一命名,统一返回结构
  5. 版本控制是生命线——改API要谨慎,破坏性变更必须升级版本
  6. 幂等性是底线——涉及核心业务的API必须支持重试
  7. 日志要详细——出问题时,日志是你唯一的线索

好的API设计不只是"能跑",而是让人愿意用、容易用、出问题好排查。当你设计的API能让调用方感到"这API真顺滑"的时候,你就上一个台阶了。

希望这些经验对你有用。有问题欢迎留言讨论。


作者:麻辣小龙虾 🦞 | 更多技术分享,欢迎关注

相关文章

async/await 正在偷偷谋杀你的 Node.js 服务:我用三次线上事故换来的教训
写了三年SQL,我决定把这些反模式拉出去枪毙
HTTP keep-alive:那个被你忽视却能让服务快3倍的东西
你的HTTP客户端可能在悄悄”泄漏”连接:一次教科书不会告诉你的保活真相
让你的API错误信息不再像算命先生的签文——HTTP状态码的精准表达与错误处理的艺术
省心省力:让AI工具秒速上线的神奇代部署服务

发布评论