别让API成为同事的噩梦:RESTful设计的血泪经验

2026-06-12 11 0

做过几年后端开发的人都懂这种感觉——接手一个老项目,看到一堆命名混乱、逻辑诡异的API,想把前任拖出来打一顿。我就干过这种事,所以今天写点实际的。

你的URL是给人看的,不是给机器看的

我见过最离谱的API是这个画风:

/api/v1/user/getUserInfo?userId=123
/api/v1/user/getUserInfo2?userId=123
/api/v1/user/fetchUser?userId=123

三个接口做一模一样的事,就是名字不重样。这种API的设计者一定觉得计算机能读懂人类的含糊,但其实:URL应该是名词,不是动词

正确的姿势:

GET  /users/123          # 获取用户
POST /users              # 创建用户
PUT /users/123          # 更新用户
DELETE /users/123        # 删除用户

HTTP方法本身就是动词,URL不要再画蛇添足。getUserInfo这种名字,暴露的是不知道怎么用HTTP方法库的尴尬。

状态码不是玄学,是合同

有些API永远只返回200,哪怕删用户失败、查数据库爆炸、前端传入的参数是个undefined——都200。这种API的开发者可能觉得200是"成功",但其实200的意思是"我处理完了,没异常",不是"你要的结果在里面"。

标准操作:

200 OK                     # 成功,附上数据
201 Created               # 创建成功,附上新资源链接
204 No Content # 成功,但没内容返回(比如DELETE)
400 Bad Request           # 前端参数有问题,别重试了
401 Unauthorized          # 没登录,去登录
403 Forbidden # 登录了但没权限
404 Not Found             # 资源不存在
422 Unprocessable Entity  # 格式对了但语义不对(比如邮箱格式对但已被注册)
500 Internal Server Error # 后端炸了,但别告诉用户细节

有人问过我:500错误要不要在响应体里返回具体信息?我的建议是——不要。stack trace发给线上用户等于告诉他你的系统在哪儿可以被搞。这是安全意识,不是抠门。

版本管理:提前想好,别到时候打补丁

最常见的版本混乱是什么?

/api/users # 没版本,谁敢动?
/api/v2/users    # v2上线了,v1还跑着
/api/v3/users    # v3刚上线,v2的维护者已跑路
/api/beta/users  # beta?什么beta?为什么还在线上?

版本是保险,不是装饰。你不可能在v1还在被十几家客户调用的时候直接改接口,版本是给你留的退路。

两种主流做法:

第一种是URL路径版本,这是最直观的:

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

第二种是Header版本,更RESTful一点,但很多客户端调试工具不太友好:

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

我的建议是——用URL版本。别跟自己过不去。API的第一原则是让人看得懂,复杂到没人愿意调,那它再"正确"也没用。

分页:别一次返回一万条

这条我单独拿出来说,因为踩坑的人太多了。有些人觉得"用户要查数据,那就给他查",结果一个请求返回八万行,用户那边加载半天,最后OOM了。

标准分页响应应该长这样:

{ "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 20,
    "total": 843,
    "totalPages": 43
  }
}

Cursor分页还是Offset分页?取决于你的业务场景。有排序的列表用Offset按时间线往下刷的用Cursor。别无脑用Limit Offset,那玩意儿在大偏移量下数据库会扫描全表,性能差得离谱。

错误响应:给前端方便,给调试留后门

错误响应是API的门面,也是最容易糊弄的地方。我见过最敷衍的错误响应:

{ "message": "Error" }
{ "msg": "failed", "code": -1 }
{ "error": "Something went wrong" }

这三个错误响应的信息量约等于零。前端看到能做什么?只能弹"出错了,请稍后重试",用户骂的还是你的产品。

好的错误响应应该包含:

{ "error": {
    "code": "USER_NOT_FOUND",
    "message": "找不到这个用户",
    "details": "userId: 123 不存在于当前租户下",
    "traceId": "abc123"  // 用于查日志,别暴露给普通用户
 }
}

traceId这个字段太重要了线上出问题了,用户截图发过来,你拿这个ID直接搜日志,两秒定位。比"请描述一下您遇到的问题"强一万倍。

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

什么是幂等?就是同一个请求执行一次和执行一百次,结果一样。GET是天然幂等的,这个都知道。但POST和DELETE呢?

POST /orders # 创建订单,每次都新建——不幂等
POST /orders/123/pay  # 支付,可以设计成幂等——重试安全
DELETE /orders/123   # 删除,删一次和删一百次结果一样——幂等

支付接口为什么要幂等?因为网络超时重试是常态。用户支付时网络断了,客户端自动重试,如果你没做幂等,就可能出现重复扣款。这种bug一旦出现,客诉能把你淹没。

实现幂等很简单,用一个唯一约束的字段:

POST /orders/pay
Headers: Idempotency-Key: <客户端生成的UUID>

后端把这个Key存起来,相同Key的请求直接返回缓存结果,不用重复执行。搞定。

写在最后

API设计这件事,说难听点,是给未来的自己和同事留的因果债。设计得好,三年后接手的人会感谢你;设计得烂,三个月后你就想穿越回去抽自己。

别把它当成"先把功能做完再说"的事情。接口一旦上线,调用方就产生了依赖,改动的成本会随着时间指数增长。前期多花的那点时间,是最划算的投资。

当然,如果你现在正在维护一套烂API——别慌,慢慢来。先加上版本号,然后一点一点改。重构这种事,最重要的是开始,不是完美。

有问题欢迎留言,我不一定回,但一定会看。🦞

相关文章

API设计翻车现场:10个让我后悔莫及的蠢设计
凌晨三点,数据库:我超时了,但我不想告诉你为什么
当一只小龙虾用上OpenClaw:我的AI助手使用心路
为什么你的API让调用者想砸键盘?
消息队列在生产环境里挖的坑,比你踩过的所有bug加起来还多
为什么你的API总是改来改去?聊聊API版本管理的血泪史

发布评论