别让你的API成为同事的噩梦:RESTful设计踩坑实录

2026-04-06 3 0

别让你的API成为同事的噩梦:RESTful设计踩坑实录

干这行这么多年,我见过太多API设计得跟心情似的——今天这样明天那样,返回格式飘忽不定,错误信息跟谜语一样。同事问起来,开发者还理直气壮:"能用啊!" 能用个锤子。今天咱们就来聊聊那些让人血压飙升的API设计问题,以及怎么避坑。

一、URL设计:别把接口写成情书

先来看个反面教材:

GET /getUserInfoById?id=123
POST /updateUserInfo
GET /deleteUser?id=456
GET /queryAllOrdersForUserWithPaginationAndSort

看到这种接口,我血压直接拉满。URL应该是名词复数+资源标识,不是动词+一堆说明文。

正确的姿势:

GET /users/123          # 获取单个用户
PATCH /users/123         # 部分更新用户
DELETE /users/456        # 删除用户
GET /users/123/orders    # 获取用户的订单

记住:HTTP方法才是动词,URL里别再塞动词了。你又不是在写情书,不需要"亲爱的请帮我获取一下用户信息"这种表达。

二、状态码:别总返回200然后在body里说"失败了"

这个我真的要单独开一章骂一骂。多少人干这种事:

{
  "success": false,
  "message": "用户不存在",
  "code": 404
}

兄弟,你这是脱了裤子放屁吗?HTTP状态码就是用来表达状态的,你搞个success: false然后还返回200,是觉得调试的时候不够刺激是吧?

正确做法:

// 用户不存在
GET /users/999
Status: 404
Body: { "error": "用户不存在", "code": "USER_NOT_FOUND" }

// 创建成功
POST /users
Status: 201 Created
Body: { "id": 123, "name": "峰哥", ... }

// 验证失败
POST /users
Status: 400 Bad Request
Body: { "error": "邮箱格式不正确", "fields": ["email"] }

状态码这东西,HTTP规范已经给你定义好了,老老实实用就行了。常用状态码给我背下来:

  • 200 - 成功(但别什么都返回200)
  • 201 - 创建成功
  • 204 - 成功但没内容(适合DELETE)
  • 400 - 客户端请求有问题
  • 401 - 没登录
  • 403 - 登录了但没权限
  • 404 - 资源不存在
  • 422 - 请求格式对但语义有问题
  • 500 - 服务器炸了

三、分页:要么做要么别做,别做一半

最讨厌那种号称支持分页,结果返回:

{
  "data": [...],
  "total": 10000
}

然后就没有然后了。让我猜猜page和size是啥?做梦呢?

标准分页应该这样:

GET /articles?page=2&per_page=20

Response Headers:
  X-Total-Count: 1000
  X-Total-Pages: 50
  Link: <http://api.example.com/articles?page=3&per_page=20>; rel="next",
        <http://api.example.com/articles?page=1&per_page=20>; rel="first",
        <http://api.example.com/articles?page=50&per_page=20>; rel="last"

或者用cursor分页,适合大数据量和实时性要求高的场景:

GET /orders?cursor=eyJpZCI6MTAwMH0&limit=50

Response:
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTA1MH0",
  "has_more": true
}

别忘了,默认分页参数要合理。10条太少,1000条太多,20或50是个不错的默认值。

四、版本管理:要么不管,要么管到底

很多项目一开始不管版本,觉得"小改动不需要"。然后有一天你发现一个字段要改名,结果线上几十个客户端在跑,你改还是不改?

版本管理的正确姿势:URL里带版本号

GET /v1/users/123  # 旧版本客户端
GET /v2/users/123  # 新版本客户端

注意:URL版本不是让你在代码里复制粘贴一整套。正确做法是:

# 路由层统一处理
router.use('/v1/*', v1Handler)
router.use('/v2/*', v2Handler)

# 业务逻辑复用,只在转换层做差异处理
# v1和v2的handler复用同一个service层

还有,别版本越改越多,超过3个版本就该考虑强制升级了。别学某些大厂,v1还在维护是因为"有些客户说不想升级"——这是技术债,不是理由。

五、错误信息:说人话,别打哑谜

看看这个错误返回:

{
  "error": "INVALID_PARAM",
  "message": "参数错误"
}

我:???什么参数?哪里错了?为什么错?

好的错误返回应该是:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数校验失败",
    "details": [
      {
        "field": "email",
        "rejected_value": "峰哥@qq.com",
        "reason": "邮箱域名不在允许列表中,允许的域名: gmail.com, 163.com, qq.com"
      },
      {
        "field": "age",
        "rejected_value": "150",
        "reason": "年龄必须在 0-120 之间"
      }
    ]
  },
  "request_id": "req_abc123xyz"
}

几个要点:

  • 告诉用户哪个字段错了
  • 告诉用户提交的值是什么
  • 告诉用户为什么错
  • 给个request_id,方便排查问题

六、幂等性:这个概念救过我的命

幂等性听起来高大上,其实很简单:一个操作执行一次和执行多次,结果是一样的。

GET显然是幂等的,DELETE也是(删两次和删一次效果一样),POST就不是(创建两次就是两个资源)。

重点说说PATCH,很多人以为PATCH和POST一样不安全,其实不对:

# 假设用户当前余额是100
PATCH /accounts/123
{ "balance": 80 }  # 设置为80

# 如果网络超时,重试一次
PATCH /accounts/123
{ "balance": 80 }  # 结果还是80,幂等!

但如果你是这样设计的:

PATCH /accounts/123
{ "increment": -20 }  # 减少20

# 重试一次?糟糕,扣了两次钱!

所以设计API的时候要想清楚:这个操作重试会不会出问题?如果会,客户端怎么知道要不要重试?加个幂等key是个好方案:

POST /payments
Headers: Idempotency-Key: your-unique-key-here
Body: { "amount": 100, ... }

# 重试时用同样的key,服务器返回同样的结果

写在最后

API设计这事儿,说难不难,说简单不简单。核心就一句话:设计API的时候,想想调用你的人。如果你自己调用这个API,会不会骂娘?

好的API应该像一本使用说明书写得好的产品——看着舒服,用着顺手,出问题了也知道怎么解决。别把调试API变成一场侦探游戏,大家都挺忙的。

下次设计API之前,先问自己三个问题:

  • 这个接口命名清晰吗?
  • 返回状态码对吗?
  • 出错了我能快速定位问题吗?

如果三个问题都是肯定的,恭喜你,你写了一个不会被人背后骂的API。这就够了,比很多线上跑着的API都强了。

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

相关文章

你的后端正在被”超时”慢慢杀死:80%的人都在犯同一个致命错误
上线五分钟,排查两小时:你需要分布式链路追踪
「懒人福音」AI工具一键部署,自己折腾还是花钱搞定?
RESTful API设计:那些年我们一起踩过的坑,今天一次说清楚
你的数据库事务可能是定时炸弹:没人告诉你的隔离级别真相
RESTful API 已经不是银弹了,你们还在盲目追从?

发布评论