我喜欢一些一些帮助处理一个奇怪的边缘情况与分页API我正在建设。

与许多api一样,这个api也会分页较大的结果。如果你查询/foos,你会得到100个结果(即foo #1-100),和一个链接到/foos?Page =2,返回foo #101-200。

不幸的是,如果在API使用者进行下一次查询之前从数据集中删除了foo #10, /foos?Page =2将偏移100并返回foos #102-201。

这对于试图获取所有foo的API使用者来说是一个问题——他们不会收到foo #101。

处理这种情况的最佳实践是什么?我们希望使它尽可能的轻量级(即避免为API请求处理会话)。来自其他api的示例将非常感谢!


当前回答

我认为目前你的api的反应应该是这样的。页面上的前100条记录按照您所维护的对象的总体顺序排列。您的解释告诉我们,您正在使用某种排序id来定义分页对象的顺序。

现在,如果您希望第2页始终从101开始,到200结束,那么您必须将该页上的条目数量作为变量,因为它们可能会被删除。

你应该做如下的伪代码:

page_max = 100
def get_page_results(page_no) :

    start = (page_no - 1) * page_max + 1
    end = page_no * page_max

    return fetch_results_by_id_between(start, end)

其他回答

根据您的服务器端逻辑,可能有两种方法。

方法1:当服务器不够智能,无法处理对象状态时。

您可以将所有缓存记录的唯一id发送到服务器,例如["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]和一个布尔参数,以知道您是在请求新记录(拉取以刷新)还是旧记录(加载更多)。

你的服务器应该负责返回新记录(加载更多的记录或通过拉取刷新的新记录)以及从["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]中删除的记录的id。

例子:- 如果你请求加载更多,那么你的请求应该看起来像这样:-

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]
}

现在假设你正在请求旧记录(加载更多),假设“id2”记录被某人更新,“id5”和“id8”记录从服务器上删除,那么你的服务器响应应该是这样的

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

但在这种情况下,如果你有很多本地缓存记录,假设500,那么你的请求字符串将太长,像这样:-

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10",………,"id500"]//Too long request
}

方法2:当服务器足够智能,可以根据日期处理对象状态时。

您可以发送第一个记录和最后一个记录的id以及前一个请求的纪元时间。这样,即使您有大量的缓存记录,您的请求也总是很小

例子:- 如果你请求加载更多,那么你的请求应该看起来像这样:-

{
        "isRefresh" : false,
        "firstId" : "id1",
        "lastId" : "id10",
        "last_request_time" : 1421748005
}

您的服务器负责返回last_request_time之后删除的记录的id,以及返回last_request_time之后在“id1”和“id10”之间更新的记录。

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

拉到刷新:-

加载更多

你有几个问题。

首先,你有你引用的例子。

如果插入行,也会遇到类似的问题,但在这种情况下,用户获得重复的数据(可以说比丢失数据更容易管理,但仍然是一个问题)。

如果您没有对原始数据集进行快照,那么这就是现实。

你可以让用户创建一个显式快照:

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

结果:

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

然后你可以一整天都在上面分页,因为它现在是静态的。这可以是相当轻的重量,因为您可以只捕获实际的文档键,而不是整个行。

如果用例只是你的用户想要(并且需要)所有的数据,那么你可以简单地给他们:

GET /query/12345?all=true

把全套装备都寄过来。

参考API分页设计,我们可以通过游标来设计分页API

他们有一个概念,叫做游标,它是指向一行的指针。你可以对数据库说"在那之后返回100行"对于数据库来说,这要容易得多,因为很有可能通过带索引的字段来标识行。这样你就不需要获取和跳过这些行了,你可以直接跳过它们。 一个例子:

  GET /api/products
  {"items": [...100 products],
   "cursor": "qWe"}

API返回一个(不透明的)字符串,你可以使用它来检索下一页:

GET /api/products?cursor=qWe
{"items": [...100 products],
 "cursor": "qWr"}

实现方面有许多选项。通常,您有一些排序标准,例如,产品id。在这种情况下,您将使用一些可逆算法(比如哈希)对产品id进行编码。在接收到带有游标的请求时,对其进行解码并生成类似WHERE id >:cursor LIMIT 100的查询。

优势:

通过游标可以提高数据库的查询性能 处理好时,新内容插入到db查询

劣势:

使用无状态API生成前一个页面链接是不可能的

我认为目前你的api的反应应该是这样的。页面上的前100条记录按照您所维护的对象的总体顺序排列。您的解释告诉我们,您正在使用某种排序id来定义分页对象的顺序。

现在,如果您希望第2页始终从101开始,到200结束,那么您必须将该页上的条目数量作为变量,因为它们可能会被删除。

你应该做如下的伪代码:

page_max = 100
def get_page_results(page_no) :

    start = (page_no - 1) * page_max + 1
    end = page_no * page_max

    return fetch_results_by_id_between(start, end)

可能很难找到最佳实践,因为大多数带有api的系统都不适应这种情况,因为这是一个极端的优势,或者它们通常不会删除记录(Facebook, Twitter)。Facebook实际上表示,由于分页后进行了过滤,每个“页面”可能没有请求的结果数量。 https://developers.facebook.com/blog/post/478/

如果你真的需要适应这种边缘情况,你需要“记住”你停止的地方。jandjorgensen的建议是正确的,但我将使用保证唯一的字段,如主键。您可能需要使用多个字段。

按照Facebook的流程,您可以(也应该)缓存已经请求的页面,如果它们请求已经请求过的页面,则只返回已删除的行。