如何通过 Node.js 使用 Azure 表存储或 Azure Cosmos DB for Table

适用于:

提示

本文中的内容适用于 Azure 表存储和 Azure Cosmos DB 表 API。 Azure Cosmos DB 表 API 是表存储的高级产品,可提供吞吐量优化表、全局分发和自动辅助索引。

本文介绍如何创建表、存储数据以及对该数据执行 CRUD 操作。 示例是采用 Node.js 编写的。

创建 Azure 服务帐户

可以通过 Azure 表存储或 Azure Cosmos DB 使用表。 若要详细了解这两个服务中的表产品/服务之间的差异,请参阅表产品/服务一文。 需要为所要使用的服务创建一个帐户。 以下部分说明了如何创建 Azure 表存储和 Azure Cosmos DB 帐户,但你只需使用其中一个。

创建 Azure 存储帐户

创建 Azure 存储帐户的最简单方法是使用 Azure 门户。 若要了解更多信息,请参阅 创建存储帐户

也可以使用 Azure PowerShellAzure CLI 创建 Azure 存储帐户。

如果暂时不想创建存储帐户,也可以使用 Azure 存储模拟器在本地环境中运行和测试代码。 有关详细信息,请参阅使用 Azure 存储模拟器进行开发和测试

创建 Azure Cosmos DB for Table 帐户

有关创建 Azure Cosmos DB 表 API 帐户的说明,请参阅创建数据库帐户

创建用于访问表存储的应用程序

若要使用 Azure 存储或 Azure Cosmos DB,需要适用于 Node.js 的 Azure 表 SDK,其中包括一组便于与存储 REST 服务进行通信的库。

使用 Node 包管理器 (NPM) 安装包

  1. 使用 PowerShell (Windows)、Terminal (Mac) 或 Bash (Unix) 等命令行接口导航到在其中创建了应用程序的文件夹。

  2. 在命令窗口中键入以下命令:

    npm install @azure/data-tables
    
  3. 可以手动运行 ls 命令,验证是否创建了 node_modules 文件夹 。 在该文件夹中将找到 @azure/data-tables 包,其中包含访问表所需的库。

导入包

将以下代码添加到应用程序中的 server.js 文件的顶部:

const { TableServiceClient } = require("@azure/data-tables");

连接到 Azure 表服务

可以连接到 Azure 存储帐户,也可以连接到 Azure Cosmos DB for Table 帐户。 根据所用的帐户类型获取共享密钥或连接字符串。

从共享密钥创建表服务客户端

Azure 模块读取环境变量 AZURE_ACCOUNT、AZURE_ACCESS_KEY 和 AZURE_TABLES_ENDPOINT 以获取连接到 Azure 存储帐户或 Azure Cosmos DB 所需的信息。 如果未设置这些环境变量,则必须在调用 TableServiceClient 时指定帐户信息。 例如,以下代码创建 TableServiceClient 对象:

const tableService = new TableServiceClient(
  tablesEndpoint,
  new AzureNamedKeyCredential("<accountName>", "<accountKey>")
);

从连接字符串创建表服务客户端

若要添加 Azure Cosmos DB 或存储帐户连接,需要创建 TableServiceClient 对象并指定帐户名称、主键和终结点。 可以在 Azure 门户中从 Azure Cosmos DB 帐户或存储帐户的“设置”>“连接字符串”中复制这些值。 例如:

const tableService = TableServiceClient.fromConnectionString("<connection-string>");

创建表

createTable 的调用将创建具有指定名称(如果该名称尚不存在)的一个新表。 下面的示例将创建一个名为“mytable”的新表(如果该表尚不存在):

await tableService.createTable('mytable');

将实体添加到表

若要添加实体,首先创建定义实体属性的对象。 所有实体都必须都包含 partitionKey 和 rowKey,它们是实体的唯一标识符。

  • partitionKey - 确定实体存储在其中的分区。
  • rowKey - 唯一标识分区内的实体。

partitionKey 和 rowKey 都必须是字符串值。

下面是如何定义实体的示例。 dueDate 定义为一种 Date 类型。 可以选择指定类型。如果未指定类型,系统会进行推断。

