3. URI 设计规范:命名约定、版本号位置、层级与查询参数的使用
说到 URI 设计,我见过太多“想怎么来就怎么来”的接口了。有人用动词、有人用驼峰、有人把版本号藏在请求头里……最后维护起来那叫一个酸爽。今天咱们就把这根硬骨头啃干净——命名、版本号、层级、查询参数,一样一样说透。
3.1 命名约定:别让名字出卖你的设计
URI 里的资源名,说白了就是身份证。我个人的强迫症是:全小写、全名词、全复数、连字符连接。举个例子:
# ✅ 推荐
GET /api/v2/users/123/orders
GET /api/v2/products?category=electronics
# ❌ 别这么干
GET /api/v2/getUserOrders?userId=123
GET /api/v2/productList?cat=Electronics
为什么用复数?因为 /users 代表“用户集合”,/users/123 是集合里的单个元素。这种语义在 RESTful 里很自然。连字符 - 比下划线 _ 更易读,尤其在小屏幕上。我一开始用了下划线,后来被前端同事追着吐槽——“鼠标框选都费劲”。
核心原则:URI 描述资源,不是操作。操作全部交给 HTTP 方法(GET/POST/PUT/DELETE)。
3.2 版本号位置:放路径还是放查询?
版本号该怎么放?行业里基本上两派:路径派和查询参数派。我个人坚定地站路径派,原因是——一目了然。
| 位置 | 示例 | 优缺点 |
|---|---|---|
| 路径前缀 | /api/v1/users | 显式,易于缓存、路由;最常用 |
| 查询参数 | /api/users?version=1 | 隐蔽,容易被遗忘;缓存键难处理 |
| 请求头 | Accept: application/vnd.example.v1+json | 灵活但不直观,调试麻烦 |
| 子域名 | v1.api.example.com/users | DNS 配置成本高,不建议 |
你想想看,一个不熟悉你系统的人,看到 /api/v1/users 立刻就知道这是第1版。如果放在 ?version=1,他可能翻半天文档才找到。我之前接手过一个老项目,版本号放在 Accept Header 里——每次用 curl 都要加 -H,简直想骂人。
我的建议:版本号放在路径最前面,紧跟在 /api 之后。比如 /api/v2/orders。这样路由、监控、文档都能统一处理。
3.3 层级结构:别超过三层
资源之间的层级,应该反映现实世界的从属关系。比如“用户的订单”:
GET /api/v2/users/123/orders/456
这里 users 是顶层,orders 是子资源。但要注意,层级别太深。我见过 /api/v2/schools/1/classes/2/students/3/exams/4/questions/5 这种——五个层级,谁看了都想吐。
避坑:层级最好不要超过3层。如果需要更深的关系,考虑用查询参数、反关联或者引入查询对象(如 GraphQL)。我曾经在项目中死磕四级嵌套,结果前端每次调用都要拼出一长串 ID,维护成本巨高。
实在需要多级,可以用展平的方式:
# 深度嵌套
GET /api/v2/schools/1/classes/2/students/3
# 展平(用查询参数)
GET /api/v2/students?school_id=1&class_id=2
两者都合理,看上下文。我个人倾向于:如果子资源有独立的生命周期(比如订单可以单独删除),就用嵌套;否则用展平。
3.4 查询参数:过滤、排序、分页的统一约定
查询参数用来对结果做“加工”,不是用来定位资源的。记住:路径定身份,参数定视图。常见场景:
# 过滤
GET /api/v2/orders?status=paid&amount_min=100
# 排序
GET /api/v2/orders?sort=-created_at&sort=id
# 分页
GET /api/v2/orders?page=1&per_page=20
# 字段选取
GET /api/v2/orders?fields=id,status,total
这里我踩过一个坑:排序字段用 sort=created_at,asc 还是 sort=+created_at?后来我统一用 - 表示降序、+ 表示升序(可省略),因为这样排序多个字段时更紧凑。
推荐参数名:page、per_page、sort、fields、q(搜索关键字)。不要用 p、limit 那种容易混淆的缩写。
另外要注意:查询参数里如果包含空格、中文、特殊符号,必须进行百分比编码。我曾经在生产环境遇到有人传 ?name=张三,结果中间代理直接把这个非 ASCII 字符截断了……最后还是乖乖用 %E5%BC%A0%E4%B8%89 才搞定。
3.5 一张图看懂 URI 设计全貌
下面这张图把上面四点串在一起,以后设计 URI 时照着它来就行了。
3.6 避坑指南:我踩过的三个坑
- 看似规范,实则混乱:曾经用驼峰命名(
getUserOrders),后来团队扩招,新来的同事不习惯,一半人继续用驼峰,一半人用全小写。等我想统一时,前端缓存已经散落一地。所以,一开始就定死规范比什么都重要。 - 版本号放查询参数:有个项目为了方便,把版本号放在查询参数里。结果缓存服务根据完整 URI 做 key,导致
/api/users?version=1和/api/users?version=2被认为是两个不同的资源,缓存全部失效。后来改成/api/v1/users,问题解决。 - 深层嵌套导致性能裂化:设计了一个四层嵌套的资源(
/a/b/c/d),每次请求都要查四次数据库。后来加了一个旁路索引,用一对多关系直接把深层资源拉到顶层,速度提升了 10 倍。所以别死磕“资源必须严格层级对应”——灵活一点,用查询参数代替深层路径。
一句话总结:URI 是 API 的身份证,命名用名词复数+连字符,版本号放路径前缀,层级不超过三层,查询参数专用于过滤/排序/分页。照着这个套路走,以后谁接手你的 API 都会夸你一句:这接口写得真规整。