如何使用适用于 Azure 移动应用的 Node.js SDK

本文提供了详细信息和示例,介绍如何使用适用于 Azure 移动应用的 NodeJS 后端。

介绍

使用 Azure 移动应用可将移动优化的数据访问 Web API 添加到 Web 应用程序。 Azure 移动应用 SDK 适用于 ASP.NET Framework 和 Node.js Web 应用程序。 此 SDK 提供以下操作:

  • 数据访问的表操作(读取、插入、更新、删除)
  • 自定义 API 操作

这两种操作都可用于 Azure 应用服务允许的所有标识提供者之间的身份验证。 这些提供程序包括 Facebook、Twitter、Google 和 Microsoft 等社交标识提供者,以及企业标识的 Microsoft Entra ID。

支持的平台

Azure 移动应用 Node.js SDK 支持 Node 6.x 及更高版本,已通过测试的最高版本为 Node 12.x。 其他 Node 版本可能有效,但不受支持。

Azure 移动应用 Node.js SDK 支持两个数据库驱动程序:

  • node-mssqll 驱动程序支持 Azure SQL 数据库和本地 SQL Server 实例。
  • sqlite3 驱动程序仅支持单个实例上的 SQLite 数据库。

使用命令行创建基本 Node 后端

每个 Azure 移动应用 Node.js 后端都以快速应用程序的形式启动。 在适用于 Node.js 的 Web 服务框架中,Express 最广为使用。 可按以下方式创建基本的 Express 应用程序:

  1. 在命令窗口或 PowerShell 窗口中,为项目创建目录:

    $ mkdir basicapp
    
  2. 运行 npm init 初始化包结构:

    $ cd basicapp
    $ npm init
    

    npm init 命令将提出一系列问题以初始化项目。 查看示例输出:

    The npm init output

  3. 从 npm 存储库安装 expressazure-mobile-apps 库:

    npm install --save express azure-mobile-apps
    
  4. 创建 app.js 文件,实现基本移动服务器:

    var express = require('express'),
        azureMobileApps = require('azure-mobile-apps');
    
    var app = express(),
        mobile = azureMobileApps();
    
    // Define a TodoItem table.
    mobile.tables.add('TodoItem');
    
    // Add the Mobile API so it is accessible as a Web API.
    app.use(mobile);
    
    // Start listening on HTTP.
    app.listen(process.env.PORT || 3000);
    

此应用程序创建具有单个终结点 (/tables/TodoItem) 的移动优化 Web API,让用户使用动态架构访问基础 SQL 数据存储,而无需经过身份验证。 它适用于以下客户端库快速入门:

可以在 GitHub 上的示例区域中找到此基本应用程序的代码。

启用应用程序的主页

许多应用程序是 Web 和移动应用的组合。 可以使用 Express 框架组合两个分面。 但有时,我们可能只想要实现移动接口。 移动接口用于提供主页,确保应用服务已启动并在运行。 可以提供自己的主页,或启用临时主页。 若要启用临时主页,请使用以下代码来实例化 Azure 移动应用:

var mobile = azureMobileApps({ homePage: true });

如果想要让此选项仅在本地开发时可供使用,可以将此设置添加到 azureMobile.js 配置文件:

module.exports = {
    homePage: true,
};

可以根据需要将其他设置添加到 azureMobile.js 文件。

表操作

azure-mobile-apps Node.js Server SDK 提供将存储在 Azure SQL 数据库中的表公开为 Web API 的机制。 它提供五个操作:

操作 说明
GET /tables/tablename 获取表中的所有记录。
GET /tables/tablename/:id 获取表中的特定记录。
POST /tables/tablename 在表中创建记录。
PATCH /tables/tablename/:id 在表中更新记录。
DELETE /tables/tablename/:id 删除表中的记录。

此 Web API 支持 OData v3,并扩展表架构以支持脱机数据同步

使用动态架构定义表

表必须先经过定义才能使用。 可以使用静态架构来定义表(在架构中定义列),或者动态定义表(SDK 根据传入的请求控制架构)。 此外,可以通过将 JavaScript 代码添加到定义,来控制 Web API 的特定层面。

根据最佳做法,应在 tables 目录中的 JavaScript 文件内定义每个表,并使用 tables.import() 方法导入表。 扩展 basic-app 示例后,调整 app.js 文件:

var express = require('express'),
    azureMobileApps = require('azure-mobile-apps');

