干了这么多年后端,我发现一件事:大部分程序员写的API,用四个字形容就是「能用就行」。
不是说代码不能用,而是设计得稀烂。后来者接手的难度堪比读天书,前端同学骂骂咧咧,产品经理问为什么接口返回的数据格式这么离谱。
今天不整虚的,直接聊聊RESTful API设计里那些看似正确实则坑爹的错误操作。
第一个坑:URL命名全靠直觉
我见过最离谱的API URL长这样:
/getUserInfo
/get_user_data?id=123
/UserServlet?type=getInfo
看到这种URL,我血压直接拉满。
RESTful的精髓是什么?资源+操作通过HTTP语义表达。 正确的姿势应该是:
GET /users/123 # 获取用户信息
PUT /users/123 # 更新用户信息
DELETE /users/123 # 删除用户
名词用复数,动词不要出现在URL里。HTTP方法已经告诉你这是啥操作了,你再加个get、create、delete是几个意思?
小技巧:URL就是地址栏里那串东西,它应该是「名词」的世界,不应该是「动词」的秀场。
第二个坑:HTTP方法乱用,把POST当万能钥匙
这是重灾区。我见过一个项目,95%的接口全是POST,不管查还是改,统统POST一把梭。
问就是「POST最稳」「GET带参数容易丢」「项目赶先这样」。
我:???
HTTP方法不是给你随便选的,它们有明确的语义:
- GET - 查,安全、幂等、可以缓存
- POST - 增,不安全、不幂等
- PUT - 改(全量),幂等
- PATCH - 改(部分),不幂等
- DELETE - 删,幂等
你一个查询接口用POST,不是不能用,是把HTTP缓存机制直接干废了。CDN同学会从屏幕那边爬过来揍你。
第三个坑:状态码全靠200活着
很多项目的接口响应永远都是:
{
"code": 200,
"message": "success",
"data": {...}
}
// 或者
{
"success": true,
"msg": "操作成功",
"result": {...}
}
不管出啥事,HTTP状态码永远是200。然后在body里自己定义code。
我就想问:你都判断了code,为啥不让HTTP状态码直接表达语义?
正确的做法:
// 资源不存在
GET /users/999
HTTP 404 Not Found
{
"error": "User not found",
"code": "USER_404"
}
// 参数校验失败
POST /users
HTTP 400 Bad Request
{
"error": "Validation failed",
"details": ["email must be valid"]
}
// 未认证
GET /users/me
HTTP 401 Unauthorized
{
"error": "Authentication required"
}
这样有什么好处?HTTP中间件、网关、监控都能直接识别错误类型,不用解析body。 你自己定义的code只有你的业务代码认识,HTTP状态码谁都认识。
第四个坑:分页设计五花八门
分页这事儿,我见过至少七八种实现方式:
// 方式1:offset+limit
GET /users?offset=0&limit=20
// 方式2:page+size
GET /users?page=1&size=20
// 方式3:skip+take
GET /users?skip=0&take=20
// 方式4:cursor
GET /users?cursor=abc123&limit=20
// 方式5:range(很古早的做法)
GET /users?start=0&end=20
不是说哪种绝对错,但问题是团队内部不统一,项目之间不通用。每次接新项目都得先问:「咱这分页用的啥格式?」
我的建议:统一用一种,推荐cursor-based分页。为什么?
- 数据新增时不会导致分页错位
- 性能稳定,不随offset增大而变慢
- 适合实时性要求高的场景
offset分页的唯一好处是「能跳页」,但实际场景里用户真的需要跳到第500页吗?真要跳页,前端可以先筛选再分页。
第五个坑:版本号放URL还是Header?
这事儿社区吵了很久。我的态度是:放URL。
// 推荐
GET /v1/users/123
GET /v2/users/123
// 不推荐(除非你有非常充分的理由)
GET /users/123
Accept: application/vnd.api+json; version=2
为什么?URL是可视的、shareable的、cacheable的。调试的时候复制粘贴就能用,Nginx配置也简单。如果放Header,每次调试都要改请求头,多一步操作。
当然,如果你的API是面向公众的SDK或者需要严格版本管理,Header方案也行。但大多数内部项目,放URL更实用。
第六个坑:返回数据嵌套过深
我见过这种返回结构:
{
"code": 200,
"data": {
"result": {
"user": {
"info": {
"name": "张三",
"profile": {
"avatar": "https://..."
}
}
}
}
}
}
三层四层嵌套,这是套娃呢?
过深的嵌套带来的问题:
- 前端取值地狱:data.result.user.info.name
- 字段冗余,很多地方重复存储相同数据
- 序列化/反序列化性能损耗
正确的做法:保持扁平,关联数据用ID关联。或者在需要深度的地方用include参数:
GET /users/123?include=profile,orders
{
"id": 123,
"name": "张三",
"profile": {...},
"orders": [...]
}
写在最后
API设计这事儿,说难不难,说简单也不简单。核心就一句话:让别人用得爽。
怎么判断你的API设计得好不好?
- 新来的前端同学能不问你,看文档就调通吗?
- 你的接口能被正确缓存、代理、监控吗?
- 五年后你接手自己的代码,会不会骂自己?
如果答案都是Yes,恭喜你,你已经比大多数人强了。
如果答案是No,从今天开始改。代码烂了可以重构,API设计烂了,只能等下一任来填坑——或者你自己当那个下一任。
共勉。