You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure Table Storage分页实现:前后页切换与键值追踪问询

我明白你在Azure Table Storage实现分页时的核心痛点——既要跟踪每一页的nextPartitionKey和nextRowKey,又要实现流畅的上下页切换。结合Gaurav Mantri的分页思路,我给你一套完整的实现方案,核心是用一个数组来维护分页标记历史,让前后页切换变得简单直接。

核心思路

我们需要维护三个核心状态:

  1. currentPageIndex:当前所在的页码索引(从0开始)
  2. paginationTokens:数组,存储每一页对应的起始续传标记({ partitionKey, rowKey }或null),比如paginationTokens[0]对应第一页的空标记(无需续传),paginationTokens[1]对应第二页的起始标记
  3. pageSize:每页固定显示的实体数量

点击下一页时:

  • 如果是已经加载过的页面,直接复用数组中存储的标记
  • 如果是新页面,查询后将新的续传标记追加到数组中

点击上一页时:

  • 直接通过currentPageIndex-1获取数组中存储的上一页标记,重新查询数据

完整代码示例

前端(HTML + JavaScript)

负责页面渲染、状态管理和前后端交互:

<!-- 分页控制按钮 -->
<div class="pagination-controls">
  <button id="prevBtn" disabled>上一页</button>
  <span id="pageInfo">第1页</span>
  <button id="nextBtn">下一页</button>
</div>

<!-- 实体列表展示区域 -->
<div id="entityList"></div>

<script>
// 分页配置与状态
const PAGE_SIZE = 10;
let currentPageIndex = 0;
// 存储每一页的起始续传标记,初始时第一页无需标记
const paginationTokens = [null];

// 获取实体数据的核心函数
async function fetchPageEntities(continuationToken) {
  const params = new URLSearchParams();
  params.append('pageSize', PAGE_SIZE);
  
  if (continuationToken) {
    params.append('nextPartitionKey', continuationToken.partitionKey);
    params.append('nextRowKey', continuationToken.rowKey);
  }

  try {
    const response = await fetch('/api/get-table-entities', {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    });
    return await response.json();
  } catch (err) {
    console.error('获取数据失败:', err);
    return { entities: [], nextToken: null };
  }
}

// 渲染实体列表到页面
function renderEntities(entities) {
  const listEl = document.getElementById('entityList');
  listEl.innerHTML = '';
  
  if (entities.length === 0) {
    listEl.innerHTML = '<p>暂无数据</p>';
    return;
  }

  entities.forEach(entity => {
    const itemEl = document.createElement('div');
    itemEl.className = 'entity-item';
    itemEl.innerHTML = `
      <strong>PartitionKey:</strong> ${entity.PartitionKey}<br>
      <strong>RowKey:</strong> ${entity.RowKey}<br>
      <strong>内容:</strong> ${entity.Description || '无描述'}
    `;
    listEl.appendChild(itemEl);
  });
}

// 更新按钮状态与页码信息
function updateUIState() {
  const prevBtn = document.getElementById('prevBtn');
  const nextBtn = document.getElementById('nextBtn');
  const pageInfo = document.getElementById('pageInfo');

  // 上一页按钮:只有当前页不是第一页时启用
  prevBtn.disabled = currentPageIndex === 0;
  // 下一页按钮:如果最后一个标记为null,说明已到最后一页
  const hasNextPage = paginationTokens[currentPageIndex + 1] !== null;
  nextBtn.disabled = !hasNextPage;

  pageInfo.textContent = `第${currentPageIndex + 1}页`;
}

// 初始化加载第一页
async function init() {
  const { entities, nextToken } = await fetchPageEntities(paginationTokens[0]);
  renderEntities(entities);
  
  // 如果有下一页,将标记存入数组
  if (nextToken) {
    paginationTokens.push(nextToken);
  } else {
    // 标记最后一页,避免重复查询
    paginationTokens.push(null);
  }
  updateUIState();
}

