93

我想在我的 RESTful API 中支持分页。

我的 API 方法应该通过/products/index. 但是,可能有数千种产品,我想对它们进行分页,所以我的请求应该如下所示:

/products/index?page_number=5&page_size=20

但是我的 JSON 响应需要是什么样的?API 使用者通常会期望响应中的分页元数据吗?还是只需要一系列产品?为什么?

看起来 Twitter 的 API 包含元数据:https ://dev.twitter.com/docs/api/1/get/lists/members (请参阅示例请求)。

使用元数据:

{
  "page_number": 5,
  "page_size": 20,
  "total_record_count": 521,
  "records": [
    {
      "id": 1,
      "name": "Widget #1"
    },
    {
      "id": 2,
      "name": "Widget #2"
    },
    {
      "id": 3,
      "name": "Widget #3"
    }
  ]
}

只是一个产品数组(没有元数据):

[
  {
    "id": 1,
    "name": "Widget #1"
  },
  {
    "id": 2,
    "name": "Widget #2"
  },
  {
    "id": 3,
    "name": "Widget #3"
  }
]
4

5 回答 5

124

ReSTful API 主要由其他系统使用,这就是我将分页数据放在响应标头中的原因。但是,某些 API 使用者可能无法直接访问响应标头,或者可能正在您的 API 上构建 UX,因此提供一种(按需)检索 JSON 响应中的元数据的方法是一个优势。

我相信你的实现应该包括机器可读的元数据作为默认值,并在请求时包括人类可读的元数据。如果您愿意,可以随每个请求返回人类可读的元数据,或者最好通过查询参数按需返回,例如include=metadataor include_metadata=true

在您的特定场景中,我会在记录中包含每个产品的 URI。这使 API 使用者可以轻松地创建指向各个产品的链接。我还会根据我的寻呼请求的限制设置一些合理的期望。实施和记录页面大小的默认设置是一种可接受的做法。例如,GitHub 的 API将默认页面大小设置为 30 条记录,最多 100 条,另外还设置了查询 API 次数的速率限制。如果您的 API 具有默认页面大小,则查询字符串可以只指定页面索引。

在人类可读的场景中,当导航到 时/products?page=5&per_page=20&include=metadata,响应可能是:

{
  "_metadata": 
  {
      "page": 5,
      "per_page": 20,
      "page_count": 20,
      "total_count": 521,
      "Links": [
        {"self": "/products?page=5&per_page=20"},
        {"first": "/products?page=0&per_page=20"},
        {"previous": "/products?page=4&per_page=20"},
        {"next": "/products?page=6&per_page=20"},
        {"last": "/products?page=26&per_page=20"},
      ]
  },
  "records": [
    {
      "id": 1,
      "name": "Widget #1",
      "uri": "/products/1"
    },
    {
      "id": 2,
      "name": "Widget #2",
      "uri": "/products/2"
    },
    {
      "id": 3,
      "name": "Widget #3",
      "uri": "/products/3"
    }
  ]
}

对于机器可读的元数据,我会在响应中添加链接头:

Link: </products?page=5&perPage=20>;rel=self,</products?page=0&perPage=20>;rel=first,</products?page=4&perPage=20>;rel=previous,</products?page=6&perPage=20>;rel=next,</products?page=26&perPage=20>;rel=last

(链接标头值应为 urlencoded)

...total-count如果您选择,可能还有自定义响应标头:

total-count: 521

以人为中心的元数据中显示的其他分页数据对于以机器为中心的元数据可能是多余的,因为链接标题让我知道我在哪个页面以及每页的数量,并且我可以快速检索数组中的记录数. 因此,我可能只会为总数创建一个标题。您可以随时改变主意并添加更多元数据。

顺便说一句,您可能会注意到我/index从您的 URI 中删除了。一个普遍接受的约定是让您的 ReST 端点公开集合。最后/index有点混乱。

这些只是我在使用/创建 API 时喜欢的一些东西。希望有帮助!

