本节摘要:URI 是资源的"地籍图",每一处设计决策都有考据:用名词而不用动词、集合用复数、用层级表达从属、小写加连字符增强可读、避免扩展名与查询类操作。本节先把一套能落地的 URI 规范逐条讲清,再用一张设计模式总览图把"何时这样写"归位,最后做一次"坏 URI 打磨成好 URI"的演练。
阅读完本节,你应当能够:
不少人把 URI 当成"一串能点的网址"来写,于是下意识往里塞动词、塞扩展名、塞一堆口水词。但 URI 在 REST 里承担的责任更重:它是资源在系统里的地籍编号——就像一块地的编号,一要唯一,二要稳定,三要能看懂这块地归谁管、在哪个区。编号写得乱,后续版本控制、链接、缓存、监控全跟着乱。
把一套常用规范逐条拆开,每一条都连着理由:
| 规范 | 示例 | 理由 |
|---|---|---|
| 全小写 | /users 而非 /Users |
URI 对大小写敏感,统一小写消除混淆 |
| 用连字符分隔单词 | /product-orders 而非 /product_orders |
连字符在部分场景可读性更好也更规范 |
| 集合用复数 | /users、/orders |
语义统一:资源是集合,实例是其中一员 |
| 方法表达动词,路径放名词 | POST /users 而非 /createUser |
动词交给 HTTP 方法,路径只留名词 |
| 层级表达从属 | /users/123/orders |
用斜杠表达资源间从属关系 |
| 避免扩展名 | /book/42 而非 /book/42.json |
格式交由头协商,不让格式进地籍 |
把上表单独拎出一种最容易踩的:下划线与连字符之争(product_orders vs product-orders)。下划线本身不算错,但问题出在某些环境里下划线会与字体、正则、复制粘贴错位纠缠;连字符读起来更像自然词分节,也躲开这些乱子。规范一旦定下全册统一用连字符,就只管遵守,别在一份契约里两样混着来——地籍编号最怕的是"同一种资源今天这样写、明天那样写"。
查询参数这一层同样该有命名约定,否则查询面越用越乱。常用的三把尺:筛选字段与路径字段同名(?status=),一组来自某实体的筛选放平级参数而非复合表达式(?author=&year= 好过 ?filter="author:小七,year:2020"),分页统一叫 page/limit 或 offset/limit 并在一份契约里二选一。命名稳定之后,客户端写查询、文档做速查、网关做缓存键都对着同一套字典,摩擦骤减。
把"什么时候这样写"归纳成一张总览图,比背规则表更好记:

把一条反模式 URI 打磨成合规范的样子,直观感受每笔改动:
打磨前:/getUserByEmail?email=alice&app=v2 或 /Users/Alice/Orders_list.json
打磨后:/users(查用户靠 GET /users?email=alice)、/users/99/orders?page=2(按 page 分页)
每一处改动的理由:
/getUserByEmail 里的"get"是多此一举,方法本身就是 GET;按 email 过滤是查询意图,放查询参数。Users 大写改 users,避免大小写歧义;Alice 作为标识应换成稳定的数字 id;Orders_list.json 的扩展名与下划线一并清理,改成 /users/99/orders 并按分页参数取数。⚠️ 常见坑:把"查询的角度"塞进永久路径,如
/users/byEmail、/reports/activeInactive。这些是过滤/分组语义,放查询参数更合适——因为它们在语义上是同一份资源的不同视角,而非不同资源。
URI 一旦对外发布,就成了别的系统、缓存、历史链接、书签的锚点。改 URI 等于改地号,一改全崩。所以设计时就要把"将来要过滤、要分页、要加子域"的余地超前留给查询参数,而不是等封地后再改路径。这也是为什么版本号常放路径最前面(/v1/users),把体量最大的职权留给完整的版本块,细节下的取舍第 3 章版本节专门展开。
💡 关键直觉:把 URI 当资源地籍,把查询参数当"从哪个角度看这块地"。这样写出来的接口,客户端想 follow、监控想归类、缓存想对接都自然顺手。
地籍写规范了,还剩四处边界最经不起反目,列出来一次钉死:
/users/ 和 /users 应视作同一份资源。规范里往往二选一起用,最忌负载均衡或网关各自按不同写法缓存,结果同一资源出两套报头。GET /users?role=nobody 返回空数组而非报错,GET /users/404(不存在的 id)回 404。一份集合返回空、一个实例没找到,是两种完全不同的话,别混写成一个语义。/users/99 的 99 是标识,进路径;?status=paid 的 paid 是视角,进查询。判断标准就一条——换掉它会不会变成另一份资源,会则进路径,不会则以参数看待。/users;lang=zh 这类分号参数可读性差、可缓存性差,多数团队直接用查询参数替代,除非做严格的内容协商场景。这四处定完,URI 的地籍才是真正"唯一、稳定、可读"三把尺全过。
地籍图就位,接下来给每个资源分配合适的动词——HTTP 方法语义这一笔,直接决定接口是"顺手"还是"别扭"。