// 上一页按钮点击事件
document.getElementById('prevBtn').addEventListener('click', async () => {
  if (currentPageIndex <= 0) return;
  
  currentPageIndex--;
  const { entities } = await fetchPageEntities(paginationTokens[currentPageIndex]);
  renderEntities(entities);
  updateUIState();
});

// 下一页按钮点击事件
document.getElementById('nextBtn').addEventListener('click', async () => {
  // 检查是否已经加载过下一页的标记
  let targetToken = paginationTokens[currentPageIndex + 1];
  
  // 如果是未加载过的新页面,用当前页的下一个标记查询
  if (currentPageIndex + 1 === paginationTokens.length - 1 && targetToken !== null) {
    const { entities, nextToken } = await fetchPageEntities(targetToken);
    renderEntities(entities);
    
    // 将新的标记存入数组
    paginationTokens.push(nextToken || null);
  } else {
    // 已加载过的页面,直接复用标记查询
    const { entities } = await fetchPageEntities(targetToken);
    renderEntities(entities);
  }
  
  currentPageIndex++;
  updateUIState();
});

// 页面加载完成后初始化
window.addEventListener('load', init);
</script>

后端(Node.js + Azure Tables SDK)

负责处理分页查询请求,返回实体数据和续传标记:

const express = require('express');
const { TableClient, AzureNamedKeyCredential } = require('@azure/data-tables');
const app = express();

// 配置Azure Table Storage连接信息
const STORAGE_ACCOUNT_NAME = '你的存储账户名';
const STORAGE_ACCOUNT_KEY = '你的存储账户密钥';
const TABLE_NAME = '你的表名';

// 初始化Table客户端
const credential = new AzureNamedKeyCredential(STORAGE_ACCOUNT_NAME, STORAGE_ACCOUNT_KEY);
const tableClient = new TableClient(
  `https://${STORAGE_ACCOUNT_NAME}.table.core.windows.net`,
  TABLE_NAME,
  credential
);

// 分页查询API
app.get('/api/get-table-entities', async (req, res) => {
  const pageSize = parseInt(req.query.pageSize) || 10;
  const nextPartitionKey = req.query.nextPartitionKey;
  const nextRowKey = req.query.nextRowKey;

  try {
    const queryOptions = { maxPerPage: pageSize };
    
    // 如果有续传标记,添加到查询参数中
    if (nextPartitionKey && nextRowKey) {
      queryOptions.continuationToken = {
        nextPartitionKey,
        nextRowKey
      };
    }

    // 获取一页数据
    const pageIterator = tableClient.listEntities(queryOptions).byPage();
    const pageResult = await pageIterator.next();
    
    const entities = [];
    let nextToken = null;

    if (pageResult.value) {
      // 遍历当前页的所有实体
      for await (const entity of pageResult.value) {
        entities.push(entity);
      }
      // 获取下一页的续传标记
      nextToken = pageResult.value.continuationToken;
    }

    // 构造返回结果,将续传标记格式化为前端易处理的对象
    res.json({
      entities,
      nextToken: nextToken ? {
        partitionKey: nextToken.nextPartitionKey,
        rowKey: nextToken.nextRowKey
      } : null
    });
  } catch (err) {
    console.error('查询Table失败:', err);
    res.status(500).json({ error: '查询数据失败,请稍后重试' });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`服务器运行在端口 ${PORT}`);
});

关键细节说明

  1. 分页标记存储:paginationTokens数组会记录所有已访问页面的起始标记,避免重复查询和计算,提升切换效率
  2. 按钮状态管理:根据当前页码和是否有下一页,动态禁用/启用按钮,避免无效操作
  3. 续传标记处理:后端将Azure SDK返回的continuationToken格式化为前端易处理的对象,前端只需传递partitionKey和rowKey即可

内容的提问来源于stack exchange,提问作者Z Pat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 08:10:30