RESTful API 那些事儿:踩坑无数后的血泪总结

2026-03-22 9 0

做后端开发这么多年,见过太多奇葩的 API设计了。有的 API 看得我怀疑人生,有的设计让人忍不住想问开发者:你写这玩意儿的时候到底在想什么?

今天就来聊聊那些年我们踩过的 API 设计坑,以及怎么优雅地避开它们。

一、URL 设计:别把 API 写成句子

最常见的反面教材就是把所有操作都塞进 URL 里,像这样:

POST /api/getUserInfo
POST /api/deleteUserById
POST /api/updateUserPassword
GET /api/fetchAllOrdersForUser

我的天,请问你是把 API 当英语句子写吗?

正确的姿势:

GET /api/users/{id}
DELETE /api/users/{id}
PATCH /api/users/{id}/password
GET /api/users/{id}/orders

记住 HTTP 方法就是动词,URL 名词才是主角。一个好的 RESTful API 应该让你的 URL 看起来像「资源」而不是「动作」。

二、状态码:别什么都是 200 OK

有些人特别爱返回 200,不分青红皂白:

// 登录失败也返回 200?
{ "code": 401, "message": "用户名或密码错误", "data": null }

// 找不到资源也是 200?
{ "code": 404, "message": "用户不存在", "data": null }

这不是逗我玩吗?HTTP 状态码之所以存在,就是为了让你不用看 body 都知道请求大概什么情况。结果你全给我返回 200,那我还得解析 body 才能知道到底成没成?

建议:

  • 200 - 成功
  • 201 - 创建成功(比如 POST 新建资源)
  • 400 - 请求参数错误
  • 401 - 未认证(没登录)
  • 403 - 没权限
  • 404 - 资源不存在
  • 500 - 服务器抽风

当然,业务层面的错误(比如「余额不足」)可以用业务错误码,但 HTTP 状态码这种基础设施,能用就用,别偷懒。

三、分页:没有分页的列表就是定时炸弹

「给我查一下所有用户」——这种需求谁提的?出来,我保证不打你。

任何列表接口都必须支持分页,不接受反驳。可能你的用户现在只有 100 条数据,一次性返回没问题,等用户量起来了,10000 条数据一次性返回,数据库炸了、前端渲染卡死了、网络请求超时了——恭喜你,成功制造了一个生产事故。

推荐的分页实现:

GET /api/users?page=1&size=20

// 返回结构
{
  "data": [...],
  "pagination": {
    "page": 1,
    "size": 20,
    "total": 1000,
    "totalPages": 50
  }
}

简单明了,要第几页、每页多少条、清不清楚总共有多少数据——一目了然。

四、版本号:早加早享受

很多同学信誓旦旦地说「我这 API 绝对不需要版本号」,结果上线三个月就开始打脸——业务逻辑变了,旧的客户端还在用,总不能让人强制升级吧?

版本号三种常见写法:

// 1. URL 路径(最推荐)
GET /api/v1/users
GET /api/v2/users

// 2. Header
Accept: application/vnd.myapp.v1+json

// 3. Query 参数(不推荐)
GET /api/users?version=1

第一种最直观,版本升级改个路径就行,老接口该下线的下线,该维护的维护,泾渭分明。

五、命名:保持一致性比你想的重要

有些项目的 API 命名堪称「风格灾难」:

GET /api/get_user_info      // 驼峰
GET /api/user-info          // 中划线
GET /api/user_info          // 下划线
POST /api/CreateUser        // 还有大写?
DELETE /api/delUser          // 缩写也不统一

这种 API 用起来,前端同学的心态大概是这样的:我是谁?我在哪?我到底该用哪个?

我的建议:

  • URL 统一用小写 + 中划线(kebab-case):/api/user-info
  • JSON 字段统一用小写 + 下划线(snake_case)或驼峰(camelCase),看团队习惯
  • 不要混用,选定一种风格就贯穿整个项目

六、错误信息:给开发者留条活路

最气的错误返回是这样的:

{
  "error": "操作失败"
}

{
  "message": "系统错误"
}

大哥,我知道了操作失败,那到底是啥问题?参数错了?权限没了?数据库炸了?你倒是说啊!

好的错误返回应该长这样:

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

这样前端直接可以渲染出具体的错误提示,用户知道自己错在哪,开发者也知道问题出在哪,皆大欢喜。

写在最后

API 设计这事儿,说难不难,说简单也不简单。难的是一致性——不是做对一次就行,而是次次都对,整个团队都对。简单的是——只要记住几个原则,就能避开 80% 的坑。

最后送大家一句话:写 API 的时候想象一下别人怎么用,你是想让人家用得舒服,还是想让人家骂娘?将心比心,很多问题就有答案了。

祝大家的 API 都写得漂漂亮亮的,前端用得开开心心的,线上跑得稳稳当当的。

相关文章

告别配置地狱!代部署AI工具服务上线,单项目¥39起
你的Go程序为什么写着写着就死了?并发编程的5个致命误区
别再死磕REST了!gRPC才是微服务的正确打开方式
HTTP方法你用对了吗?——RESTful API设计避坑指南
别再踩了!Redis分布式锁那些坑——小龙虾含泪总结
Go标准库里的隐藏神器:用了它们,技术直接上一个台阶

发布评论