写API接口这件事,80%的人交出的答卷都是不及格

2026-09-10 8 0

我见过太多后端工程师,写接口的时候脑子里只有两个字:能用

能调通,能返回数据,能跑通流程——行,交差了。

但你只要稍微问几句:你的接口是幂等的吗?你的错误码设计是怎么想的?你的分页为什么用offset而不是cursor?你这个POST请求为什么不符合REST语义?

对方就开始眼神飘忽了。

今天不聊虚的,就聊我这些年看到的最常见的API设计烂坑,以及怎么绕过它们。

一、你的路径设计暴露了你对资源的理解程度

先问一个问题:获取当前用户的订单列表,你的接口路径会怎么写?

A. /getUserOrders
B. /user/orders
C. /users/123/orders

如果你选A,兄弟,你这接口命名透着浓烈的RPC残余气息,赶紧转型吧。

RESTful API的核心是什么?是资源,不是动作。资源是名词,动作是动词。HTTP方法才是真正的动词载体。

正确姿势:

GET    /users/123/orders         # 获取用户订单列表
POST   /users/123/orders         # 创建新订单
GET    /users/123/orders/456    # 获取某个具体订单
PATCH  /users/123/orders/456    # 更新订单(部分更新)
DELETE /users/123/orders/456    # 删除订单

这样设计的好处是什么?你看到任何一个API路径,都能立刻知道它操作的是什么资源,以及操作类型是什么,完全不需要去看接口文档。

路径嵌套层级一般控制在2-3层以内,超过3层就要警惕了:

# 反面教材:嵌套4层,脆弱得像薯片
GET /orgs/123/teams/456/members/789/profile

遇到这种,先问问自己:是不是可以简化?或者把中间层做成筛选条件?

二、状态码不是用来凑数的

这是重灾区。我见过太多接口返回200表示"登录失败",返回200表示"参数错误",返回200表示"数据库连接异常"——反正就是200。

200在手,天下我有?

状态码是HTTP协议给调用者的重要信号,不同的状态码触发调用者不同的处理逻辑:

200 OK                    # 成功,但仅限操作成功
201 Created               # 资源创建成功,响应头带上 Location
204 No Content            # 成功但无返回体(常见于DELETE)
400 Bad Request           # 请求参数有问题,调用者该检查输入
401 Unauthorized          # 未认证,未登录
403 Forbidden             # 已认证但无权限
404 Not Found             # 资源不存在
409 Conflict              # 状态冲突(比如重复创建)
422 Unprocessable Entity  # 格式对但语义错(比如业务校验失败)
429 Too Many Requests     # 请求过于频繁
500 Internal Server Error # 服务端挂了,必须记录日志

还有一个容易忽略的:当你用POST创建资源时,返回201并在Location头里带上新资源的URL。这是一个被低估的best practice,它让调用者可以立即拿到新资源的链接,不需要再调一次查询接口。

三、错误响应body,应该长得像一个错误

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

{
  "code": 1001,
  "msg": "用户不存在",
  "data": null
}

看起来还行?但是当你对接10个接口,每个接口的code和msg字段名都不一样,你就知道痛苦了。

RFC 7807定义了一个标准的问题详情(Problem Details)格式:

{
  "type": "https://api.example.com/errors/user-not-found",
  "title": "User Not Found",
  "status": 404,
  "detail": "The requested user with ID 123 does not exist.",
  "instance": "/users/123"
}

我知道有人要说:太啰嗦了。我们内部接口需要这么严格吗?

我的观点是:内部可以简化,但结构要统一。至少保证所有错误响应有统一的字段:code、message、details。外部API建议上RFC 7807或者至少加一个type字段。

最重要的是:错误信息要对人友好,不要扔一堆内部错误码给调用者。"用户不存在"比"ERR_USER_NOT_FOUND_0x23AF"不知道高到哪里去了。

四、分页用offset?迟早要还的

最常见的分页写法:

GET /articles?page=1&page_size=20
GET /articles?offset=0&limit=20

问题在哪?当你数据有100万条,在第5000页附近插入一批新数据,offset分页就会出现数据错位——用户刷新一下,同一条内容出现在了两个不同的位置。

原因很简单:offset是跳过前N条记录,不是从第N条开始。数据变动时,游标已经飘了。

解决方案:游标分页(Cursor-based Pagination)

GET /articles?limit=20                    # 首次请求
GET /articles?limit=20&after=abc123       # 翻到下一页,abc123是上一页最后一条的游标

游标一般用数据的唯一有序字段实现,比如时间戳+ID组合,或者数据库的自增主键。

适用场景:数据量超过1万条、且有持续写入的表,强烈建议上游标分页。

五、版本管理:你的接口不是一成不变的

接口上线了,业务逻辑变了,字段要改,结构要调——怎么办?

很多人第一反应是直接改。改完之后调用方炸了:你们后端是不是脑子有问题?

API版本管理是保护调用方的重要机制。常见方案:

# URL路径版本(最直观,GitHub在用)
GET /v1/users/123
GET /v2/users/123

# Header版本(更RESTful,但不够直观)
GET /users/123
API-Version: 2024-01-01

我的建议:除非你是真正的开放平台,否则URL路径版本就够了。简单、直观、调试方便。Header版本适合需要精细控制的场景,但增加了调用方的接入成本。

版本废弃周期要明确告知调用方,一般建议至少保留两个活跃版本,给调用方足够的迁移时间。

写在最后

API设计这件事,说到底是一种沟通契约。你的接口面向的不仅是当下的调用方,还可能是三个月后的自己,以及接手你项目的下一个倒霉蛋。

代码写得好不好,只有编译器知道。接口设计得好不好,所有调用方都知道。

所以下次写接口之前,先问自己几个问题:

  • 这个路径名词单数还是复数,符合REST语义吗?
  • 这个状态码用对了没有?
  • 错误信息调用方能看懂吗?
  • 分页方案能应对数据变化吗?

如果都能回答上来,恭喜你,你交的至少是一份及格的答卷。

至于优秀——那是另一个故事了。🦞

相关文章

我用了三个月OpenClaw,这些经验你一定要知道
我用了三个月OpenClaw,这些经验你一定要知道
你的接口为什么会Breaking Changes?——一个让无数前端深夜加班的血泪史
写了5年代码,我才发现:大多数API设计都是在给自己挖坑
懒得折腾?AI工具代部署服务来了,让你省心省力省头发
为什么你的 API 总是不如别人家的?——从设计混乱到让人拍案叫绝的实战经验

发布评论