干过后端的都知道,API这玩意儿,写出来容易,写好难。我见过太多团队,一边喊着"敏捷开发",一边把API设计成一坨浆糊,最后害得前端同学天天骂娘,移动端同学摔键盘,测试同学差点提刀来见。
今天不整虚的,直接上干货。我把这些年见过的坑、踩过的雷、悟出的道理,全部分享出来。看完这篇文章,你的API设计能力至少能上一个台阶。
坑一:URL设计得像鬼画符
先看几个反面教材:
❌ GET /getUserInfoById?id=123
❌ POST /user/createNewUser
❌ GET /api/v1/query_user_info_for_display
❌ POST /userManager/addUserAction
你是不是在笑?但我跟你说,很多线上跑的生产代码就是这个德行。URL设计有几个原则必须记住:
- 用名词,不用动词。HTTP方法本身就是动词,URL不需要再画蛇添足。
- 用复数。/users而不是/user,/orders而不是/order。
- 层级清晰。/users/123/orders表示用户123的订单列表,自然又直观。
- 小写加横线。/user-info不要写成userInfo或者UserInfo。
✅ GET /users
✅ GET /users/123
✅ GET /users/123/orders
✅ POST /users
✅ PUT /users/123
✅ DELETE /users/123
记住:URL是资源的地址,不是动作的描述。你去菜市场不会说"给我拿取白菜操作"对吧?
坑二:状态码乱用,响应格式五花八门
这是重灾区。我见过有人200表示失败,404表示成功,500表示"我再想想"。状态码是API的标准化语言,乱用等于自废武功。
常用状态码及使用场景:
- 200 OK - 最常用的成功状态码,但别啥都用它
- 201 Created - 资源创建成功时使用。重要:响应头里要带Location指向新资源
- 204 No Content - 删除成功等不需要返回 body 的场景
- 400 Bad Request - 请求参数校验失败
- 401 Unauthorized - 未认证(没登录)
- 403 Forbidden - 已认证但没权限
- 404 Not Found - 资源不存在
- 422 Unprocessable Entity - 请求格式对但语义错(比如业务校验失败)
- 429 Too Many Requests - 请求过于频繁,接口限流
- 500 Internal Server Error - 服务端出错了,这个一定要记录日志
响应格式也必须统一。我推荐这种结构:
{n "code": 0,
"message": "success",
"data": {
// 实际数据
}
}
或者更RESTful一点,用HATEOAS:
{n "data": {
"id": 123,
"name": "张三",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders"
}
}
}
错误响应也要统一格式:
{n "code": 40001,
"message": "手机号格式不正确",
"detail": "请输入11位有效手机号"
}
code是业务错误码,方便前端做判断;message是给用户看的提示;detail是可选的调试信息。
坑三:分页设计反人类
很多API的分页实现堪称灾难。来看看几个经典反面教材:
❌ 只返回数据,不告诉总数
❌ offset+limit但不知道总共有多少页
❌ 上一页下一页用pageIndex=1,2,3...这种鬼设计
❌ limit参数名字五花八门(size, pageSize, per_page, count...)
正确的分页应该是这样的:
GET /users?page=1&per_page=20
响应:
{n "data": [...],
"pagination": {
"total": 156,
"page": 1,
"per_page": 20,
"total_pages": 8
}
}
这里有个大坑我要单独说:禁止使用游标分页时返回绝对位置(如"第3页")。因为数据随时可能变化,用户刷到第3页时,第1页的数据被删了,你的"第3页"就变成笑话了。
另一个建议:默认每页数量要有限制。你不能允许用户请求per_page=100000,这不是分页,这是DoS。
const MAX_PER_PAGE = 100;
const DEFAULT_PER_PAGE = 20;
let perPage = Math.min(parseInt(request.per_page) || DEFAULT_PER_PAGE, MAX_PER_PAGE);
坑四:忽视版本管理
API不是写完就完事的,它会变。问题是,API一变,已有的客户端可能就挂了。你不能要求所有用户同时升级他们的App。
版本管理的几种策略:
- URL版本(最常用):/api/v1/users,/api/v2/users
- Header版本:Accept: application/vnd.api+json; version=2
- Query参数版本:/users?version=2(不推荐,不够明显)
我的建议是URL版本,简单直观,而且nginx配置起来也方便。
还有几个版本管理的原则:
- 只增不减。字段可以新增,但不能删除。标记为deprecated的字段要保留至少2个版本。
- 字段可以改type,不可以改语义。把user_name从string改成object?那你是在作死。
- 保持向后兼容的技巧:新增字段用optional,删除字段先标记deprecated。
坑五:安全措施形同虚设
这一条很多人觉得自己做得挺好,其实漏洞一堆。我来列几个常见的:
1. 没有做权限校验
很多新手写API是这样的:GET /orders,然后后端直接查全表。这是严重的越权漏洞。正确的做法是在后端做用户身份校验,并确保用户只能访问自己的数据:
// 错误示例
GET /orders
// 直接查全表
// 正确示例
GET /orders
// 后端: WHERE user_id = current_user.id
2. 敏感数据裸奔
密码、身份证号、银行卡号这些东西,绝对不能明文返回。就算数据库里加密存储了,返回给前端之前也要脱敏:
// 响应里绝对不能出现
"password": "123456"
"id_card": "110101199001011234"
// 应该返回
"phone": "138****5678"
"id_card": "1101011990****1234"
3. SQL注入和XSS
这条本来不想提,但确实还有人踩坑。永远不要相信用户输入,永远使用参数化查询,永远对输出做转义。
总结:好API的标准
说了这么多坑,最后总结一下好API的标准:
- URL设计清晰:见名知意,层级合理
- 状态码正确:不乱用,该是什么就是什么
- 响应格式统一:成功失败都有固定格式
- 分页合理:带总数,有页码,限制每页数量
- 版本管理规范:只增不减,充分兼容
- 安全到位:鉴权、授权、脱敏一个都不能少
- 文档先行:写代码前先写文档,API-first开发
API设计是后端开发的基本功,也是体现工程师水平的重要细节。你代码写得再漂亮,API设计一团糟,照样被人骂。
希望这篇文章能帮你少踩几个坑,少被骂几次。毕竟,愉快的团队协作,从好的API设计开始。
有问题欢迎留言讨论。觉得有用的话,转发是对我最大的支持。