var app = express(),
    mobile = azureMobileApps();

// Define the database schema that is exposed.
mobile.tables.import('./tables');

// Provide initialization of any tables that are statically defined.
mobile.tables.initialize().then(function () {
    // Add the Mobile API so it is accessible as a Web API.
    app.use(mobile);

    // Start listening on HTTP.
    app.listen(process.env.PORT || 3000);
});

在 ./tables/TodoItem.js 中定义表:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Additional configuration for the table goes here.

module.exports = table;

表默认使用动态架构。

使用静态架构定义表

可以将列显式定义为通过 Web API 公开。 azure-mobile-apps Node.js SDK 自动将脱机数据同步所需的任何其他列添加到所提供的列表。 例如,快速入门客户端应用程序需要包含两个列的表:text(字符串)和 complete(布尔值)。 可以在表定义 JavaScript 文件中(位于 tables 目录中)定义该表,如下所示:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Define the columns within the table.
table.columns = {
    "text": "string",
    "complete": "boolean"
};

// Turn off the dynamic schema.
table.dynamicSchema = false;

module.exports = table;

如果以静态方式定义表,则还必须调用 tables.initialize() 方法,在启动时创建数据库架构。 tables.initialize() 方法返回 Promise,使 Web 服务不会在数据库初始化之前处理请求。

使用 SQL Server Express 作为本地计算机上的开发数据存储

Azure 移动应用 Node.js SDK 提供三种现成可用的数据服务选项:

  • 使用内存驱动程序提供非持久性示例存储。
  • 使用 mssql 驱动程序提供可供开发使用的 SQL Server Express 数据存储。
  • 使用 mssql 驱动程序提供可供生产使用的 Azure SQL 数据库数据存储。

Azure 移动应用 Node.js SDK 利用 mssql Node.js 包来建立和使用 SQL Server Express 与 SQL 数据库的连接。 若要使用此包,需要在 SQL Server Express 实例上启用 TCP 连接。

提示

内存驱动程序不提供完整的测试工具集。 若要在本地测试后端,建议使用 SQL Server Express 数据存储和 mssql 驱动程序。

  1. 下载并安装 Microsoft SQL Server 2019 Developer

  2. 运行 Configuration Manager:

    • 在树菜单中,展开“SQL Server 网络配置”节点。
    • 选择“instance-name 的协议”
    • 右键单击“TCP/IP”,并选择“启用”。 在弹出对话框中选择“确定”
    • 在树菜单中,选择“SQL Server 服务”
    • 右键单击“SQL Server (instance-name)”,并选择“重启”
    • 关闭 Configuration Manager。

还需要创建 Azure 移动应用可用于连接到数据库的用户名和密码。 确保创建的用户具有 dbcreator 服务器角色。 如需详细了解如何配置用户,请参阅 SQL Server 文档

请务必记下选择的用户名和密码。 可能需要根据数据库要求分配其他服务器角色或权限。

Node.js 应用程序将读取 SQLCONNSTR_MS_TableConnectionString 环境变量,以读取此数据库的连接字符串。 可以在环境中设置此变量。 例如,可以使用 PowerShell 设置此环境变量:

$env:SQLCONNSTR_MS_TableConnectionString = "Server=127.0.0.1; Database=mytestdatabase; User Id=azuremobile; Password=T3stPa55word;"

通过 TCP/IP 连接访问数据库。 提供连接的用户名和密码。

配置项目以进行本地开发

Azure 移动应用从本地文件系统读取名为 azureMobile.js 的 JavaScript 文件。 不要使用此文件在生产环境中配置 Azure 移动应用 SDK。 请改用 Azure 门户中的“应用设置”

azureMobile.js 文件应导出配置对象。 最常见的设置如下:

  • 数据库设置
  • 诊断日志记录设置
  • 备用 CORS 设置

以下示例 azureMobile.js 文件实现上述数据库设置:

module.exports = {
    cors: {
        origins: [ 'localhost' ]
    },
    data: {
        provider: 'mssql',
        server: '127.0.0.1',
        database: 'mytestdatabase',
        user: 'azuremobile',
        password: 'T3stPa55word'
    },
    logging: {
        level: 'verbose'
    }
};

建议将 azureMobile.js 添加到 .gitignore 文件(或其他源代码管理 ignore 文件),防止将密码存储在云中。

配置移动应用的应用设置

