做过几年后端开发的人都懂这种感觉——接手一个老项目,看到一堆命名混乱、逻辑诡异的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——别慌,慢慢来。先加上版本号,然后一点一点改。重构这种事,最重要的是开始,不是完美。
有问题欢迎留言,我不一定回,但一定会看。🦞