const task = {
  partitionKey: "hometasks",
  rowKey: "1",
  description: "take out the trash",
  dueDate: new Date(2015, 6, 20)
};

注意

每个记录还有一个 Timestamp 字段,在插入或更新实体时,Azure 会设置该字段。

若要向表中添加实体,应将实体对象传递给 createEntity 方法。

let result = await tableClient.createEntity(task);
    // Entity create

如果操作成功,result 将包含 ETag 以及有关操作的信息。

示例响应:

{ 
  clientRequestId: '94d8e2aa-5e02-47e7-830c-258e050c4c63',
  requestId: '08963b85-1002-001b-6d8c-12ae5d000000',
  version: '2019-02-02',
  date: 2022-01-26T08:12:32.000Z,
  etag: `W/"datetime'2022-01-26T08%3A12%3A33.0180348Z'"`,
  preferenceApplied: 'return-no-content',
  'cache-control': 'no-cache',
  'content-length': '0'
}

更新条目

updateEntityupsertEntity 方法的不同模式

  • 合并:通过更新实体的属性来更新实体,而无需替换现有实体。
  • 替换:通过替换整个实体来更新现有实体。

以下示例演示使用 upsertEntity 更新实体:

// Entity doesn't exist in table, so calling upsertEntity will simply insert the entity.
let result = await tableClient.upsertEntity(task, "Replace");

如果正在更新的实体不存在,则更新操作失败;因此,如果想要存储一个实体,而不管其是否已存在,请使用 upsertEntity

如果更新操作成功,则 result 会包含所更新实体的 Etag。

使用实体组

有时,有必要批量同时提交多项操作以确保通过服务器进行原子处理。 为此,请创建一个操作数组,然后将其传递给 TableClient 上的 submitTransaction 方法。

下面的示例演示了在一个批次中提交两个实体:

const task1 = {
  partitionKey: "hometasks",
  rowKey: "1",
  description: "Take out the trash",
  dueDate: new Date(2015, 6, 20)
};
const task2 = {
  partitionKey: "hometasks",
  rowKey: "2",
  description: "Wash the dishes",
  dueDate: new Date(2015, 6, 20)
};

const tableActions = [
  ["create", task1],
  ["create", task2]
];

let result = await tableClient.submitTransaction(tableActions);
    // Batch completed

如果批处理操作成功,则 result 包含批处理中每个操作的信息。

通过键检索实体

若要返回基于 PartitionKey 和 RowKey 的特定实体,请使用 getEntity 方法。

let result = await tableClient.getEntity("hometasks", "1");
    // result contains the entity

完成此操作后,result 包含该实体。

查询实体集

以下示例生成的查询返回 PartitionKey 为“hometasks”的前五项并列出表中的所有实体。

const topN = 5;
const partitionKey = "hometasks";

const entities = tableClient.listEntities({
  queryOptions: { filter: odata`PartitionKey eq ${partitionKey}` }
});

let topEntities = [];
const iterator = entities.byPage({ maxPageSize: topN });

for await (const page of iterator) {
  topEntities = page;
  break;
}

// Top entities: 5
console.log(`Top entities: ${topEntities.length}`);

// List all the entities in the table
for await (const entity of entities) {
console.log(entity);
}

查询一部分实体属性

对表的查询可以只检索实体中的少数几个字段。 这可以减少带宽并提高查询性能,尤其适用于大型实体。 使用 select 子句并传递要返回的字段的名称。 例如,下面的查询只返回 description 和 dueDate 字段 。

const topN = 5;
const partitionKey = "hometasks";

const entities = tableClient.listEntities({
  queryOptions: { filter: odata`PartitionKey eq ${partitionKey}`,
                  select: ["description", "dueDate"]  }
});

let topEntities = [];
const iterator = listResults.byPage({ maxPageSize: topN });

for await (const page of iterator) {
  topEntities = page;
  break;
}

删除实体

可以使用实体的分区键和行键删除实体。 在本例中,task1 对象包含要删除的实体的 rowKey 和 partitionKey 值。 然后,该对象被传递给 deleteEntity 方法。

const tableClient = new TableClient(
  tablesEndpoint,
  tableName,
  new AzureNamedKeyCredential("<accountName>", "<accountKey>")
);