azureMobile.js 文件中的大多数设置在 Azure 门户中都有对等的应用设置。 使用以下列表在“应用设置”中配置应用

应用设置 azureMobile.js 设置 说明 有效值
MS_MobileAppName name 应用的名称 string
MS_MobileLoggingLevel logging.level 要记录的消息的最小日志级别 error、warning、info、verbose、debug、silly
MS_DebugMode 调试 启用或禁用调试模式 true、false
MS_TableSchema data.schema SQL 表的默认架构名称 字符串(默认值:dbo)
MS_DynamicSchema data.dynamicSchema 启用或禁用调试模式 true、false
MS_DisableVersionHeader 版本(设置为 undefined) 禁用 X-ZUMO-Server-Version 标头 true、false
MS_SkipVersionCheck skipversioncheck 禁用客户端 API 版本检查 true、false

更改大多数应用设置后都需要重新启动服务。

使用 Azure SQL 作为生产数据存储

无论使用哪种 Azure 应用服务应用程序类型,将 Azure SQL 数据库用作数据存储的过程都是相同的。 如果还没有这样做,请按照以下步骤创建 Azure 移动服务后端。 创建 Azure SQL 实例,然后将应用设置 SQLCONNSTR_MS_TableConnectionString 设置为你想要使用的 Azure SQL 实例的连接字符串。 确保运行后端的 Azure 应用服务可以与 Azure SQL 实例通信。

要求在访问表时进行身份验证

若要对 tables 终结点使用应用服务身份验证,必须先在 Azure 门户中配置应用服务身份验证。 有关详细信息,请参阅要使用的标识提供者的配置指南:

每个表都有一个访问属性用于控制对表的访问。 以下示例显示了以静态方式定义的、要求身份验证的表。

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Define the columns within the table.
table.columns = {
    "text": "string",
    "complete": "boolean"
};

// Turn off the dynamic schema.
table.dynamicSchema = false;

// Require authentication to access the table.
table.access = 'authenticated';

module.exports = table;

访问属性可接受三个值中的一个:

  • anonymous 表示允许客户端应用程序未经身份验证即可读取数据。
  • authenticated 表示客户端应用程序必须随请求发送有效的身份验证令牌。
  • disabled 表示此表当前已禁用。

如果未定义访问属性,则允许未经身份验证的访问。

对表使用身份验证声明

可以设置不同的声明,在设置身份验证时将请求这些声明。 这些声明通常无法通过 context.user 对象获取。 但是,可以使用 context.user.getIdentity() 方法来检索它们。 getIdentity() 方法返回可解析成某个对象的 Promise。 该对象由身份验证方法(facebookgoogletwittermicrosoftaccountaad)进行键控。

注意

如果通过 Microsoft Entra ID 使用 Microsoft 身份验证,则身份验证方法不是aadmicrosoftaccount

例如,如果设置了 Microsoft Entra 身份验证并请求电子邮件地址声明,则可以使用下表控制器将电子邮件地址添加到记录:

var azureMobileApps = require('azure-mobile-apps');

// Create a new table definition.
var table = azureMobileApps.table();

table.columns = {
    "emailAddress": "string",
    "text": "string",
    "complete": "boolean"
};
table.dynamicSchema = false;
table.access = 'authenticated';

/**
* Limit the context query to those records with the authenticated user email address
* @param {Context} context the operation context
* @returns {Promise} context execution Promise
*/
function queryContextForEmail(context) {
    return context.user.getIdentity().then((data) => {
        context.query.where({ emailAddress: data.aad.claims.emailaddress });
        return context.execute();
    });
}

/**
* Adds the email address from the claims to the context item - used for
* insert operations
* @param {Context} context the operation context
* @returns {Promise} context execution Promise
*/
function addEmailToContext(context) {
    return context.user.getIdentity().then((data) => {
        context.item.emailAddress = data.aad.claims.emailaddress;
        return context.execute();
    });
}

// Configure specific code when the client does a request.
// READ: only return records that belong to the authenticated user.
table.read(queryContextForEmail);

// CREATE: add or overwrite the userId based on the authenticated user.
table.insert(addEmailToContext);

// UPDATE: only allow updating of records that belong to the authenticated user.
table.update(queryContextForEmail);

// DELETE: only allow deletion of records that belong to the authenticated user.
table.delete(queryContextForEmail);

module.exports = table;

若要查看哪些声明可用,请使用 Web 浏览器查看站点的 /.auth/me 终结点。

