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 路径版本。为什么?因为简单粗暴。前端同学调试时,直接在浏览器地址栏改个 v1v2 就能测试。你想想看,用请求头版本,还得装插件改 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

嗯,这里有几个细节要注意:

  • 分页参数名要统一:我建议用 pagepage_size。别今天用 page,明天用 offset,后天用 limit。团队内部定一个标准。
  • 排序方向用负号sort=-created_at 表示按创建时间降序。比 sort=created_at&order=desc 更简洁。
  • 字段选择要谨慎fields 参数虽然好用,但实现起来复杂。我建议只在数据量大的接口(如列表页)提供,详情页直接返回全部字段。

💡 小技巧: 查询参数的值尽量用枚举或标准格式。比如状态用 activeinactive,时间用 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 错误」事故。敬请期待。