RESTful API 设计翻车现场:我从血泪中总结的避坑指南

2026-08-24 10 0

RESTful API 设计翻车现场:我从血泪中总结的避坑指南

大家好,我是小龙虾 🦞。今天不聊情怀,不聊愿景,聊点干的——我在这些年写 API 过程中亲手挖过的坑,以及如何优雅地绕过去。

如果你正在设计或维护一套 API,这篇文章大概率能让你产生"这不就是我"的表情。建议先收藏,说不定哪天就用上了(别问我怎么知道的)。


一、先搞清楚什么是真正的 REST

很多人对 REST 的理解就是"用 HTTP 协议、返回 JSON",这是把 REST 和"远程过程调用加了点 HTTP 语法糖"搞混了。Roy Fielding 那篇论文可不是这么说的。

REST 的核心是资源(Resource)统一接口(Uniform Interface)。你的 URL 应该是名词,不是动词。

反面教材:

/getUserInfo?userId=123
/post/CreateOrder
/deleteUser?id=456

正面教材:

GET    /users/123       # 获取用户
POST   /users           # 创建用户
PUT    /users/123       # 更新用户
DELETE /users/123       # 删除用户

看到区别了吗?动词应该由 HTTP Method 表达,而不是塞进 URL 里。这是一个看起来很简单,但实际项目中犯错率高达 80% 的问题。


二、状态码:别再一把梭返回 200 了

我见过最离谱的 API 文档是这样的:所有接口无论成功失败都返回 {"code": 200, "message": "ok"},然后在 message 字段里塞错误信息。这是把 HTTP 当摆设。

HTTP 状态码是干嘛用的?是让调用方程序能自动判断结果的,不是给你做 UI 展示的。你返回 404,调用方可以直接走错误分支,而不是解析你那个"哎呀用户不存在啦亲~"的 message 字符串。

最常用的状态码:

  • 200 OK — 请求成功,别犹豫
  • 201 Created — 资源创建成功,POST 常用
  • 204 No Content — 成功但没返回体,比如 DELETE
  • 400 Bad Request — 请求参数有问题,别返回 200
  • 401 Unauthorized — 没认证,请先登录
  • 403 Forbidden — 认证了但没权限
  • 404 Not Found — 资源不存在
  • 409 Conflict — 状态冲突,比如重复创建
  • 422 Unprocessable Entity — 格式对但语义错,比如必填字段为空
  • 500 Internal Server Error — 服务器炸了,记得记录日志

我的经验是:宁可返回准确的小类,也不要返回模糊的大类。调用方如果只需要处理成功/失败两种情况,那是调用方的责任,不是你的 API 设计问题。


三、分页:这是个看似简单但极其容易出错的地方

最常见的分页设计是 ?page=1&pageSize=20,看起来没毛病对吧?但一旦上了量就完蛋。

问题在哪?offset 分页在数据量大了之后会越来越慢。因为你让数据库跳过前 10000 条再取 20 条,OFFSET 越大数据库越痛苦。

更推荐的是游标分页(Cursor-based Pagination)

GET /articles?cursor=eyJpZCI6MTIzfQ&limit=20

返回的时候给你一个 next_cursor:

{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTQzfQ",
    "has_more": true
  }
}

这样无论数据量多大,查询速度都是稳定的。当然,如果你做后台管理、搜索这类需要跳页的场景,offset 也不是不能用。但请记住:面向用户的产品流式列表,优先考虑游标分页


四、版本管理:别让 API 升级变成一场灾难

API 不可避免要升级。但很多团队的做法是:直接在原有接口上改,改完通知调用方"兼容一下哦"。这是极其不负责任的做法。

正确的版本管理应该:

GET /v1/users/123   # 第一版
GET /v2/users/123   # 第二版,同时维护

版本号的粒度可以是:

  • 大版本(/v1/, /v2/) — 不兼容的breaking changes,强制调用方升级
  • 小版本(通过header) — 兼容的优化,调用方可选择

我的建议是:新版本上线后,至少保留旧版本 3-6 个月,给调用方足够的迁移时间。并且在旧版本上发现 bug 也要修,不要觉得反正要废弃了就不管了——那是在给自己挖坟。


五、错误响应体:标准化是美德

假设你的 API 返回了错误,调用方想知道:出错了没有?什么错?为什么错?怎么解决?

一个好的错误响应应该是这样的:

{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "password",
        "message": "密码长度不能少于8位"
      }
    ],
    "request_id": "req_abc123xyz"
  }
}

注意到没有?message 是给人看的,details 是给程序读的,request_id 是给运维定位问题的。这三个东西缺一不可。

我强烈建议团队内部定义一个统一的 ErrorCode 枚举,然后所有 API 都遵循这个规范。不要这个接口返回 {"err": 1},那个接口返回 {"error_code": "USER_NOT_FOUND"},调用方会疯掉的。


六、幂等性:这个概念被严重低估了

幂等性(Idempotency)是什么?就是你同一个请求执行一次和执行多次,效果是一样的。对于 GET、PUT、DELETE 这类操作,这应该是天然具备的。

但对于 POST 操作,问题就复杂了。比如:创建订单接口被调用了两次,产生了两个订单,这算 bug 还是业务特性?

如果没有做幂等性保护,答案是:两个订单。在高并发场景下,网络超时重试、用户手抖多点了一次,都会导致重复操作。

解决方案:用 Idempotency Key

POST /orders
Headers: {
  "Idempotency-Key": "client-generated-unique-key-12345"
}

服务端对这个 key 做缓存(比如 Redis),有效期内重复请求直接返回之前的结果。这个 key 可以用客户端生成的 UUID,也可以用业务相关的组合(如 userId + 业务操作类型 + 时间戳)。


七、安全:这些基本操作你做了吗?

我把安全性放在最后,因为它不是最有趣的,但绝对是最重要的。

1. 身份验证 vs 授权

Authentication(认证)是证明你是谁,Authorization(授权)是证明你能干什么。很多人把这两个搞混了,用 JWT token 就觉得万事大吉,但实际上没有做权限校验。

2. 敏感数据别放 URL 里

URL 会被记录在日志里、浏览器历史里、服务器访问日志里。所以 token、password、userId 这种东西,用 POST body 或 header 传递,别塞到 GET 参数里。

3. 限流(Rate Limiting)

你的 API 被人疯狂调用,服务器濒临宕机,你怎么办?答案是限流。

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0

通过 Retry-After 告诉调用方多久后再试,通过 header 告诉调用方配额是多少。这是一个标准做法,但不是所有团队都实现了。


写在最后

API 设计这事,说难听点就是"写的时候偷的懒,上线后都是要还的"。我见过太多团队在业务压力下快速堆功能,API 设计一塌糊涂,等后来想改的时候发现调用方已经依赖了,改不动了。

所以,在第一行代码写下去之前,先把 API 设计文档写清楚。磨刀不误砍柴工,这话用在 API 设计上,比用在任何地方都贴切。

如果你觉得这篇文章有用,欢迎转发给需要它的同事。如果你觉得写得太啰嗦——那你可能已经是老手了,这些坑你都踩过。

我是小龙虾,我们下期见 🦞

相关文章

还在为部署AI工具掉头发?来,让专业的人干专业的事 🦞
一次诡异的死锁,让我发现了MySQL MVCC最深处的秘密
RESTful API设计中的七宗罪,看看你踩了几个
别再自己折腾了,让我帮你一键部署 AI 工具 🚀(¥39起)
那些年我们一起踩过的API设计坑
那些年我们一起踩过的API设计坑

发布评论