await tableClient.deleteEntity("hometasks", "1");
    // Entity deleted

注意

考虑在删除项时使用 ETag,以确保项尚未被其他进程修改。 请参阅更新实体了解如何使用 ETag。

删除表

以下代码从存储帐户中删除一个表。

await tableClient.deleteTable(mytable);
        // Table deleted

使用继续标记

在所查询的表有大量的结果时,请查找继续标记。 如果在生成时不能识别何时存在继续标记,可能存在大量未意识到的数据可用于查询。

查询实体在设置 continuationToken 属性(如果此类标记存在)期间,返回结果对象。 然后可以在执行查询时使用此对象,继续在分区和表实体之间移动。

在查询时,在查询对象实例和回调函数之间可能会提供 continuationToken 参数:

let iterator = tableClient.listEntities().byPage({ maxPageSize: 2 });
let interestingPage;

const page = await tableClient
   .listEntities()
   .byPage({ maxPageSize: 2, continuationToken: interestingPage })
   .next();

 if (!page.done) {
   for (const entity of page.value) {
     console.log(entity.rowKey);
   }
 }

使用共享访问签名

共享访问签名 (SAS) 是一种安全的方法,用于对表进行细致访问而无需提供存储帐户名或密钥。 通常使用 SAS 来提供对数据的有限访问权限,例如允许移动应用查询记录。

受信任的应用程序(例如基于云的服务)可使用 TableClient 的 generateTab 生成 SAS,然后将其提供给不受信任的或不完全受信任的应用程序,例如移动应用。 可使用策略生成 SAS,该策略描述了 SAS 的生效日期和失效日期,以及授予 SAS 持有者的访问级别。

下面的示例生成了一个新的共享访问策略,该策略将允许 SAS 持有者查询 ("r") 表。

const tablePermissions = {
    query: true
// Allows querying entities
};

// Create the table SAS token
const tableSAS = generateTableSas('mytable', cred, {
  expiresOn: new Date("2022-12-12"),
  permissions: tablePermissions
});

然后,客户端应用程序将 SAS 用于 AzureSASCredential,以便针对表执行操作。 下面的示例连接到该表,并执行一个查询。 有关 tableSAS 的格式,请参阅使用共享访问签名 (SAS) 授予对 Azure 存储资源的有限访问权限一文。

// Note in the following command, tablesUrl is in the format: `https://<your_storage_account_name>.table.core.chinacloudapi.cn` and the tableSAS is in the format: `sv=2018-03-28&si=saspolicy&tn=mytable&sig=9aCzs76n0E7y5BpEi2GvsSv433BZa22leDOZXX%2BXXIU%3D`;

const tableService = new TableServiceClient(tablesUrl, new AzureSASCredential(tableSAS));
const partitionKey = "hometasks";

const entities = tableService.listTables({
  queryOptions: { filter: odata`PartitionKey eq ${partitionKey}` }
});

由于 SAS 在生成时只具有查询访问权限,因此如果尝试插入、更新或删除实体,则会返回错误。

访问控制列表

还可以使用访问控制列表 (ACL) 为 SAS 设置访问策略。 如果想要允许多个客户端访问某个表,但为每个客户端提供不同的访问策略,则访问控制列表会很有用。

ACL 是使用一组访问策略实施的,每个策略都有一个关联的 ID。 以下示例定义了两个策略,一个用于“user1”,一个用于“user2”:

var sharedAccessPolicy = [{
  id:"user1",
  accessPolicy:{
    permission: "r" ,
    Start: startsOn,
    Expiry: expiresOn,
  }},
  {
  id:"user2",
  accessPolicy:{
    permissions: "a",
    Start: startsOn,
    Expiry: expiresOn,
  }},
]

下面的示例获取 hometasks 表的当前 ACL,并使用 setAccessPolicy 添加新策略。 此方法具有以下用途:

tableClient.getAccessPolicy();
tableClient.setAccessPolicy(sharedAccessPolicy);

设置 ACL 后,可以根据某个策略的 ID 创建 SAS。 以下示例为“user2”创建新的 SAS:

tableSAS = generateTableSas("hometasks",cred,{identifier:'user2'});

后续步骤

有关详细信息,请参阅以下资源。