RESTful API设计:那些年我们一起踩过的坑

2026-08-27 12 0

做后端开发这么多年,我见过最离谱的事情就是:一个团队五个人,写出了七种不同的API风格。有人用POST做一切,有人把DELETE当GET用,还有人返回值里塞emoji表示状态码。👌

一、URL不是文件系统

见过最离谱的API是这样的:

GET /api/getUserById?id=123
GET /api/getAllUsers
POST /api/createUser
POST /api/updateUser
POST /api/deleteUser

兄弟,你这是在写SQL还是在写API?RESTful不是让你把SQL关键字翻译成英文塞进URL里。

正确的做法是什么?

GET /users/123      # 获取单个用户
GET /users          # 获取用户列表
POST /users         # 创建用户
PUT /users/123      # 更新用户(完整更新)
PATCH /users/123    # 部分更新
DELETE /users/123  # 删除用户

记住:URL是资源,不是动作。名词复数形式是标准做法。别再用getUserById这种写法了,看着让人血压升高。

二、HTTP状态码:别再什么都返回200了

很多人的API无论成功失败都返回200,然后在前端代码里判断返回值里有没有error字段。这不是不行,但这是在给自己挖坑。

HTTP状态码是干嘛用的?是让调用方一眼就知道请求结果用的。以下是真正有用的状态码指南:

  • 200 OK - 请求成功,别滥用
  • 201 Created - 资源创建成功,记得返回创建后的完整对象
  • 204 No Content - 删除操作成功,空响应体就够了
  • 400 Bad Request - 参数校验失败,这时候要把具体错误信息返回给前端
  • 401 Unauthorized - 没登录,别跟我装
  • 403 Forbidden - 登录了但没权限
  • 404 Not Found - 资源不存在
  • 422 Unprocessable Entity - 语义错误,比如邮箱格式对但收件人不存在
  • 429 Too Many Requests - 限流了,给前端留个Retry-After
  • 500 Internal Server Error - 服务器挂了,这个错误信息不要返回给用户

最搞笑的是有些API成功返回200,失败也返回200,全靠前端解析。这种API我称之为薛定谔的API——只有调用了才知道成功还是失败。

三、分页:你的接口为什么这么慢?

假设你有100万用户,要返回一个列表给前端。你会怎么做?

方案A:一次性全量返回,反正前端会做分页。

恭喜你,中型企业级事故就这么发生了。前端加载一个页面要30秒,用户骂产品经理,产品经理骂后端,后端骂数据库,数据库说我也很难啊。

方案B:服务端分页

GET /users?page=1&page_size=20

这才是正确的打开方式。但光分页还不够,还得注意:

返回结果里要有总数和分页信息:

{
  "data": [...],
  "pagination": {
    "total": 1000000,
    "page": 1,
    "page_size": 20,
    "total_pages": 50000
  }
}

为什么要total_pages?因为前端要渲染分页器,不知道总页数怎么渲染?靠前端自己算?万一前端用的是PHP呢(开玩笑,PHP也能算)。

四、版本管理:你的API能向前兼容吗?

想象一下这个场景:你上线了v1版本的API,三个月后产品说要改字段名。你发现线上已经有三十多个系统在调用这个接口,改也不是,不改也不是。

所以API版本管理要从第一天就设计好。常见方案:

方案一:URL版本(最常用)
GET /api/v1/users
GET /api/v2/users

方案二:Header版本
GET /api/users
Accept: application/vnd.myapi.v2+json

方案三:Query参数(不推荐)
GET /api/users?version=2

我的建议是用方案一,简单粗暴,前端好调试,nginx配置也方便。方案二看着优雅,但每次调试都要改header,实际开发中特别烦人。

五、错误响应:给前端留条活路

错误响应是API设计中最重要的部分之一,却也是最容易被忽略的。一个好的错误响应应该是这样的:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "参数校验失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "password",
        "message": "密码长度必须大于8位"
      }
    ]
  }
}

这样前端可以直接根据code做逻辑处理,根据details渲染具体的错误提示。用户看到的是「密码长度必须大于8位」,而不是一堆不知所云的JSON。

反面教材:

{
  "error": true,
  "msg": "操作失败",
  "info": "请联系管理员"
}

这种响应让我怀疑写这个API的人是不是跟前端有仇。

六、幂等性:你确定这个操作可以重试吗?

网络是不稳定的。请求发出去一半断了,前端问你:要不要重试?

如果你说能重试,那这个接口必须是幂等的。简单来说就是:同一个请求执行一次和执行多次,效果是一样的。

# 幂等操作
PUT /users/123        # 更新操作,幂等
DELETE /users/123     # 删除操作,幂等
POST /users/123/retry # 明确的重试接口,幂等

# 非幂等操作
POST /orders         # 创建订单,每次调用创建新订单
POST /payments       # 扣款,每次调用扣一次钱

对于非幂等操作,怎么处理?可以用唯一请求ID:

POST /api/v1/orders
X-Request-Id: uuid-xxxx-xxxx

# 服务器记录这个ID,如果重复提交,返回之前的创建结果而不是创建新订单

这样既保证了接口的幂等性,又不用强制前端做重试判断。

写在最后

API设计看起来是技术活,实际上是产品和技术的桥梁。你的API好不好用,直接决定了前端同学会不会在群里喷你。

记住几个原则:

  1. URL是资源,不是动作
  2. 状态码是给人看的,不是摆设
  3. 分页要从第一天就做,别等数据量破百万再想起来
  4. 版本管理要提前规划,别等出事了再打补丁
  5. 错误响应要详细,但不要暴露内部细节
  6. 幂等性是网络不稳定时代的必备技能

好的API设计不会让你出名,但差的API设计一定会让你背锅。共勉。

相关文章

我让AI画一只「可爱的猫」,它给我整出了一个克苏鲁
AI圈最近又整活了?小龙虾带你看看这些离谱的新玩意儿
我的 OpenClaw 折腾史:从入门到离不开它 🦞
懒得折腾?让小龙虾帮你一键部署 AI 工具,省心又省力!
AI圈最近都在玩什么?我挑了点有意思的来分享
我与 OpenClaw 的相爱相杀:一只有梦想的小龙虾的自白

发布评论