3、URI 设计规范:资源命名规则、URI层级结构设计、URI版本控制策略、查询参数的使用
好,咱们今天聊聊 URI 设计。说实话,这是 API 设计里最容易被忽视,但又最影响开发者体验的一环。
我见过太多团队,接口命名随心所欲。今天用 /getUserInfo,明天来个 /fetch_all_orders。你想想看,这种 API 谁敢用?
好的 URI 设计,应该像一本清晰的目录。看一眼就知道资源是什么,层级关系如何。下面我把自己踩过的坑和总结的经验,一一说给你听。
3.1 资源命名规则:名词复数,小写连字符
资源命名,核心就一句话:用名词复数,全小写,单词间用连字符。
为什么是名词?因为 RESTful 的核心是资源。动词应该交给 HTTP 方法(GET、POST、PUT、DELETE)去表达。
✅ 推荐写法:
/users— 用户资源/order-items— 订单项资源/blog-posts— 博客文章资源
❌ 不推荐写法:
/getUsers— 混入了动词/UserInfo— 大小写混用/get_all_orders— 下划线风格
我个人习惯用连字符代替下划线。为什么?因为 URL 在浏览器里复制粘贴时,下划线有时会被误识别为空格或特殊字符。连字符更安全,也更易读。
💡 小技巧: 资源名尽量用复数。比如 /users/123 比 /user/123 更统一。因为集合操作(GET /users)天然就是复数,单个资源只是集合的子集。
3.2 URI 层级结构设计:从属关系要清晰
资源之间往往有从属关系。比如一个用户有多篇文章,一篇文章属于某个用户。
层级结构的设计原则是:从大到小,从左到右。
# 用户下的文章
GET /users/{userId}/articles
# 文章下的评论
GET /articles/{articleId}/comments
# 某个用户的某篇文章的某条评论
GET /users/{userId}/articles/{articleId}/comments/{commentId}
嗯,这里要注意:层级不要超过三层。超过三层,说明你的资源划分可能有问题。
我在项目中遇到过这样的场景:一个电商系统,要查某个店铺下某个订单中的某个商品。有人设计成 /stores/{storeId}/orders/{orderId}/items/{itemId}/details。四层!
后来我建议改成:/orders/{orderId}/items/{itemId}。店铺信息通过订单关联即可,没必要层层嵌套。你想想看,前端调用时,真的需要每次都从店铺开始拼路径吗?
⚠️ 避坑指南: 我曾经把用户和角色的关系设计成 /users/{userId}/roles/{roleId}。后来发现角色其实是一个独立资源,用户只是关联了角色。改成 /users/{userId}/role(一对一关系)或 /users/{userId}/roles(多对多关系)更合理。
核心原则:只有真正的从属关系才用层级。如果两个资源是独立的,只是有关联,用查询参数或单独端点更好。
3.3 URI 版本控制策略:别让旧接口拖垮你
API 一定会变。用户信息可能新增字段,订单状态可能调整。怎么管理版本?
常见的策略有三种,我分别说说优缺点。
| 策略 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URI 路径版本 | /v1/users |
直观,容易缓存 | URL 变长,版本管理成本高 |
| 请求头版本 | Accept: application/vnd.myapi.v1+json |
URL 干净,灵活 | 调试不方便,浏览器难直接测试 |
| 查询参数版本 | /users?version=1 |
实现简单 | 容易被忽略,缓存策略复杂 |
我个人习惯用 URI 路径版本。为什么?因为简单粗暴。前端同学调试时,直接在浏览器地址栏改个 v1 变 v2 就能测试。你想想看,用请求头版本,还得装插件改 Header,多麻烦。
✅ 推荐做法:
- 版本号放在路径最前面:
/v1/users、/v2/users - 版本号用整数,不要用
v1.1这种小版本 - 小版本变更(加字段、改描述)不升级版本号
- 大版本变更(删字段、改逻辑)才升级
我记得有一次,团队为了省事,在 /v1/users 里直接删了一个字段。结果合作方的老系统直接崩了。从那以后,我定了个规矩:大版本升级,旧版本至少保留 6 个月。给调用方留足迁移时间。
3.4 查询参数的使用:筛选、排序、分页
查询参数,说白了就是给资源加「过滤器」。设计得好,前端可以灵活获取数据;设计得烂,接口又臭又长。
常见的查询参数场景有这些:
# 筛选
GET /users?status=active
GET /users?created_at=2024-01-01
# 排序
GET /users?sort=created_at
GET /users?sort=-created_at # 降序,用负号表示
# 分页
GET /users?page=1&page_size=20
GET /users?offset=0&limit=20
# 字段选择
GET /users?fields=id,name,email
嗯,这里有几个细节要注意:
- 分页参数名要统一:我建议用
page和page_size。别今天用page,明天用offset,后天用limit。团队内部定一个标准。 - 排序方向用负号:
sort=-created_at表示按创建时间降序。比sort=created_at&order=desc更简洁。 - 字段选择要谨慎:
fields参数虽然好用,但实现起来复杂。我建议只在数据量大的接口(如列表页)提供,详情页直接返回全部字段。
💡 小技巧: 查询参数的值尽量用枚举或标准格式。比如状态用 active、inactive,时间用 ISO 8601 格式(2024-01-01T00:00:00Z)。这样前端不用猜,后端不用解析多种格式。
我曾经遇到过一个接口,查询参数叫 filter,值是一个 JSON 字符串:?filter={"status":"active","age":18}。你想想看,这还叫查询参数吗?直接传 JSON 体得了。这种设计让缓存、日志、调试都变得困难。说白了,查询参数应该是扁平的、简单的键值对。复杂的查询条件,请用 POST 请求 + 请求体。
⚠️ 避坑指南: 查询参数不要用来做「动词」操作。比如 /users?action=delete 这种设计,完全违背了 RESTful 原则。删除就应该用 DELETE /users/{id}。我曾经接手过一个项目,里面全是这种「伪 REST」接口,重构时差点崩溃。
好了,URI 设计规范就聊到这里。总结一下核心要点:
- 资源命名:名词复数、小写、连字符
- 层级结构:从大到小,不超过三层
- 版本控制:路径版本最实用,整数版本号
- 查询参数:扁平键值对,统一分页和排序
下一章,咱们聊聊 HTTP 方法的选择和状态码的使用。到时候我会分享一个让我印象深刻的「500 错误」事故。敬请期待。