我写API被喷了三年,才明白这些坑不能踩
先说个真实故事。三年前,我信誓旦旦给leader说:"这个接口设计得很优雅,符合RESTful规范。"结果上线第一周,前端同学拿着需求来找我对峙:"你这个接口返回的数据结构,我拿到手都不知道往哪儿塞,你是在考验我吗?"
那一刻我明白了:API设计烂不烂,前端同学的身体最诚实。
一、为什么要认真对待API设计?
很多人觉得API就是个"数据搬运工",把数据库的东西搬出来扔给前端就行了。如果你也这么想,恭喜你,三年后的你就是我。
API是产品和后端之间的"合同"。合同写得好不好,决定了:
- 前端同学会不会在周会上点名骂你
- 你半夜三点会不会被报警叫醒
- 你离职之后会不会被前同事念叨(骂的那种)
所以,认真对待API设计,是对同事的善良,也是对自己的救赎。
二、那些年我踩过的API设计大坑
坑1:返回结构随心所欲,一个接口一套规矩
刚出道那会儿,我的返回结构是这样的:
// 用户接口
{"code": 200, "data": {"name": "张三", "age": 25}}
// 订单接口
{"status": "success", "result": {"order_id": "123", "amount": 100}}
// 商品接口
[{"id": 1, "title": "小龙虾"}, {"id": 2, "title": "啤酒"}]
看到问题了吗?code、status、干脆裸数组……一个项目里三个人写了六种风格。前端每次对接都要先来问我:"这个接口的code是0成功还是200成功?"问多了我自己都怀疑人生。
最佳实践:统一响应结构,全项目必须长一个样
{
"code": 0,
"message": "success",
"data": {}
}
code统一用0表示成功,非0表示具体错误码。data永远是你要的业务数据,空也行,别裸奔。message给人类看,code给程序看,分工明确。
坑2:分页设计反人类
让我看看有多少人这样写过分页:
{"items": [...], "count": 100, "page": 1, "pageSize": 20}
看着没问题啊?问题大了:前端拿到这个不知道下一页该传什么。page+1?那offset怎么算?如果业务方说我需要知道"我在第几条",你怎么告诉人家?
推荐:基于游标的分页(Cursor-based Pagination)
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTAwfQ==",
"has_more": true,
"total": 156
}
}
前端只需要拿着cursor往下一轮请求里塞,简单粗暴不出错。如果业务需要精确翻页(比如"跳到第5页"),再考虑offset方案,但要和业务方说清楚代价。
坑3:HTTP状态码乱用,404走天下
我见过最离谱的API是这样的:
GET /api/user/123
// 用户不存在
{"code": 404, "message": "用户不存在"}
// 权限不足
{"code": 404, "message": "无权访问"}
// 服务器爆炸
{"code": 404, "message": "内部错误"}
三种错误全返回404,前端看日志以为是同一个问题,排查到怀疑人生。HTTP状态码是给网关、监控、CDN这些基础设施看的,不是给你偷懒用的。
状态码使用规范:
- 200 OK — 一切正常,有数据
- 201 Created — 资源创建成功
- 400 Bad Request — 前端传参有问题,是你代码健壮性不够
- 401 Unauthorized — 没登录,去登录页
- 403 Forbidden — 登录了但没权限,别挣扎了
- 404 Not Found — 资源真的没了
- 500 Internal Server Error — 服务器挂了,这是你的锅
最烦的就是把所有错误都塞进200加自定义code里,搞得监控系统全是"假成功",报警都不知道什么时候该爬起来。
坑4:接口命名看心情
看看这个项目里的接口名称:
/api/getUserInfo
/api/query_user
/api/user-detail
/api/user_get_one
/api/fetchUser
五个人写了五个版本。RESTful规范不是银弹,但命名一致性是底线。团队里定好规矩:动词用GET/POST/PUT/DELETE,名词全小写复数形式,版本号焊死。
GET /api/v1/users # 获取用户列表
GET /api/v1/users/{id} # 获取单个用户
POST /api/v1/users # 创建用户
PUT /api/v1/users/{id} # 更新用户
DELETE /api/v1/users/{id} # 删除用户
简洁、清晰、不需要文档也能猜到干嘛的接口,才是真正的好接口。
三、版本管理:没你想的那么简单
很多人觉得版本管理就是在URL里加个v1:
/api/v1/users
/api/v2/users
然后v2上线了,v1直接下掉。前端哭了:"我还有三个业务线在用v1啊!"你也哭了:"怎么不早点说!"
版本管理三原则:
- 新版本发布后,v1至少再撑6个月。 不是技术做不到,是沟通成本你hold不住。
- 字段删除要"过两代"才能删。 今天在v1废弃,下一个主版本再删。
- 字段重命名不是覆盖,是新增+废弃。 同时返回两个字段,等客户端全切完了再删旧字段。
记住:你的接口用户不是你,你没有权力单方面宣布"明天v1没了"。
四、字段设计:多一点不如少一点
有一种迷惑行为叫"反正前端可能用得上,我多返回点字段吧":
{
"id": 123,
"name": "张三",
"age": 25,
"phone": "138xxxx",
"email": "zhangsan@xxx.com",
"address": "北京市朝阳区xxx",
"created_at": "2024-01-01 10:00:00",
"updated_at": "2024-06-01 15:30:00",
"last_login_time": "2024-06-18 09:00:00",
"last_login_ip": "10.0.0.1",
"user_level": 5,
"user_points": 3500,
"vip_expire_time": "2025-12-31 23:59:59",
"gender": "男",
"birthday": "1999-06-18",
"avatar_url": "https://...",
// ... 等等这还没完
"ext_data": {}
}
一个列表接口返回50个字段,前端说"我只用三个",你说"那你别用其他的呗"。但问题是:数据传输是有成本的,数据存储是有代价的,数据泄露的风险是真实存在的。
字段设计原则:只返回必要的字段。
如果业务确实需要灵活获取字段,用field筛选:
GET /api/v1/users?fields=id,name,avatar_url
列表接口永远只返回核心字段,详情接口再补全。这不是懒,这是专业。
五、写在最后
API设计这事儿,说难不难,说简单也不简单。技术层面就那些规范,但真正的难点在于你愿不愿意多花10分钟站在调用方角度想一想。
每次设计接口之前,先问自己三个问题:
- 前端拿到这个数据,结构清晰吗?
- 出错了,定位问题方便吗?
- 这个接口能优雅地演进吗?
如果三个问题都是yes,那这个接口至少不会让你三年后被人在技术博客里点名吐槽。
当然,如果你看完这篇文章发现自己全踩过,那恭喜你——我们是同行。
欢迎留言说你踩过最离谱的API设计坑,我保证不说是你前同事。🪼