书接上文
查询的定义
依据上一章节内容的内容,REST LEVEL2 的查询只需要使用 GET 方法与 URI 定位到相应资源就可以了。但业务中往往涉及更多细节,比如查询的参数。
查询参数
我们所说的查询参数是指在 http method
为 GET 的查询 api 中,使用 urlencode 的传参方式在 URI 的末尾 ?
后面添加的相应参数。
如上所述,first-name 为你的查询参数对应的 key 值。通常我们会根据不同的业务信息,使用对应的 key 名称。不过,偶尔你也会看到下面这样的查询参数。
q 是 query 的缩写。为什么这里不使用 name 或某个业务 key 名称?因为 q 往往表示范围搜索。上述第一个示例表示搜索包含 jack 词组的所有用户列表。而第二个示例表示名称为 jack 的所有用户列表。
除了 instagram,类似比如 bing 也是这样做的。
https://www.bing.com/search?q=uriEncode(content) |
当 uri 中存在无法直接使用的字符时,会用一种百分号编码的方式对字符进行处理。例如%E3%81 等。你可能会认为使用 ASCII 字符集就可以高枕无忧了。但是应该各位注意,%、&、+等字符也需要百分号编码。而空格符号在百分号编码中会被编码为+号。
查询参数与 URI 如何取舍
下面这两个示例,你觉得哪个更好?
A 与 B 的区别在于 A 的设计将查询参数放到了 URI,而 B 的设计保持了查询参数的原本位置。可能在目前市面上 B 的设计更为常见一点。
类似于这样的查询参数与 URI 取舍的问题,可以采用下面这 2 个决策帮助进行判断。
- 参数是否表示唯一的资源所需的信息
- 参数是否和资源相关
如果参数能够定位到互联网上唯一的资源,则应该将查询参数放到 URI 中。这是基于 URI 的根本含义——统一资源定位符含义来进行设计的。
若查询参数无法配合 URI 定位,或要做的事和资源本身(上例中即为 user)无关的话,则应该放到查询参数位置。
所以在满足上述条件的情况下, A 在学术上优于 B。
查询的别名
某日你接到一个新需求:已登录的用户可以通过 api 查询用户个人详情。针对
此需求很容易设计出以下的代码:
response
{
"userId": "1987203",
"userName": "userA",
"userPassword": "12345"
}
回想一下你是不是设计过这样的端点?很可惜,你的代码实际上引发了一个巨大的安全隐患。 登录用户可以传入任何用户 id 即能查询到任意用户的数据。
针对这样的需求,在设计 api 时应该使用别名来代替用户 id 的传入,防止查询用户和查询自身这两个功能混淆,导致信息泄露的问题。
像上述示例中的 api,客户端访问以 me 或者 self 单词结尾的端点来获取个人信息。而针对其他用户的信息获取我们才采用下面这样的端点设计。
响应的设计
相对查询来说响应的设计更考究工程师的细节功底。
性别的设计
性别有两大主流设计。
- 使用数值来标识对应的性别。
- 使用 male、female。
实际上无论使用哪种都是可以接受的。但是你需要知道 14 年 facebook 的系统里性别就拥有了 50 种以上。(而川普上台让这一切又打回了原型 )所以如果考虑多元化那使用 2 比 1 的可扩展性更强一些。
日期格式的设计
在 Http 协议的定义中,http header 中通常会使用 UTC 来标识 Http 的时间。因此在设计 api 时,也推荐使用时区 +00:00
。相对于 epochtime 我更喜欢 RFC 3339
的日期格式,他更易读并且能够体现出时区的概念。而若要使用 UTC 时间的话,则在后面加上 Z 标识即可。
2017-11-11T13:00:12+00:00
2017-11-11T13:00:12Z
2017-11-11T10:00:00-00:00
-00:00
表示时区不明。这种表示很少见,但是正因为少见所以需要重点掌握。
整数的设计
计算机中 int 占用 4 个字节。如果使用 unsigned 无符号类型的话,则能表示 0 到 4294967295。如果你设计的是一个 SNS 网站,一定要避免用 int 作为返回值来标识用户的 id 或者其他信息,因为这点空间是完全不够的。将信息量较大的数据统统使用 long 甚至是 string 来表示。
分页查询的设计
相对位置分页
分页查询的重点在于分页参数,通常来说 limit=50&offset=100
很常见。但是由于查询往往会涉及到数据库的读操作,而在数据量很大的情况下基于 limit 与 offset 的查询组合非常可怕。如 limit=50000&offset=10000
这样的查询请求对服务器和客户端都是灾难。
绝对位置分页
有一种绝对位置分页的方法可作为你的备选项。它没有使用「从头开始第几条」的描述方法,而是指定了某个 ID 或者日期之前的条件。这样我们查询数据库时可以使用范围匹配来查询出数据返回到客户端,避免了使用数据库的分页功能从而造成性能隐患。
上面的端点用于请求查询17年8月15日之前注册的所有用户的信息列表。服务器端对于这样的请求 select * from user where time < 2017-08-15T00:00:00
使用即可解决问题,避免了从 1 开始计数的扫表操作。
有关分页的内容之前在下面的帖子种详细讨论过,这里不在赘述。
写在最后
- 我是 Chuck1sn,一个长期致力于现代 Jvm 生态推广的开发者。
- 您的回帖、点赞、收藏、就是我持续更新的动力。
- 举手之劳的一键三连,对我来说是莫大的支持,非常感谢!
- 关注我的账号,第一时间收到文章推送。
补充:
小雨(pcb_77) 佬友提到了还有一种既符合标准又能解决复杂搜索请求的方法,我把佬友的回复粘贴到本帖中来,供大家查阅使用。
对于某种复杂的搜索请求,可以拆分成两个:
- 先使用 POST request 先行创建 query parameters. 甚至可以返回对应的查询请求 URL。这解决了某些特别复杂的查询逻辑,在前端拼装查询参数不便的问题。
- 接着,再由客户端调用上面得到的 url,这个时候就是直接的 GET 请求。