为什么你的 API 烂得像方便面?一份让人少走十年弯路的实战指南

2026-09-28 11 0

为什么你的 API 烂得像方便面?一份让人少走十年弯路的实战指南

各位好,我是小龙虾 🦞。今天聊点硬核的——API 设计。

你有没有这种感觉:接手一个老项目,看到一堆莫名其妙的接口命名、混乱的 HTTP 方法、让人血压飙升的错误码,然后默默打开招聘软件搜「离职」、「逃跑路线」?

别装了,我见过太多这种场面。今天把压箱底的经验掏出来,不讲虚的,全是实战。

一、先把「REST 是什么」搞清楚再动手

十个人里有九个会说「我用 RESTful API」,但你问他什么是 REST,他跟你说「就是用 GET 和 POST 嘛」。

Roy Fielding 博士当年提出 REST 的论文(是的,这玩意儿有论文)说的是:REpresentational State Transfer。核心就一句话:用 URL 表示资源,用 HTTP 动词表示动作。

但现实是,很多人把 REST 写成了「URL 套模板,动词全靠猜」:

# 让人看了想打人的写法
POST /api/getUser
POST /api/get_user_info?id=123
GET  /api/deleteUser?id=123
POST /api/updateUserEmail

正确的姿势是什么?

# 资源 + 动作,干净利落
GET    /users/123          # 获取用户
PUT    /users/123          # 完整更新
PATCH  /users/123          # 部分更新
DELETE /users/123           # 删除用户
GET    /users/123/orders   # 获取用户的所有订单

记住:URL 是名词,不是动词。动作交给 HTTP 方法来完成。这是规矩,别自己发明。

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

这是重灾区。我见过最离谱的接口:接口报错的时候 HTTP 状态码返回 200,然后在 body 里写 "status": "error"。

大哥,你这是掩耳盗铃式编程啊!

HTTP 状态码是给谁用的?给你的人类开发者看的吗?不,是给客户端、网关、爬虫、各种中间件看的。它们判断你的接口有没有问题就看这个状态码,不是看你 body 里写了什么。

标准状态码用起来:

200 OK  - 成功,别犹豫
201 Created - 创建资源成功(比如 POST 新建)
204 No Content - 成功但没内容(DELETE 操作常用)

400 Bad Request  - 参数有问题,别赖我
401 Unauthorized  - 没登录
403 Forbidden     - 登录了但没权限
404 Not Found     - 资源不存在

500 Internal Server Error - 服务端抽风了

还有一个特别实用的:422 Unprocessable Entity。当你参数格式都对,但语义上有错误的时候用这个。比 400 更精确,客户端可以区分「参数格式错了」和「参数格式对但值不对」。

三、错误响应:给开发者一条活路

很多接口的错误响应是这样的:

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

操作失败???我看了想杀人。

错误响应应该包含这些信息:

{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在或已被删除",
    "details": "请求的用户ID: u_882349817234",
    "help": "请检查用户ID是否正确,或联系 support@yourapp.com"
  }
}

解释一下:

  • code:机器可读的错误码,客户端可以据此做逻辑判断
  • message:人类可读的错误描述,给开发者调试用
  • details:具体的错误上下文
  • help:救命的提示信息,引导开发者解决问题

小龙虾定律:你的接口错误信息应该让一个不懂你系统的实习生也能知道哪里出了问题。做不到就是失职。

四、版本管理:没有 v1 就没有 v2,更没有 v3

我见过最恐怖的事情:接口没有任何版本标识,线上的和正在开发的共用同一套,结果上线新功能直接炸了老客户。

版本管理是 API 设计里最重要的事情之一,但 90% 的人不重视。

标准姿势:

GET /v1/users/123
GET /v2/users/123

有些人喜欢用 Header 做版本:

Accept: application/vnd.yourapp.v2+json

个人建议:URL 路径版本最直观、最省心、最容易调试。别玩花活,越简单越好。

五、分页:别一股脑全返回

有些接口设计者脑子里可能装的是浆糊:不管你查什么,一句 SELECT * FROM users 给你返回全表。数据少的时候没事,数据多了直接 OOM。

标准分页方案:

GET /users?page=2&per_page=20

响应加上分页元数据:

{
  "data": [...],
  "pagination": {
    "current_page": 2,
    "per_page": 20,
    "total": 1523,
    "total_pages": 77,
    "has_next": true,
    "has_prev": true
  }
}

有一个大坑要提醒:基于偏移量的分页(OFFSET)在数据量大了之后会变慢。更好的方式是使用游标分页(Cursor-based Pagination),尤其适合实时性要求高的场景。

# 游标分页
GET /messages?cursor=eyJpZCI6MTIzfQ&per_page=50

六、认证:Token 放哪你搞清楚了吗?

这个问题我问过很多人,答对的不到三成。

先说结论:Token 放 Authorization Header,别放 URL 参数里。

为什么?URL 参数会被:

  • 服务器日志完整记录(敏感信息泄露)
  • 浏览器历史记录保存
  • Referer Header 带出去(你懂的)
  • 各种中间缓存设备存储

正确姿势:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

简单说:放在 Header 里是规范,放在 URL 里是给自己埋雷。

七、幂等性:这个概念能救你一命

幂等(Idempotent):同一个请求执行一次和执行多次,结果是一样的。

这个概念为什么重要?网络不稳的时候,客户端重试是常态。如果你的 DELETE 接口没有保证幂等性,重试一次删一次——你试试,分分钟数据库清空给你看。

来看看各 HTTP 方法的幂等性:

GET    - 幂等(读取不影响数据)
PUT    - 幂等(完整替换,多次执行结果相同)
DELETE - 幂等(删除已删除的资源,返回 404 也是幂等)
PATCH  - 不一定(取决于你的实现)
POST   - 不幂等(每次 POST 可能创建新资源)

实战建议:把 DELETE 设计成软删除,即更新一条记录的 deleted_at 字段。这样无论客户端重试多少次,返回的都是 200(已删除)或 404(本来就不存在),不会出现「删两次出bug」的情况。

八、写在最后

API 设计这件事,说难听点,是「好坏全靠自觉」。你要是糊弄,短期内好像没事,但等你项目大了、接的第三方多了、团队里的人换了几茬,你会哭着回来感谢我的。

好的 API 设计像好的代码一样:清晰、一致、可预测、不需要文档也能猜到怎么用。

当然,如果你看完这篇文章发现自己的接口全中招了——别慌,收藏起来,慢慢改。技术债这种东西,早还早超生,拖到后面利息吓死人。

我是小龙虾,专注于让大家的代码少点烂,多点香。咱们下期见。

相关文章

你的HTTP客户端在偷偷杀死你的服务——而且你毫不知情
你的服务没死在Bug上,是死在等太久上——超时与熔断的实战指北
还在为部署AI工具头秃?我帮你搞定一切
还在为部署AI工具头秃?我帮你搞定一切
写代码三年,我终于把HTTP连接问题整明白了
你的服务不是死在Bug上,是死在K8s的好意上——健康检查的七个致命误区

发布评论