4、REST API 设计实践:URL 命名规范、版本管理、分页与过滤、错误处理
聊到 REST API 设计,我见过太多团队踩坑了。说白了,API 就是你和外部世界的契约。契约写得好,大家合作愉快;写得烂,后期维护就是一场噩梦。今天我把几个核心要点掰开揉碎讲清楚。
4.1 URL 命名规范:别让接口名变成猜谜游戏
URL 命名这件事,我个人的习惯是「名词复数 + 层级关系」。为什么用名词?因为 REST 的核心是资源,资源就是名词。动词应该交给 HTTP 方法去表达。
核心原则:
- 使用小写字母,单词间用连字符(-)分隔
- 避免使用下划线(_),因为下划线在某些字体下容易被遮挡
- 不要出现动词,比如
/getUser就是典型的反模式 - 层级关系用斜杠(/)表示,但不要超过三级
举个例子,我之前接手过一个项目,接口长这样:/api/getUserInfoById。你想想看,这跟 RPC 有什么区别?改完之后变成 /api/users/{id},清爽多了。
# 好的命名
GET /api/users # 获取用户列表
GET /api/users/{id} # 获取单个用户
POST /api/users # 创建用户
PUT /api/users/{id} # 更新用户
DELETE /api/users/{id} # 删除用户
# 不好的命名
GET /api/getUserList # 动词,冗余
POST /api/createUser # 动词,冗余
GET /api/user_info # 下划线,不推荐
小技巧:对于关联资源,用嵌套表示。比如获取某个用户的订单:/api/users/{userId}/orders。但注意,嵌套不要超过两层,否则考虑用查询参数。
4.2 版本管理:给 API 留条后路
我记得有一次,我们团队上线了一个新功能,结果把老接口的返回字段改了。前端同学第二天上班发现页面全崩了……嗯,从那以后我再也不敢不做版本管理了。
版本管理常见的有三种方式,我列个表格对比一下:
| 方式 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 路径 | /api/v1/users |
直观,容易缓存 | URL 变长,版本升级需要改路径 |
| 请求头 | Accept: application/vnd.myapp.v1+json |
URL 干净,符合 REST 理念 | 调试不方便,浏览器直接访问困难 |
| 查询参数 | /api/users?version=1 |
实现简单 | 容易被忽略,缓存策略复杂 |
我个人习惯用 URL 路径方式。为什么?因为直观。你打开浏览器就能看到版本号,调试的时候不用翻请求头。但要注意,版本号建议用 v1、v2 这样的整数,不要用 v1.0.1 这种小版本号——API 版本和软件版本是两码事。
注意:不要轻易废弃旧版本。我建议至少保留两个大版本,给客户端留足迁移时间。比如 v1 和 v2 同时运行,等 v2 稳定运行三个月后再考虑下线 v1。
4.3 分页与过滤:别让用户一次拿太多
你有没有遇到过这种情况?调一个接口,返回了 10 万条数据,浏览器直接卡死。其实这就是没做分页的后果。分页不是可选项,是必选项。
我常用的分页参数设计如下:
GET /api/users?page=1&size=20
返回格式:
{
"data": [...],
"pagination": {
"page": 1,
"size": 20,
"total": 156,
"totalPages": 8
}
}
这里有个细节:total 字段要不要返回?我的建议是返回。虽然每次查询总数会有性能开销,但前端需要它来渲染分页组件。如果数据量实在太大(比如百万级),可以考虑用 hasMore 布尔值替代。
过滤参数怎么设计?我见过有人把过滤条件塞进 URL 路径里,比如 /api/users/active。这其实不好,因为「活跃用户」是一种状态,不是一种资源。正确的做法是用查询参数:
# 过滤
GET /api/users?status=active
GET /api/users?createdAfter=2024-01-01&createdBefore=2024-12-31
GET /api/users?search=keyword
# 排序
GET /api/users?sort=createdAt&order=desc
# 字段选择
GET /api/users?fields=id,name,email
避坑指南:我曾经遇到过一个接口,过滤参数名不统一,有的用 status,有的用 state,前端同学每次都要翻文档确认。后来我定了个规矩:所有过滤参数统一用 filter[字段名] 的格式,比如 filter[status]=active。这样既清晰又容易扩展。
4.4 错误处理:让错误信息真正有用
错误处理是 API 设计中最容易被忽视的部分。很多团队只返回一个 HTTP 状态码,比如 400 或 500,然后前端同学一脸懵逼:「到底哪里错了?」
我建议的错误响应格式长这样:
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户 ID 为 12345 的记录不存在",
"details": {
"userId": "12345",
"resourceType": "User"
},
"requestId": "req-abc-123-def"
}
}
几个关键点:
- code:机器可读的错误码,前端可以根据它做不同的处理
- message:人类可读的错误描述,要具体,不要只说「参数错误」
- details:额外的上下文信息,方便调试
- requestId:请求追踪 ID,排查问题时特别有用
HTTP 状态码也要选对。我见过有人所有错误都返回 400,这其实不对。常用的状态码:
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 400 | Bad Request | 参数校验失败、请求格式错误 |
| 401 | Unauthorized | 未登录或 token 过期 |
| 403 | Forbidden | 已登录但无权限 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 数据冲突,比如重复创建 |
| 422 | Unprocessable Entity | 业务逻辑错误 |
| 429 | Too Many Requests | 限流触发 |
| 500 | Internal Server Error | 服务器内部错误 |
注意:永远不要把服务器内部的堆栈信息直接返回给客户端。我曾经见过一个接口,报错时直接把 Java 的 StackTrace 扔了出来,里面包含了数据库连接信息……这太危险了。内部错误记录到日志里就好,返回给客户端的 message 要经过脱敏处理。
4.5 知识体系总览
说了这么多,我画了一张图帮你梳理一下 REST API 设计的核心脉络:
你看,这四个方面其实都围绕一个核心——一致性。命名一致、版本策略一致、分页格式一致、错误结构一致。只要做到这四点,你的 API 就算不是最优雅的,也一定是最容易用的。
可可精品资料库