禁用对特定表操作的访问

除了出现在表上以外,访问属性还可用于控制单个操作。 共有四项操作:

  • read 是对表运行的 RESTful GET 操作。
  • insert 是对表运行的 RESTful POST 操作。
  • update 是对表运行的 RESTful PATCH 操作。
  • delete 是对表运行的 RESTful DELETE 操作。

例如,若要提供未经身份验证的只读表:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Read-only table. Only allow READ operations.
table.read.access = 'anonymous';
table.insert.access = 'disabled';
table.update.access = 'disabled';
table.delete.access = 'disabled';

module.exports = table;

调整与表操作配合使用的查询

表操作的常见要求是提供受限制的数据视图。 例如,可以提供标有已经过身份验证的用户 ID 的表,以便只有你能够读取或更新自己的记录。 以下表定义提供此功能:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Define a static schema for the table.
table.columns = {
    "userId": "string",
    "text": "string",
    "complete": "boolean"
};
table.dynamicSchema = false;

// Require authentication for this table.
table.access = 'authenticated';

// Ensure that only records for the authenticated user are retrieved.
table.read(function (context) {
    context.query.where({ userId: context.user.id });
    return context.execute();
});

// When adding records, add or overwrite the userId with the authenticated user.
table.insert(function (context) {
    context.item.userId = context.user.id;
    return context.execute();
});

module.exports = table;

正常运行查询的操作包含一个可以使用 where 子句进行调整的查询属性。 该查询属性是一个 QueryJS 对象,用于将 OData 查询转换成数据后端可以处理的某种形式。 在简单的相等性比较方案中(如上例),可以使用映射。 还可以添加特定的 SQL 子句:

context.query.where('myfield eq ?', 'value');

在表中配置软删除

软删除并不实际删除记录。 它将已删除的列设置为 true,将记录标记为已在数据库中删除。 Azure 移动应用 SDK 自动从结果中删除已软删除的记录,除非移动客户端 SDK 使用 includeDeleted()。 若要为表配置软删除,请在表定义文件中设置 softDelete 属性:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Define the columns within the table.
table.columns = {
    "text": "string",
    "complete": "boolean"
};

// Turn off the dynamic schema.
table.dynamicSchema = false;

// Turn on soft delete.
table.softDelete = true;

// Require authentication to access the table.
table.access = 'authenticated';

module.exports = table;

建立记录永久删除机制,例如客户端应用程序、WebJob、Azure 函数或自定义 API。

在数据库中植入数据

创建新应用程序时,可能需要在表中植入数据。 可在表定义 JavaScript 文件中植入数据,如下所示:

var azureMobileApps = require('azure-mobile-apps');

var table = azureMobileApps.table();

// Define the columns within the table.
table.columns = {
    "text": "string",
    "complete": "boolean"
};
table.seed = [
    { text: 'Example 1', complete: false },
    { text: 'Example 2', complete: true }
];

// Turn off the dynamic schema.
table.dynamicSchema = false;

// Require authentication to access the table.
table.access = 'authenticated';

module.exports = table;

仅当表是由 Azure 移动应用 SDK 创建时,才能植入数据。 如果表已在数据库中,则不会在表中注入任何数据。 如果打开了动态架构,将从植入的数据推断架构。

建议显式调用 tables.initialize() 方法,在服务开始运行时创建表。

启用 Swagger 支持

Azure 移动应用随附内置的 Swagger 支持。 若要启用 Swagger 支持,请先安装 swagger-ui 作为依赖项:

npm install --save swagger-ui

然后,可以在 Azure 移动应用构造函数中启用 Swagger 支持:

var mobile = azureMobileApps({ swagger: true });

可能只想要在开发版本中启用 Swagger 支持。 可以使用 NODE_ENV 应用设置在开发中启用 Swagger 支持:

var mobile = azureMobileApps({ swagger: process.env.NODE_ENV !== 'production' });

swagger 终结点位于 http://你的站点.azurewebsites.net/swagger。 可以通过 /swagger/ui 终结点访问 Swagger UI。 如果选择要求在整个应用程序中进行身份验证,Swagger 将生成错误。 为获得最佳效果,请在“Azure 应用服务身份验证/授权”设置中选择允许未经身份验证的请求通过,并使用 table.access 属性控制身份验证。

如果希望只在本地进行开发时才使用 Swagger 支持,也可以将 Swagger 选项添加到 azureMobile.js 文件中。

