写了5年代码才发现:API设计那些事儿,全是坑!

2026-09-08 10 0

干这行这么多年,我见过太多项目在API设计这块翻车。不是接口返回格式乱成一锅粥,就是版本管理让人摸不着头脑,还有那该死的错误处理,简直是灾难现场。今天咱们就来扒一扒API设计里那些不得不说的事情,保证让你看完直拍大腿——原来这里有这么多门道!

一、URL设计:别把接口写成密码

先说说URL设计这事儿。我见过最离谱的接口是这样的:

/api/v1/user/123/action/getInfoByIdAndType?type=2&from=app

看到这种接口,我只想问一句:你是故意的吧?

好的URL设计应该遵循这几个原则:

  • 名词而非动词:用 /users 而不是 /getUsers,用 /orders 而不是 /fetchOrders
  • 层级清晰:资源嵌套要有意义 /users/123/orders 表示用户123的订单列表
  • 避免冗余:不需要每个接口都加 /api 前缀,路由本身就应该语义清晰
  • 统一风格:要么全小写+横杠,要么全小写+下划线,别一会这样一会那样

我现在的习惯是:所有资源用复数名词,嵌套表示从属关系,过滤条件用查询参数。比如:

GET /articles?category=tech&status=published&page=1&per_page=20

清爽,一眼就能看懂是做啥。

二、HTTP方法:别只会用GET和POST

这是重灾区。很多后端程序员,不管什么操作一律POST走天下。你去问他为什么,他说我只会这个。

拜托,HTTP定义了这么多方法,不是摆设:

  • GET - 读取资源,安全,不会改变状态
  • POST - 创建资源,非幂等
  • PUT - 完整更新资源,幂等
  • PATCH - 部分更新资源
  • DELETE - 删除资源,幂等

有人要问了,PUT和PATCH啥区别?简单说:PUT是全量替换,PATCH是局部更新。比如你有个用户对象:

// 原始数据
{ "name": "张三", "email": "zhangsan@example.com", "age": 25 }

// PUT 更新(需要传全量)
PUT /users/123
{ "name": "张三", "email": "zhangsan_new@example.com", "age": 26 }

// PATCH 更新(只传要改的)
PATCH /users/123
{ "email": "zhangsan_new@example.com" }

用对了方法,前端开发会感谢你的,debug的时候也能少骂几句。

三、状态码:别总返回200然后在body里写error

这个问题太普遍了。我见过无数接口,HTTP状态码永远是200,但body里写着:

{ "code": 500, "message": "服务器内部错误", "data": null }

我就想问问,既然error,为啥状态码不用500?

正确的做法是让HTTP状态码本身就能说明问题:

  • 200 - 成功
  • 201 - 资源创建成功(比如POST后)
  • 400 - 请求参数有问题,客户端的错
  • 401 - 未认证,请先登录
  • 403 - 已认证但没权限
  • 404 - 资源不存在
  • 422 - 请求格式正确但语义错误(比如必填字段缺失)
  • 429 - 请求太频繁,悠着点
  • 500 - 服务器出问题了

有人说我这是RESTful设计,太严格了。朋友,这不是严格,这是基本素养。就像你不能把错误信息返回200一样,状态码和内容保持一致是天经地义的事情。

四、错误处理:给开发者一条活路

错误信息的设计,直接决定了你API的可用性。我见过最离谱的错误返回是这样的:

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

操作失败?啥操作?为啥失败?程序员看到这种错误,只能对着屏幕发呆。

好的错误响应应该包含:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "请求参数验证失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确",
        "value": "not-an-email"
      },
      {
        "field": "age",
        "message": "年龄必须在18-150之间",
        "value": "16"
      }
    ],
    "request_id": "req_abc123"
  }
}

这样开发者拿到错误信息,就知道具体是哪个字段出了问题,该怎么修复。而且加上request_id,方便排查问题。

另外,错误信息要分层次:面向开发者的详细错误(比如字段名、约束条件),和面向用户的友好提示(不用告诉他们技术细节)。

五、版本管理:没有版本控制的API是裸奔

API上线之后,不可避免地要迭代升级。但如果你改了接口,旧版本的客户端可能就全炸了。所以版本管理是必须的。

常见的版本管理方式有几种:

方式一:URL路径版本

/api/v1/users
/api/v2/users

这是最直观的方式,Netflix、GitHub都在用。优点是一眼就能看出调用的是哪个版本。

方式二:Header版本

GET /api/users
Accept: application/vnd.myapi.v2+json

这种方式更符合REST规范,但不够直观。多数开发者看到这种header,第一反应是懵。

方式三:查询参数版本

/api/users?version=2

不推荐。这种方式容易被忽略,而且会被缓存影响。

我的建议是用方式一,简单直接。版本号用v1、v2这样的大版本号,不要精确到v1.1、v1.2。破坏性变更才升主版本,小的优化用向后兼容的方式去做。

六、分页:别一次性把数据全返回了

这条可能大家都知道,但架不住还是有人犯。有人写列表接口,数据库里有10万条数据,他返回一个10万条的数组。前端拿到直接卡死。

分页是必须的,而且是服务端分页,不是前端截断。常见的有两种方式:

Offset分页

GET /articles?page=2&per_page=20

// 返回
{
  "data": [...],
  "pagination": {
    "page": 2,
    "per_page": 20,
    "total": 1000,
    "total_pages": 50
  }
}

简单易用,但有个问题:数据量大的时候,翻到后面会变慢,因为要跳过大堆数据。

Cursor分页(游标分页)

GET /articles?cursor=eyJpZCI6MTAwfQ&per_page=20

// 返回
{
  "data": [...],
  "pagination": {
    "next_cursor": "eyJpZCI6MTIwfQ",
    "has_more": true
  }
}

适合大数据量,不管翻到第几页,性能都稳定。缺点是不能随机跳页。Twitter、Instagram都在用这种方式。

选哪种?看场景。数据量小、需要随机跳页的,用offset。数据量大、只做列表翻页的,用cursor。

七、写在最后

API设计这事儿,说简单也简单,说难也难。简单在于那些原则你可能都听过,难在于真正写代码的时候能不能守住这些原则。

我见过太多项目,一开始图快,随便定义接口,上线之后要改才发现骑虎难下。改吧,要通知所有调用方同步更新;不改吧,留一堆技术债天天被人骂。

所以我的建议是:接口设计的时候多花点心思,写好文档,定义好规范。虽然前期慢一点,但后期维护会轻松很多。毕竟,写代码一时爽,接口乱了一生埋。

好了,今天就聊到这儿。如果你有什么API设计的心得体会,欢迎交流。下次再扒点别的干货。

相关文章

写了5年代码才发现:API设计那些事儿,全是坑!
我删了两千行ORM代码,换成原生SQL,然后产品经理给我买咖啡了
写SQL一时爽,线上火葬场——那些年我踩过的数据库性能坑
写API接口这事儿,有人能写成诗,有人能写成恐怖片
你的接口在说”别卷了”——我是如何用限流把爬虫和内鬼一起拒之门外的
当 AI 开始整活:最近这些新鲜玩意儿把我整不会了

发布评论