于 2012-08-29T05:01:46.777 回答
30

作为编写了多个使用 REST 服务的库的人,让我从客户的角度来说明为什么我认为将结果包装在元数据中是可行的方法:

  • 没有总计数,客户端怎么知道它还没有收到所有的东西,应该继续对结果集进行分页?在没有执行前瞻到下一页的 UI 中,在最坏的情况下,这可能表示为实际上没有获取更多数据的 Next/More 链接。
  • 在响应中包含元数据允许客户端跟踪较少的状态。现在我不必将我的 REST 请求与响应匹配,因为响应包含重建请求状态所需的元数据(在本例中是光标进入数据集)。
  • 如果状态是响应的一部分,我可以同时对同一个数据集执行多个请求,并且我可以按照它们碰巧到达的任何顺序处理请求,不一定是我发出请求的顺序。

还有一个建议:像Twitter API一样,您应该将 page_number 替换为直接索引/光标。原因是,API 允许客户端根据请求设置页面大小。返回的 page_number 是客户端到目前为止请求的页数,还是给出最后使用的 page_size 的页数(几乎可以肯定是后者,但为什么不完全避免这种歧义)?

于 2012-08-29T00:13:39.573 回答
29

我建议添加相同的标题。将元数据移动到标头有助于摆脱像resultdata或之类的信封,records并且响应正文仅包含我们需要的数据。如果您也生成分页链接,则可以使用链接标题。

    HTTP/1.1 200
    Pagination-Count: 100
    Pagination-Page: 5
    Pagination-Limit: 20
    Content-Type: application/json

    [
      {
        "id": 10,
        "name": "shirt",
        "color": "red",
        "price": "$23"
      },
      {
        "id": 11,
        "name": "shirt",
        "color": "blue",
        "price": "$25"
      }
    ]

详情请参阅:

https://github.com/adnan-kamili/rest-api-response-format

对于招摇文件:

https://github.com/adnan-kamili/swagger-response-template

于 2016-08-02T13:41:20.407 回答
1

只需将您的后端 API 新属性添加到响应正文中。从示例.net核心:

[Authorize]
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery]UserParams userParams)
{
  var users = await _repo.GetUsers(userParams);
  var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);


  // create new object and add into it total count param etc
  var UsersListResult = new
  {
    usersToReturn,
    currentPage = users.CurrentPage,
    pageSize = users.PageSize,
    totalCount = users.TotalCount,
    totalPages = users.TotalPages
  };

  return Ok(UsersListResult);
}

在身体反应中,它看起来像这样

{
"usersToReturn": [
    {
        "userId": 1,
        "username": "nancycaldwell@conjurica.com",
        "firstName": "Joann",
        "lastName": "Wilson",
        "city": "Armstrong",
        "phoneNumber": "+1 (893) 515-2172"
    },
    {
        "userId": 2,
        "username": "zelmasheppard@conjurica.com",
        "firstName": "Booth",
        "lastName": "Drake",
        "city": "Franks",
        "phoneNumber": "+1 (800) 493-2168"
    }
],
// metadata to pars in client side
"currentPage": 1,
"pageSize": 2,
"totalCount": 87,
"totalPages": 44

}

于 2020-03-12T19:55:37.590 回答
-3

通常,我通过简单的方式制作,无论如何,我使用这些参数创建一个restAPI端点,例如“localhost/api/method/:lastIdObtained/:countDateToReturn”,你可以做一个简单的请求。在服务中,例如。。网

jsonData function(lastIdObtained,countDatetoReturn){
'... write your code as you wish..'
and into select query make a filter
select top countDatetoreturn tt.id,tt.desc
 from tbANyThing tt
where id > lastIdObtained
order by id
}

在Ionic中,当我从下到上滚动时,我传递零值,当我得到答案时,我设置最后获得的id的值,当我从上到下滑动时,我传递我得到的最后一个注册id

于 2019-07-19T01:19:17.440 回答