Skip to content

分页与游标

数据墙DBW 使用游标分页(Cursor-based Pagination)来实现数据的分批返回。与传统的页码分页不同,游标分页在数据变动时也能保证翻页的一致性。REST 使用 $first$after,GraphQL 使用 firstafter

分页参数

参数RESTGraphQL说明
页大小$firstfirst每页返回的记录数
翻页游标$afterafter上一页返回的游标令牌

默认分页行为

默认每页最多返回 100 条记录。可通过 runtime.pagination 修改:

json
{
  "runtime": {
    "pagination": {
      "default-page-size": 50,
      "max-page-size": 1000
    }
  }
}

未传 $first/first 时使用默认值。传 -1 则返回配置的 max-page-size

REST 分页

基本用法

bash
# 每页 5 条
curl "http://localhost:5000/api/Book?\$first=5"

响应中带 nextLink 时表示还有更多数据:

json
{
  "value": [
    { "id": 1, "title": "第一本" },
    { "id": 2, "title": "第二本" },
    { "id": 3, "title": "第三本" },
    { "id": 4, "title": "第四本" },
    { "id": 5, "title": "第五本" }
  ],
  "nextLink": "http://localhost:5000/api/Book?$first=5&$after=WyJpZCI6NV0="
}

翻页

nextLink 中提取 $after 值,作为下一页请求的参数:

bash
curl "http://localhost:5000/api/Book?\$first=5&\$after=eyJpZCI6NX0="

$after 是一个 Base64 编码的游标令牌,客户端不需要解析其内容——直接用 nextLink 的值即可。

遍历全部数据(JavaScript)

javascript
async function fetchAllPages(baseUrl) {
  let url = `${baseUrl}/api/Book?$first=20`;
  const allItems = [];

  while (url) {
    const res = await fetch(url);
    const data = await res.json();
    allItems.push(...data.value);
    url = data.nextLink || null;
  }

  return allItems;
}

遍历全部数据(Python)

python
import requests

def fetch_all(base_url):
    url = f"{base_url}/api/Book?$first=20"
    items = []
    while url:
        res = requests.get(url)
        data = res.json()
        items.extend(data["value"])
        url = data.get("nextLink")
    return items

GraphQL 分页

基本用法

graphql
{
  books(first: 5) {
    items { id title }
    hasNextPage
    endCursor
  }
}

响应:

json
{
  "data": {
    "books": {
      "items": [
        { "id": 1, "title": "第一本" },
        { "id": 2, "title": "第二本" }
      ],
      "hasNextPage": true,
      "endCursor": "eyJpZCI6NX0="
    }
  }
}

翻页

将上一页的 endCursor 值传给 after

graphql
{
  books(first: 5, after: "eyJpZCI6NX0=") {
    items { id title }
    hasNextPage
    endCursor
  }
}

hasNextPagefalse 时已到最后一页。

遍历全部数据(JavaScript)

javascript
async function fetchAllPages(gqlUrl) {
  const allItems = [];
  let cursor = null;
  let hasNext = true;

  while (hasNext) {
    const res = await fetch(gqlUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        query: `query($after: String) {
          books(first: 20, after: $after) {
            items { id title }
            hasNextPage
            endCursor
          }
        }`,
        variables: { after: cursor }
      }),
    });
    const { data } = await res.json();
    const page = data.books;
    allItems.push(...page.items);
    hasNext = page.hasNextPage;
    cursor = page.endCursor;
  }

  return allItems;
}

分页与筛选/排序结合

分页与筛选排序一起使用时,注意顺序:先筛选,再排序,最后分页。

REST

bash
curl "http://localhost:5000/api/Book?\$filter=year%20ge%202000&\$orderby=pages%20desc&\$first=10"

GraphQL

graphql
{
  books(
    filter: { year: { gte: 2000 } }
    orderBy: { pages: DESC }
    first: 10
  ) {
    items { id title year pages }
    hasNextPage
    endCursor
  }
}

游标分页与页码分页的区别

特性游标分页(DAB)页码分页
翻页方式上一页的游标令牌页码数字(page=1, page=2)
数据变动时不会重复或遗漏可能重复或遗漏
跳页不支持支持跳转到任意页
实现复杂度低(跟随 nextLink)
性能稳定大偏移量时性能下降

数据墙DBW 选择游标分页的原因:数据库结果集在查询期间相对稳定,游标比页码偏移更高效,且不会因为中间插入新数据导致翻页结果错乱。

配置建议

场景default-page-sizemax-page-size
移动端、列表页面20~50200
后台管理、数据导出100~2001000
内部批量处理50010000

较小的默认值减少单次请求的数据库负载和网络传输量。客户端需要更多数据时再翻页。

下一步

数据墙DBW 产品文档与开发指南。