RESTful API设计:我发现大家都在犯同样的错误

2026-08-27 7 0

干了这么多年后端,我发现一件事:大部分程序员写的API,用四个字形容就是「能用就行」

不是说代码不能用,而是设计得稀烂。后来者接手的难度堪比读天书,前端同学骂骂咧咧,产品经理问为什么接口返回的数据格式这么离谱。

今天不整虚的,直接聊聊RESTful API设计里那些看似正确实则坑爹的错误操作。


第一个坑:URL命名全靠直觉

我见过最离谱的API URL长这样:

/getUserInfo
/get_user_data?id=123
/UserServlet?type=getInfo

看到这种URL,我血压直接拉满。

RESTful的精髓是什么?资源+操作通过HTTP语义表达。 正确的姿势应该是:

GET /users/123          # 获取用户信息
PUT /users/123          # 更新用户信息
DELETE /users/123       # 删除用户

名词用复数,动词不要出现在URL里。HTTP方法已经告诉你这是啥操作了,你再加个get、create、delete是几个意思?

小技巧:URL就是地址栏里那串东西,它应该是「名词」的世界,不应该是「动词」的秀场。


第二个坑:HTTP方法乱用,把POST当万能钥匙

这是重灾区。我见过一个项目,95%的接口全是POST,不管查还是改,统统POST一把梭。

问就是「POST最稳」「GET带参数容易丢」「项目赶先这样」。

我:???

HTTP方法不是给你随便选的,它们有明确的语义:

  • GET - 查,安全、幂等、可以缓存
  • POST - 增,不安全、不幂等
  • PUT - 改(全量),幂等
  • PATCH - 改(部分),不幂等
  • DELETE - 删,幂等

你一个查询接口用POST,不是不能用,是把HTTP缓存机制直接干废了。CDN同学会从屏幕那边爬过来揍你。


第三个坑:状态码全靠200活着

很多项目的接口响应永远都是:

{
  "code": 200,
  "message": "success",
  "data": {...}
}

// 或者
{
  "success": true,
  "msg": "操作成功",
  "result": {...}
}

不管出啥事,HTTP状态码永远是200。然后在body里自己定义code。

我就想问:你都判断了code,为啥不让HTTP状态码直接表达语义?

正确的做法:

// 资源不存在
GET /users/999
HTTP 404 Not Found
{
  "error": "User not found",
  "code": "USER_404"
}

// 参数校验失败
POST /users
HTTP 400 Bad Request
{
  "error": "Validation failed",
  "details": ["email must be valid"]
}

// 未认证
GET /users/me
HTTP 401 Unauthorized
{
  "error": "Authentication required"
}

这样有什么好处?HTTP中间件、网关、监控都能直接识别错误类型,不用解析body。 你自己定义的code只有你的业务代码认识,HTTP状态码谁都认识。


第四个坑:分页设计五花八门

分页这事儿,我见过至少七八种实现方式:

// 方式1:offset+limit
GET /users?offset=0&limit=20

// 方式2:page+size
GET /users?page=1&size=20

// 方式3:skip+take
GET /users?skip=0&take=20

// 方式4:cursor
GET /users?cursor=abc123&limit=20

// 方式5:range(很古早的做法)
GET /users?start=0&end=20

不是说哪种绝对错,但问题是团队内部不统一,项目之间不通用。每次接新项目都得先问:「咱这分页用的啥格式?」

我的建议:统一用一种,推荐cursor-based分页。为什么?

  1. 数据新增时不会导致分页错位
  2. 性能稳定,不随offset增大而变慢
  3. 适合实时性要求高的场景

offset分页的唯一好处是「能跳页」,但实际场景里用户真的需要跳到第500页吗?真要跳页,前端可以先筛选再分页。


第五个坑:版本号放URL还是Header?

这事儿社区吵了很久。我的态度是:放URL

// 推荐
GET /v1/users/123
GET /v2/users/123

// 不推荐(除非你有非常充分的理由)
GET /users/123
Accept: application/vnd.api+json; version=2

为什么?URL是可视的、shareable的、cacheable的。调试的时候复制粘贴就能用,Nginx配置也简单。如果放Header,每次调试都要改请求头,多一步操作。

当然,如果你的API是面向公众的SDK或者需要严格版本管理,Header方案也行。但大多数内部项目,放URL更实用


第六个坑:返回数据嵌套过深

我见过这种返回结构:

{
  "code": 200,
  "data": {
    "result": {
      "user": {
        "info": {
          "name": "张三",
          "profile": {
            "avatar": "https://..."
          }
        }
      }
    }
  }
}

三层四层嵌套,这是套娃呢?

过深的嵌套带来的问题:

  1. 前端取值地狱:data.result.user.info.name
  2. 字段冗余,很多地方重复存储相同数据
  3. 序列化/反序列化性能损耗

正确的做法:保持扁平,关联数据用ID关联。或者在需要深度的地方用include参数:

GET /users/123?include=profile,orders

{
  "id": 123,
  "name": "张三",
  "profile": {...},
  "orders": [...]
}

写在最后

API设计这事儿,说难不难,说简单也不简单。核心就一句话:让别人用得爽。

怎么判断你的API设计得好不好?

  • 新来的前端同学能不问你,看文档就调通吗?
  • 你的接口能被正确缓存、代理、监控吗?
  • 五年后你接手自己的代码,会不会骂自己?

如果答案都是Yes,恭喜你,你已经比大多数人强了。

如果答案是No,从今天开始改。代码烂了可以重构,API设计烂了,只能等下一任来填坑——或者你自己当那个下一任

共勉。

相关文章

数据库连接池:一个你从不关心直到它炸了的玩意儿
连池都不会配,你的服务不炸算我输
我写了三年API,才发现这些坑全踩过一遍
你的HTTP客户端正在偷偷”饿死”你的服务——一个被忽视的性能杀手
写API接口这事儿,80%%的人都在假装REST
还在为部署AI工具掉头发?来,让专业的人干专业的事 🦞

发布评论