自定义 API

除了通过 /tables 终结点的数据访问 API 以外,Azure 移动应用还可提供自定义 API 覆盖范围。 自定义 API 以类似于表定义的方法定义,可访问所有相同的功能,包括身份验证。

定义自定义 API

定义自定义 API 的方法与表 API 大致相同:

  1. 创建 api 目录。
  2. api 目录中创建 API 定义 JavaScript 文件。
  3. 使用 import 方法导入 api 目录。

下面是根据前面使用的基本应用示例所做的原型 API 定义:

var express = require('express'),
    azureMobileApps = require('azure-mobile-apps');

var app = express(),
    mobile = azureMobileApps();

// Import the custom API.
mobile.api.import('./api');

// Add the Mobile API so it is accessible as a Web API.
app.use(mobile);

// Start listening on HTTP
app.listen(process.env.PORT || 3000);

让我们使用一个通过 Date.now() 方法返回服务器日期的示例 API。 以下是 api/date.js 文件:

var api = {
    get: function (req, res, next) {
        var date = { currentTime: Date.now() };
        res.status(200).type('application/json').send(date);
    });
};

module.exports = api;

每个参数是标准的 RESTful 谓词之一:GET、POST、PATCH 或 DELETE。 此方法是发送所需输出的标准 ExpressJS 中间件函数。

要求在访问自定义 API 时进行身份验证

Azure 移动应用 SDK 对 tables 终结点和自定义 API 都使用相同的方式实现身份验证。 若要在前一部分开发的 API 中添加身份验证,请添加 access 属性:

var api = {
    get: function (req, res, next) {
        var date = { currentTime: Date.now() };
        res.status(200).type('application/json').send(date);
    });
};
// All methods must be authenticated.
api.access = 'authenticated';

module.exports = api;

也可以指定对特定操作的身份验证:

var api = {
    get: function (req, res, next) {
        var date = { currentTime: Date.now() };
        res.status(200).type('application/json').send(date);
    }
};
// The GET methods must be authenticated.
api.get.access = 'authenticated';

module.exports = api;

对于要求身份验证的自定义 API,必须使用与 tables 终结点相同的令牌。

处理大型文件上传

Azure 移动应用 SDK 使用正文分析器中间件来接受和解码提交件的正文内容。 可以将正文分析器预先配置为接受大型文件上传:

var express = require('express'),
    bodyParser = require('body-parser'),
    azureMobileApps = require('azure-mobile-apps');

var app = express(),
    mobile = azureMobileApps();

// Set up large body content handling.
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));

// Import the custom API.
mobile.api.import('./api');

// Add the Mobile API so it is accessible as a Web API.
app.use(mobile);

// Start listening on HTTP.
app.listen(process.env.PORT || 3000);

该文件在传输之前是以 Base-64 编码的。 此编码会增加实际上传的大小(因此必须考虑该大小)。

执行自定义 SQL 语句

Azure 移动应用 SDK 还可以通过请求对象访问整个上下文。 可以轻松针对定义的数据提供程序执行参数化的 SQL 语句:

var api = {
    get: function (request, response, next) {
        // Check for parameters. If not there, pass on to a later API call.
        if (typeof request.params.completed === 'undefined')
            return next();

        // Define the query. Anything that the mssql
        // driver can handle is allowed.
        var query = {
            sql: 'UPDATE TodoItem SET complete=@completed',
            parameters: [{
                completed: request.params.completed
            }]
        };

        // Execute the query. The context for Azure Mobile Apps is available through
        // request.azureMobile. The data object contains the configured data provider.
        request.azureMobile.data.execute(query)
        .then(function (results) {
            response.json(results);
        });
    }
};

api.get.access = 'authenticated';
module.exports = api;

调试

对 Azure 移动应用进行调试、诊断和故障排除

Azure 应用服务提供多种适用于 Node.js 应用程序的调试和故障排除方法。 若要入门针对 Node.js Azure 移动应用后端进行故障排除,请参阅以下文章:

Node.js 应用程序可访问各种诊断日志工具。 在内部,Azure 移动应用 Node.js SDK 使用 [Winston] 进行诊断日志记录。 启用调试模式,或者在 Azure 门户中将 MS_DebugMode 应用设置指定为 true,即可自动启用日志记录。 生成的日志显示在 Azure 门户上的诊断日志中。