根据@jaco0646 的要求
TL;DR
- 核心
user 资源,带有嵌入式子资源,如地址、组、帖子或下午。 (/api/v1/users/{user_uuid})
-
users 还将包含一个名为 views 的嵌入式资源,用于处理当前注册的视图 (/api/v1/users/{user_uuid}/views/{some_view})
-
view 是使用 POST 请求(即来自 HTML 表单)创建的,其中包括选定的子资源
- 每个视图都包含核心
user 数据和所选字段的数据
- 如果所有视图都以核心
user 数据开头,则使用部分GET 请求可以只下载所需的数据;虽然可能有其局限性
当前答案的问题
在我发布解决某些属性过滤问题的方法之前,我想快速了解一下为什么我不同意 @jaco0646、@Yoram 和 @JoseMartinez 目前给出的答案(它们都是相同的 IMO )
响应内容的缓存
HTTP 尝试通过缓存响应来减少网络开销。对同一资源的第二次查找最好从本地缓存中查找,而不是直接从服务器实际查询和下载结果。如果资源数据不经常更改,这将特别有用。
使用某些缓存控制标头和If-Modified-Since 请求标头,客户端可以通过加载当前内容并缓存响应来影响是使用缓存内容还是刷新缓存。然而,GET 带有查询参数的请求通常被认为是被排除在缓存之外的,这更像是一个城市传说而不是实际情况。但是,某些实现可能会避免缓存此类资源。根据 RFC 7234,缓存应该使用 effective request URI 到 reconstruct stored responses,默认情况下是目标 URI,包括任何查询、矩阵和路径参数。因此,整个 URI 被认为是用于存储和访问响应的密钥。
部分 GET 请求和用例
正如 jaco 在他的帖子中提到的,除了标准 GET 和条件 GET 请求之外,HTTP 协议还定义了一个 partial GET request,它允许客户端仅请求资源的一部分而不是全部资源。
虽然这听起来不错,但部分 GET 请求至少在 HTTP/1.1 中具有 only works on bytes 的限制。
HTTP/1.1 定义的唯一范围单位是“字节”。
Range 标头允许在请求中添加多个字节段,以在响应中包含多个段:
GET /someResource HTTP/1.1
Host: http://some-host.com/
Range: 500-700,1200-
部分请求只要求下载(包括)500-700 之间的字节以及从字节 1200 到结尾的所有内容。
通常使用部分 GET 请求来恢复中断的下载或缓冲正在运行的流,因为确切下载的字节是已知的。但是,如何提前指定每个过滤字段的字节范围?如果没有先验知识,我认为这是行不通的。
网址大小限制
如果有许多字段可用于过滤,使用带有查询或矩阵参数的GET 请求可能会导致某些浏览器问题,因为某些浏览器有2000 characters 的限制。
虽然这可能不会对 OP 问题产生影响,但需要详尽过滤属性的其他用户可能会遇到此问题。
资源和子资源
ReST 的重点是资源以及 HTTP 协议提供的与资源交互的方法。
用户资源,即具有某些“核心”数据,例如用户名、ID 以及其他特定于域的内容。但它也有额外的数据,比如地址,......这也可能是用户资源的一部分。
ReSTfull 应用程序不是将每个属性混合到一个实体中,而是尝试拥有大量资源。就像上面的示例一样,user 和 address 只是两个名称,但肯定还有更多。如果您开始使用 ReSTfull 设计,可能不清楚某些数据应该是该资源的一部分还是重构为自己的资源。这里的经验法则是,如果您需要至少两个不同资源中的某些数据,请重构它并将其嵌入到这些资源中。
将大(er)资源划分为层次结构允许在发生更改(如地址更改)时轻松更新(在纯 HTTP 意义上,用新内容替换资源 X 上当前可用的内容)子资源用户)同时拥有一个大资源来处理所有数据需要将整个实体主体(如果使用得当)发送到服务器,而不仅仅是更改。
实体格式
大量“ReSTfull”服务以application/xml 或application/json 格式交换数据。但是,两者都没有传达太多语义。他们只是列出了可能在客户端验证的使用的语法规则。但他们没有对实际内容给出任何暗示。因此,客户还必须具备有关如何处理以其中一种格式接收的数据的先验知识。
如果 JSON 是您选择的表示格式,我会改用 JSON HAL (application/hal+json),因为它定义了核心数据、链接和嵌入内容,这对于呈现的 IMO 场景尤其有用。
建议的解决方案
建议的方法有一个核心 user 资源,它嵌入了某些子资源,如地址、组、帖子或 pm。它还将包含一个名为views 的嵌入式资源,它为用户或一般用户处理当前注册的视图。 view 是通过发送 POST 请求(即来自 HTML 表单)创建的,其中包括要包含在响应中的选定子资源。
核心资源是user 资源,可能在/api/v1/users/{user_uuid} 上可用,默认情况下仅包含用户核心数据和其他资源的链接
{
"firstName": "Maria",
"lastName": "Sample",
...
"_links": {
"self": {
"href": "/api/users/1234-5678-9123-4567"
},
"addresses": [
{ "href": "/api/users/1234-5678-9123-4567/addresses/abc1" }
],
"groups": [
{ "href": "/api/users/1234-5678-9123-4567/groups" }
],
"posts": [
{ "href": "/api/users/1234-5678-9123-4567/posts" }
],
...
"views: [
{ "href": "/api/users/1234-5678-9123-4567/views/view-a" },
{ "href": "/api/users/1234-5678-9123-4567/views/view-b" }
]
}
}
任何子资源都可通过用户资源 URI:/api/v1/users/1234-5678-9123-4567/{sub_resource},其中 sub_resource 可能是以下之一:addresses、groups、posts、...
地址的实际子资源可能如下所示
{
"street": "Sample Street"
"city": "Some City"
"zipCode": "12345"
"country": "Neverland"
...
"_links": {
"self": {
"href": "/api/v1/users/1234-5678-9123-4567/addresses/abc1"
},
"googleMaps": {
"href": "http://maps.google.com/?ll=39.774769,-74.86084"
}
}
}
虽然用户有两个这样的帖子
{
"id": 1;
"date": "2016-02-21'T'14:06:20.345Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/1"
}
}
}
{
"id": 2;
"date": "2016-02-21'T'14:34:50.891Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/2"
}
}
}
包含addresses 和posts 的视图(/api/users/1234-5678-9123-4567/views/view-a)可能如下所示:
{
"firstName": "Maria",
"lastName": "Sample",
...
"_links": {
"self": {
"href": "/api/users/1234-5678-9123-4567"
},
"addresses": [
{ "href": "/api/users/1234-5678-9123-4567/addresses/abc1" }
],
"groups": [
{ "href": "/api/users/1234-5678-9123-4567/groups" }
],
"posts": [
{ "href": "/api/users/1234-5678-9123-4567/posts" }
],
...
"views: [
{ "href": "/api/users/1234-5678-9123-4567/views/view-a" },
{ "href": "/api/users/1234-5678-9123-4567/views/view-b" }
]
},
"_embedded": {
"addresses:" : [
{
"street": "Sample Street"
"city": "Some City"
"zipCode": "12345"
"country": "Neverland"
...
"_links": {
"self": {
"href": "/api/v1/users/1234-5678-9123-4567/addresses/abc1"
},
"googleMaps": {
"href": "http://maps.google.com/?ll=39.774769,-74.86084"
}
}
}
],
"posts": [
{
"id": 1;
"date": "2016-02-21'T'14:06:20.345Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/1"
}
}
},
{
"id": 2;
"date": "2016-02-21'T'14:34:50.891Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/2"
}
}
}
]
}
}
另一个视图(即/api/users/1234-5678-9123-4567/views/view-b)可能只包括所选用户完成的posts:
{
"firstName": "Maria",
"lastName": "Sample",
...
"_links": {
"self": {
"href": "/api/users/1234-5678-9123-4567"
},
"addresses": [
{ "href": "/api/users/1234-5678-9123-4567/addresses/abc1" }
],
"groups": [
{ "href": "/api/users/1234-5678-9123-4567/groups" }
],
"posts": [
{ "href": "/api/users/1234-5678-9123-4567/posts" }
],
...
"views: [
{ "href": "/api/users/1234-5678-9123-4567/views/view-a" },
{ "href": "/api/users/1234-5678-9123-4567/views/view-b" }
]
},
"_embedded": {
"posts": [
{
"id": 1;
"date": "2016-02-21'T'14:06:20.345Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/1"
}
}
},
{
"id": 2;
"date": "2016-02-21'T'14:34:50.891Z",
"text": "Lorem ipsum ...",
"_links": {
"self: {
"href": "/api/users/1234-5678-9123-4567/posts/1"
}
}
}
]
}
}
在调用/api/users/1234-5678-9123-4567/views 时,您可能会显示当前可用视图的列表以及 HTML 表单(或某些自定义 UI),您可以在其中为要包含或排除的每个可用字段设置复选框。在将表单数据发送到服务器时,它将检查给定属性的视图是否已经存在(如果存在409 Conflict)并创建一个新视图,以后可能会重用。您还可以在_links 部分的views 段中命名视图并包含某些选定的属性。
除了为每个用户指定一个视图外,您还可以为所有用户创建一次通用视图,然后按照您的意愿重复使用它们。
由于视图没有查询参数,因此整个响应是可缓存的。当您使用POST 请求创建视图时(如果幂等性是一个问题,请使用空的POST 请求,然后是PUT 请求),您可以使用几乎无限的参数。这种HAL 类似的方言对views 使用了自己的逻辑。因此,创建自己的内容类型可能也是一个好主意:application/vnd+users.views+hal+json
关于部分GET 请求:
由于每个视图的核心user 数据都是相同的,因此可以使用核心数据的长度(减去右括号和倒数第二个括号后的任何空白字符)并发出部分@987654380 @ 请求服务器。它应该只响应嵌入的数据(和最后的右括号),尽管我不确定当前的浏览器是否真的能够相应地更新当前的数据,特别是如果已知内容的某些字节需要像最后一样被删除核心user数据的括号。