3. URI 设计规范:资源命名规则、版本控制策略、路径参数与查询参数
聊到 RESTful API,URI 设计绝对是第一个要啃的硬骨头。我见过太多项目,API 上线半年后,光看 URL 就让人头大——有的用动词,有的用复数,版本号塞在 query 里,参数命名全靠心情。说白了,URI 就是你和前端、客户端之间的契约。契约写得烂,后面全是坑。
今天咱们就掰开揉碎,把资源命名、版本控制、参数设计这三块讲透。嗯,都是我在实际项目中踩过的坑,你听听看。
3.1 资源命名规则:名词复数,小写连字符
REST 的核心思想是「资源」。资源就是名词,不是动作。所以 URI 里不应该出现动词。
核心原则:
- 使用名词复数形式:
/users而不是/user或/getUser - 全部小写,单词间用连字符:
/order-items而不是/orderItems或/order_items - 避免动词:
/orders+ POST 就是创建,不需要/createOrder - 层级关系用斜杠表示:
/users/{id}/orders
举个例子。你要获取某个用户的订单列表。错误的写法是 /api/getUserOrders?userId=123。正确的写法是 /users/123/orders。你看,一眼就能看懂:用户 123 下面的订单集合。
我个人习惯在项目初期就定好命名规范文档。团队里新来的同事,照着文档写,基本不会跑偏。我曾经在一个项目里看到过 /api/v1/getAllActiveUsersList 这种 URI……嗯,那感觉就像在代码里看到 $dataArray 一样,想重构又不敢动。
小技巧:资源名尽量用业务领域内的通用术语。比如电商项目用 products、carts、orders,别自己发明 items_for_sale 这种怪名字。前后端沟通成本会低很多。
3.2 版本控制策略:放在路径里,别放 query 里
API 版本控制是个老生常谈的问题。我见过三种主流做法:
| 策略 | 示例 | 优缺点 |
|---|---|---|
| 路径版本 | /api/v1/users |
最直观,缓存友好,推荐 |
| 请求头版本 | Accept: application/vnd.myapp.v1+json |
URI 干净,但调试麻烦,不直观 |
| 查询参数版本 | /api/users?version=1 |
容易混淆,缓存困难,不推荐 |
我个人强烈推荐路径版本。为什么?
第一,直观。你在浏览器里直接敲 /api/v2/users 就能测试,不需要改请求头。第二,缓存友好。CDN 可以根据路径直接缓存不同版本。第三,迁移成本低。旧版本可以继续运行,新版本并行上线。
我曾经接手过一个项目,版本号放在 query 里:/api/users?v=1.2。结果前端传了个 v=1.2.3,后端没做校验,直接报 500。排查了半天,才发现是版本号格式不统一。从那以后,我坚持用路径版本,简单粗暴,不出幺蛾子。
注意:版本号建议用整数(v1, v2),不要用 v1.1、v2.3 这种小版本。小版本更新应该向后兼容,不需要改 URI。只有不兼容的变更才需要升大版本。
3.3 路径参数:标识具体资源
路径参数用来定位单个资源或资源集合。说白了,就是 URI 里那些花括号里的东西。
// 获取单个用户
GET /users/{id}
// 获取用户的某个订单
GET /users/{userId}/orders/{orderId}
// 嵌套资源
GET /categories/{categoryId}/products/{productId}
路径参数有几个设计要点:
- 使用唯一标识符:通常是自增 ID 或 UUID。我个人偏爱 UUID,虽然长一点,但防枚举、方便分库分表。
- 层级不要太深:超过三级嵌套就该考虑简化了。比如
/users/{id}/orders/{orderId}/items/{itemId}/details这种,读起来都费劲。 - 参数名要有意义:
{id}可以,但最好明确是{userId}还是{orderId},尤其当多个参数混在一起时。
你想想看,如果路径参数叫 {a}、{b},前端调用时得猜半天。我见过一个接口,路径是 /api/{type}/{id}/{sub},文档里写「type 是资源类型,id 是主键,sub 是子类型」。结果三个参数都是字符串,前端传错顺序,后端也不校验,数据直接写歪了。嗯,后来重构时我第一个改的就是这个接口。
3.4 查询参数:过滤、排序、分页
查询参数用来对资源集合做「非标识性」的操作。说白了,路径参数是「找哪个」,查询参数是「怎么找」。
常见的查询参数场景:
| 用途 | 示例 | 说明 |
|---|---|---|
| 过滤 | ?status=active&category=electronics |
按字段筛选 |
| 排序 | ?sort=created_at&order=desc |
指定排序字段和方向 |
| 分页 | ?page=1&per_page=20 |
控制返回数量 |
| 字段选择 | ?fields=id,name,price |
只返回需要的字段 |
| 搜索 | ?q=keyword |
全文或模糊搜索 |
这里有几个设计原则:
- 参数名用下划线或驼峰? 我个人建议统一用下划线(snake_case),因为 URI 本身不区分大小写,但参数值区分。下划线更清晰。
- 布尔值用 true/false:不要用 1/0 或 yes/no。前端传
?is_active=1,你后端还得转一下,何必呢? - 数组参数怎么传? 推荐重复参数名:
?status=active&status=pending。PHP 里用$_GET['status']直接拿到数组。 - 分页参数要统一:我习惯用
page和per_page,从 1 开始计数。也有团队用offset和limit,从 0 开始。选一种,文档写清楚,别混用。
避坑指南:我曾经在分页接口里同时支持了 page 和 offset,想着兼容旧版本。结果前端有的传 page=2,有的传 offset=20,后端逻辑里还要判断优先级。后来我直接废弃了 offset,统一用 page。少一个选择,少一堆 bug。
3.5 综合示例:一个完整的 URI 设计
咱们把上面讲的串起来,看一个完整的例子。
// 获取第2页,每页10条,状态为 active 和 pending 的订单
// 按创建时间降序排列,只返回 id、status、total 字段
GET /api/v1/orders?page=2&per_page=10&status=active&status=pending&sort=created_at&order=desc&fields=id,status,total
这个 URI 清晰表达了:
- 资源是
orders(名词复数) - 版本是
v1(路径版本) - 分页、过滤、排序、字段选择都用查询参数
你想想看,如果把这个 URI 拆成多个接口,比如 /api/v1/getActiveOrders、/api/v1/getPendingOrders、/api/v1/getOrdersSortedByDate……那得定义多少个端点?维护起来得多痛苦?
所以,好的 URI 设计,本质上是在做「抽象」。把资源抽象出来,把操作抽象成 HTTP 方法,把条件抽象成查询参数。这样,一个端点就能覆盖 80% 的业务场景。
总结一下:
- 资源用名词复数,小写连字符
- 版本放路径里,用整数
- 路径参数定位资源,查询参数过滤集合
- 参数命名统一,类型明确
- 层级不超过三级,保持简洁
嗯,URI 设计这块,说白了就是「约定大于配置」。定好规范,团队遵守,后面能省 80% 的沟通成本。我见过太多项目,前期图省事随便写,后期重构时哭都哭不出来。所以,从一开始就把 URI 设计好,比什么都重要。
下一章咱们聊聊 HTTP 方法的使用规范。GET、POST、PUT、DELETE 这些,你真的用对了吗?