教程:使用 Azure 通知中心通过后端服务向React Native应用发送推送通知

下载示例 下载示例

在本教程中,你将使用 Azure 通知中心将通知推送到面向 AndroidiOS的React Native应用程序。

ASP.NET Core Web API 后端用于使用最新和最佳安装方法处理客户端的设备注册。 该服务还将以跨平台的方式发送推送通知。

这些操作使用 用于后端操作的通知中心 SDK 进行处理。 有关总体方法的更多详细信息,请参阅 从应用后端注册 文档。

本教程将指导你完成以下步骤:

先决条件

若要继续操作,需要:

  • 可在其中创建和管理资源的 Azure 订阅
  • (安装了 Visual Studio for Mac 的 Mac 或运行 Visual Studio 2019 的电脑,) 使用 .NET 移动开发工作负载。
  • Android (物理或仿真器设备) 或 iOS (物理设备上运行应用的功能仅) 。

对于 Android,必须具有:

  • 开发人员解锁的物理设备或仿真器 (运行 API 26 及更高版本,) 安装 Google Play Services

对于 iOS,必须具有:

注意

iOS 模拟器不支持远程通知,因此在 iOS 上浏览此示例时需要物理设备。 但是,无需在 AndroidiOS 上运行应用即可完成本教程。

可以按照此第一原则示例中的步骤操作,无需事先获得任何经验。 但是,通过熟悉以下方面,你将受益匪浅。

提供的步骤适用于Visual Studio for MacVisual Studio Code但可以使用 Visual Studio 2019 执行操作。

设置推送通知服务和 Azure 通知中心

在本部分中,将设置 Firebase Cloud Messaging (FCM) Apple Push Notification Services (APNS) 。 然后,创建并配置通知中心以使用这些服务。

创建 Firebase 项目并启用适用于 Android 的 Firebase Cloud Messaging

  1. 登录到 Firebase 控制台。 创建一个新的 Firebase 项目,输入 PushDemo 作为 项目名称

    注意

    将为你生成唯一名称。 默认情况下,它由你提供的名称的小写变体以及用短划线分隔的生成数字组成。 如果需要,可以更改此设置,前提是它仍然全局唯一。

  2. 创建项目后,选择“ 将 Firebase 添加到 Android 应用”。

    将 Firebase 添加到 Android 应用

  3. “将 Firebase 添加到 Android 应用 ”页上,执行以下步骤。

    1. 对于 Android 包名称,请输入包的名称。 例如: com.<organization_identifier>.<package_name>

      指定包名称

    2. 选择“ 注册应用”。

    3. 选择 “下载google-services.json”。 然后将文件保存到本地文件夹中供稍后使用,然后选择“ 下一步”。

      下载google-services.json

    4. 选择“ 下一步”。

    5. 选择“ 继续到控制台”

      注意

      如果由于验证安装检查未启用“继续控制台”按钮,请选择“跳过此步骤”。

  4. 在 Firebase 控制台中,选择项目的齿轮。 然后选择“项目设置”。

    选择“项目设置”

    注意

    如果尚未下载 google-services.json 文件,可以在此页面上下载该文件。

  5. 切换到顶部的“ 云消息 ”选项卡。 复制并保存 服务器密钥 供以后使用。 使用此值配置通知中心。

    复制服务器密钥

为推送通知注册 iOS 应用

若要向 iOS 应用发送推送通知,请向 Apple 注册应用程序,并注册推送通知。

  1. 如果尚未注册应用,请浏览到 Apple 开发人员中心的 iOS 预配门户 。 使用 Apple ID 登录到门户,导航到 “证书”、“标识符 & 配置文件”,然后选择“ 标识符”。 单击 + 以注册新应用。

    iOS 预配门户应用 ID 页

  2. “注册新标识符” 屏幕上,选择“ 应用 ID ”单选按钮。 然后选择“ 继续”。

    iOS 预配门户注册新 ID 页

  3. 更新新应用的以下三个值,然后选择“ 继续”:

    • 说明:键入应用的描述性名称。

    • 捆绑 ID:输入 com.organization_identifier<>格式的捆绑 ID。<>product_name,如应用分发指南中所述。 在以下屏幕截图中 mobcat ,该值用作组织标识符, PushDemo 值用作产品名称。

      iOS 预配门户注册应用 ID 页

    • 推送通知:检查“功能”部分中的“推送通知”选项。

      用于注册新应用 ID 的表单

      此操作会生成应用 ID,并请求你确认信息。 选择“ 继续”,然后选择“ 注册 ”以确认新的应用 ID。

      确认新的应用 ID

      选择“ 注册”后,可以在“ 证书、标识符 & 配置文件 ”页中看到新的应用 ID 作为行项。

  4. “证书、标识符 & 配置文件 ”页 的“标识符”下,找到创建的“应用 ID”行项。 然后,选择其行以显示 “编辑应用 ID 配置” 屏幕。

为通知中心创建证书

需要证书才能使通知中心能够与 Apple Push Notification Services (APNS) 配合使用,并且可以通过以下两种方式之一提供:

  1. 创建可直接上传到通知中心的 p12 推送证书 , (原始方法)

  2. 创建可用于基于令牌的身份验证的 p8 证书 , (较新的建议方法)

较新的方法具有许多好处,如 基于令牌的 (HTTP/2) APNS 身份验证中所述。 所需步骤较少,但针对特定方案也强制要求执行。 但是,已为这两种方法提供了步骤,因为这两种方法都适用于本教程。

选项 1:创建可直接上传到通知中心的 p12 推送证书
  1. 在 Mac 上,运行密钥链访问工具。 可以从 “实用工具” 文件夹或启动板上的“ 其他 ”文件夹打开它。

  2. 选择“ 密钥链访问”,展开“ 证书助理”,然后选择“ 从证书颁发机构请求证书”。

    使用密钥链访问请求新证书

    注意

    默认情况下,Keychain Access 选择列表中的第一项。 如果你在 “证书 ”类别中,并且 Apple 全球开发人员关系证书颁发机构 不是列表中的第一项,则这可能是个问题。 在生成 CSR (证书签名请求) 之前,请确保具有非密钥项或选择了 Apple 全球开发人员关系证书颁发机构 密钥。

  3. 选择“用户Email地址”,输入“公用名”值,确保指定“保存到磁盘”,然后选择“继续”。 将“CA Email地址”留空,因为它不是必需的。

    预期的证书信息

  4. 在“另存为中输入证书签名请求 (CSR) 文件的名称,在“位置”中选择位置,然后选择“保存”。

    选择证书的文件名

    此操作将 CSR 文件 保存在所选位置。 默认位置为 桌面。 请记住为文件选择的位置。

  5. 返回 iOS 预配门户中“证书、标识符 & 配置文件”页,向下滚动到选中的“推送通知”选项,然后选择“配置”以创建证书。

    “编辑应用 ID”页

  6. 此时会显示 Apple 推送通知服务 TLS/SSL 证书 窗口。 选择“开发 TLS/SSL 证书”部分下的“创建证书”按钮。

    “为应用 ID 创建证书”按钮

    将显示 “创建新证书” 屏幕。

    注意

    本教程使用开发证书。 注册生产证书时使用相同的过程。 只需确保在发送通知时使用相同的证书类型。

  7. 选择“ 选择文件”,浏览到保存 CSR 文件的位置,然后双击证书名称进行加载。 然后选择“ 继续”。

  8. 门户创建证书后,选择“ 下载 ”按钮。 保存证书,并记住证书的保存位置。

    生成的证书下载页

    证书将下载并保存到“ 下载” 文件夹中的计算机上。

    在“下载”文件夹中找到证书文件

    注意

    默认情况下,下载的开发证书命名 为 aps_development.cer

  9. 双击下载的推送证书 aps_development.cer。 此操作会在密钥链中安装新证书,如下图所示:

    显示新证书的密钥链访问证书列表

    注意

    尽管证书中的名称可能不同,但名称将以 Apple Development iOS Push Services 作为前缀,并具有与之关联的相应捆绑标识符。

  10. 在“密钥链访问 ”中,控制 + 单击 在“证书”类别中创建的新推送 证书 。 选择“ 导出”,为文件命名,选择 p12 格式,然后选择“ 保存”。

    将证书导出为 p12 格式

    可以选择使用密码保护证书,但密码是可选的。 如果要绕过密码创建,请单击“ 确定 ”。 记下导出的 p12 证书的文件名和位置。 它们用于启用 APN 身份验证。

    注意

    p12 文件名和位置可能与本教程中所示不同。

选项 2:创建可用于基于令牌的身份验证的 p8 证书
  1. 记下以下详细信息:

    • 应用 ID 前缀 (团队 ID)
    • 捆绑 ID
  2. 返回“ 证书”“标识符 & 配置文件”中,单击“ 密钥”。

    注意

    如果已为 APNS 配置了密钥,则可以重复使用创建后立即下载的 p8 证书。 如果是这样,可以忽略步骤 3步骤 5

  3. +单击 (按钮或“创建密钥”按钮) 创建新密钥。

  4. 提供合适的“密钥名称”值,然后检查 Apple Push Notifications 服务 (APNS) 选项,然后单击“继续”,然后单击“下一个屏幕上的注册”。

  5. 单击“ 下载 ”,然后将前缀为 AuthKey_) 的 p8文件 (移动到 安全的本地目录,然后单击“ 完成”。

    注意

    请确保将 p8 文件保存在安全的位置 (,并保存备份) 。 下载密钥后,无法重新下载,因为服务器副本被删除。

  6. “密钥”上,单击 (创建的密钥或现有密钥(如果已选择改用) )。

  7. 记下 “密钥 ID” 值。

  8. 在所选的合适应用程序中打开 p8 证书,例如 Visual Studio Code。 记下 -----BEGIN 私钥----- 和----- END PRIVATE KEY-----) 之间的密钥 (值。

    -----BEGIN 私钥-----
    <key_value>
    -----发送私钥-----

    注意

    这是稍后将用于配置通知中心的令牌值

完成这些步骤后,应该有以下信息供稍后在 为通知中心配置 APNS 信息中使用:

  • 团队 ID (请参阅步骤 1)
  • 捆绑 ID (请参阅步骤 1)
  • 密钥 ID (请参阅步骤 7)
  • 步骤 8) 中获取的令牌值 (p8 密钥值

为应用创建预配配置文件

  1. 返回到 iOS 预配门户,依次选择“ 证书”、“标识符 & 配置文件”,从左侧菜单中选择“ 配置文件+ ,然后选择以创建新配置文件。 将显示 “注册新的预配配置文件” 屏幕。

  2. 选择“开发”下的“iOS 应用开发”作为预配配置文件类型,然后选择“继续”。

    预配配置文件列表

  3. 接下来,从“应用 ID”下拉列表中选择创建 的应用 ID ,然后选择“ 继续”。

    选择应用 ID

  4. “选择证书” 窗口中,选择用于代码签名的开发证书,然后选择“ 继续”。

    注意

    此证书不是在 上一步中创建的推送证书。 这是你的开发证书。 如果不存在,则必须创建它,因为这是本教程的 先决条件 。 可以在 Apple 开发人员门户XcodeVisual Studio 中创建开发人员证书。

  5. 返回到 “证书、标识符 & 配置文件 ”页,从左侧菜单中选择“ 配置文件+ ,然后选择以创建新配置文件。 将显示 “注册新的预配配置文件” 屏幕。

  6. “选择证书” 窗口中,选择创建的开发证书。 然后选择“ 继续”。

  7. 接下来,选择要用于测试的设备,然后选择“ 继续”。

  8. 最后,在“预配配置文件名称”中选择 配置文件的名称,然后选择“ 生成”。

    选择预配配置文件名称

  9. 创建新的预配配置文件后,选择“ 下载”。 记住保存它的位置。

  10. 浏览到预配配置文件的位置,然后双击它以将其安装在开发计算机上。

创建通知中心

在本部分中,将创建通知中心并使用 APNS 配置身份验证。 可以使用 p12 推送证书或基于令牌的身份验证。 如果要使用已创建的通知中心,可以跳到步骤 5。

  1. 登录到 Azure

  2. 单击“ 创建资源”,搜索并选择“ 通知中心”,然后单击“ 创建”。

  3. 更新以下字段,然后单击“ 创建”:

    基本详细信息

    订阅:从下拉列表中选择目标订阅
    资源组: 创建新的 资源组 (或选择现有的资源组)

    命名空间详细信息

    通知中心命名空间: 输入 通知中心 命名空间的全局唯一名称

    注意

    确保为此字段选择了 “新建” 选项。

    通知中心详细信息

    通知中心:输入通知中心的名称
    位置: 从下拉列表中选择合适的位置
    定价层: 保留默认的 “免费” 选项

    注意

    除非已达到免费层上中心的最大数目。

  4. 预配 通知中心 后,导航到该资源。

  5. 导航到新的 通知中心

  6. “管理) ”下的列表中选择 (访问策略

  7. 记下 “策略名称” 值及其对应的 “连接字符串” 值。

使用 APNS 信息配置通知中心

“通知服务”下,选择“ Apple ”,然后根据之前在 “为通知中心创建证书 ”部分选择的方法执行相应的步骤。

注意

仅当你想要向从应用商店购买应用的用户发送推送通知时,才使用应用程序模式的生产。

选项 1:使用 .p12 推送证书

  1. 选择“ 证书”。

  2. 选择文件图标。

  3. 选择之前导出的 .p12 文件,然后选择“ 打开”。

  4. 如有必要,请指定正确的密码。

  5. 选择 “沙盒 模式”。

  6. 选择“保存”。

选项 2:使用基于令牌的身份验证

  1. 选择“ 令牌”。

  2. 输入之前获取的以下值:

    • 密钥 ID
    • 捆绑 ID
    • 团队 ID
    • 令 牌
  3. 选择 “沙盒”。

  4. 选择“保存”。

使用 FCM 信息配置通知中心

  1. 在左侧菜单中的“设置”部分选择 Google (GCM/FCM)
  2. 输入从 Google Firebase 控制台中记录的服务器密钥
  3. 在工具栏上选择“ 保存 ”。

创建 ASP.NET Core Web API 后端应用程序

在本部分中,将创建 ASP.NET Core Web API 后端来处理设备注册和向React Native移动应用发送通知。

创建 Web 项目

  1. Visual Studio 中,选择“ 文件>新建解决方案”。

  2. 选择“.NET Core>应用>ASP.NET Core>API>下一步”。

  3. “配置新 ASP.NET Core Web API”对话框中,选择“.NET Core 3.1的目标框架”。

  4. “项目名称”输入 PushDemoApi,然后选择“创建”。

  5. (Command + Enter) 开始调试以测试模板化应用。

    注意

    模板化应用配置为使用 WeatherForecastController 作为 launchUrl。 此属性在 “属性>”launchSettings.json中设置。

    如果系统提示出现“ 发现开发证书无效” 消息:

    1. 单击“ ”同意运行“dotnet dev-certs https”工具来解决此问题。 然后,“dotnet dev-certs https”工具提示输入证书的密码和密钥链的密码。

    2. 当系统提示“安装并信任新证书”时,单击“”,然后输入密钥链的密码。

  6. 展开 “控制器” 文件夹,然后删除 WeatherForecastController.cs

  7. 删除 WeatherForecast.cs

  8. 使用 机密管理器工具设置本地配置值。 将机密与解决方案分离可确保它们最终不会在源代码管理中结束。 打开 终端 ,然后转到项目文件的 目录并运行以下命令:

    dotnet user-secrets init
    dotnet user-secrets set "NotificationHub:Name" <value>
    dotnet user-secrets set "NotificationHub:ConnectionString" <value>
    

    将占位符值替换为你自己的通知中心名称和连接字符串值。 你在 创建通知中心 部分记下了这些内容。 否则,可以在 Azure 中查找它们。

    NotificationHub:Name
    请参阅概述顶部的“概要”摘要中的“名称”。

    NotificationHub:ConnectionString
    请参阅访问策略中的 DefaultFullSharedAccessSignature

    注意

    对于生产方案,可以查看 Azure KeyVault 等选项,以安全地存储连接字符串。 为简单起见,机密将添加到Azure 应用服务应用程序设置中。

使用 API 密钥 (可选) 对客户端进行身份验证

API 密钥不如令牌安全,但足以满足本教程的目的。 可以通过 ASP.NET 中间件轻松配置 API 密钥。

  1. API 密钥 添加到本地配置值。

    dotnet user-secrets set "Authentication:ApiKey" <value>
    

    注意

    应将占位符值替换为自己的值,并记下它。

  2. 控制 + 单击PushDemoApi 项目,从“添加”菜单中选择“新建文件夹,然后单击“使用身份验证添加”作为文件夹名称

  3. 控制 + 单击“身份验证”文件夹,然后从“添加”菜单中选择“新建文件...”

  4. 选择“常规>空类”,输入“名称ApiKeyAuthOptions.cs”,然后单击“新建”添加以下实现。

    using Microsoft.AspNetCore.Authentication;
    
    namespace PushDemoApi.Authentication
    {
        public class ApiKeyAuthOptions : AuthenticationSchemeOptions
        {
            public const string DefaultScheme = "ApiKey";
            public string Scheme => DefaultScheme;
            public string ApiKey { get; set; }
        }
    }
    
  5. 将另一个空类添加到名为 ApiKeyAuthHandler.csAuthentication 文件夹,然后添加以下实现。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Text.Encodings.Web;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    
    namespace PushDemoApi.Authentication
    {
        public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOptions>
        {
            const string ApiKeyIdentifier = "apikey";
    
            public ApiKeyAuthHandler(
                IOptionsMonitor<ApiKeyAuthOptions> options,
                ILoggerFactory logger,
                UrlEncoder encoder,
                ISystemClock clock)
                : base(options, logger, encoder, clock) {}
    
            protected override Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                string key = string.Empty;
    
                if (Request.Headers[ApiKeyIdentifier].Any())
                {
                    key = Request.Headers[ApiKeyIdentifier].FirstOrDefault();
                }
                else if (Request.Query.ContainsKey(ApiKeyIdentifier))
                {
                    if (Request.Query.TryGetValue(ApiKeyIdentifier, out var queryKey))
                        key = queryKey;
                }
    
                if (string.IsNullOrWhiteSpace(key))
                    return Task.FromResult(AuthenticateResult.Fail("No api key provided"));
    
                if (!string.Equals(key, Options.ApiKey, StringComparison.Ordinal))
                    return Task.FromResult(AuthenticateResult.Fail("Invalid api key."));
    
                var identities = new List<ClaimsIdentity> {
                    new ClaimsIdentity("ApiKeyIdentity")
                };
    
                var ticket = new AuthenticationTicket(
                    new ClaimsPrincipal(identities), Options.Scheme);
    
                return Task.FromResult(AuthenticateResult.Success(ticket));
            }
        }
    }
    

    注意

    身份验证处理程序是实现方案行为的类型,在本例中为自定义 API 密钥方案。

  6. 将另一个空类添加到名为 ApiKeyAuthenticationBuilderExtensions.cs的 Authentication 文件夹中,然后添加以下实现。

    using System;
    using Microsoft.AspNetCore.Authentication;
    
    namespace PushDemoApi.Authentication
    {
        public static class AuthenticationBuilderExtensions
        {
            public static AuthenticationBuilder AddApiKeyAuth(
                this AuthenticationBuilder builder,
                Action<ApiKeyAuthOptions> configureOptions)
            {
                return builder
                    .AddScheme<ApiKeyAuthOptions, ApiKeyAuthHandler>(
                        ApiKeyAuthOptions.DefaultScheme,
                        configureOptions);
            }
        }
    }
    

    注意

    此扩展方法简化了 Startup.cs 中的中间件配置代码使其更易于阅读,并且通常更易于使用。

  7. Startup.cs中,更新 ConfigureServices 方法,在对服务的调用下配置 API 密钥身份验证 。AddControllers 方法。

    using PushDemoApi.Authentication;
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = ApiKeyAuthOptions.DefaultScheme;
            options.DefaultChallengeScheme = ApiKeyAuthOptions.DefaultScheme;
        }).AddApiKeyAuth(Configuration.GetSection("Authentication").Bind);
    }
    
  8. 仍在Startup.cs更新 Configure 方法,以在应用的 IApplicationBuilder 上调用 UseAuthenticationUseAuthorization 扩展方法。 确保在 UseRouting 之后和应用之前调用这些方法 。UseEndpoints

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseHttpsRedirection();
    
        app.UseRouting();
    
        app.UseAuthentication();
    
        app.UseAuthorization();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
    

    注意

    调用 UseAuthentication 会注册中间件,该中间件使用以前从 ConfigureServices) 注册的身份验证方案 (。 在依赖于用户进行身份验证的任何中间件之前,必须调用它。

添加依赖项并配置服务

ASP.NET Core支持依赖项注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制 (IoC) 的反转的技术。

将通知中心和 通知中心 SDK 用于后端操作 会封装在服务中。 该服务已注册并通过合适的抽象提供。

  1. 控制 + 单击“依赖项”文件夹,然后选择“管理 NuGet 包...”

  2. 搜索 Microsoft.Azure.NotificationHubs 并确保已选中它。

  3. 单击“添加包”,然后在系统提示接受许可条款时单击“接受”。

  4. 控制 + 单击PushDemoApi 项目,从“添加”菜单中选择“新建文件夹”,然后单击“使用模型添加”作为文件夹名称

  5. 控制 + 单击“模型”文件夹,然后从“添加”菜单中选择“新建文件...”

  6. 选择“常规>空类”,输入“名称PushTemplates.cs,然后单击“新建”添加以下实现。

    namespace PushDemoApi.Models
    {
        public class PushTemplates
        {
            public class Generic
            {
                public const string Android = "{ \"notification\": { \"title\" : \"PushDemo\", \"body\" : \"$(alertMessage)\"}, \"data\" : { \"action\" : \"$(alertAction)\" } }";
                public const string iOS = "{ \"aps\" : {\"alert\" : \"$(alertMessage)\"}, \"action\" : \"$(alertAction)\" }";
            }
    
            public class Silent
            {
                public const string Android = "{ \"data\" : {\"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\"} }";
                public const string iOS = "{ \"aps\" : {\"content-available\" : 1, \"apns-priority\": 5, \"sound\" : \"\", \"badge\" : 0}, \"message\" : \"$(alertMessage)\", \"action\" : \"$(alertAction)\" }";
            }
        }
    }
    

    注意

    此类包含此方案所需的泛型和无提示通知的标记化通知有效负载。 有效负载在 安装 外部定义,以允许试验,而无需通过服务更新现有安装。 以这种方式处理对安装的更改不在本教程中。 对于生产环境,请考虑 使用自定义模板

  7. 将另一个空类添加到名为 DeviceInstallation.csModels 文件夹,然后添加以下实现。

    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    
    namespace PushDemoApi.Models
    {
        public class DeviceInstallation
        {
            [Required]
            public string InstallationId { get; set; }
    
            [Required]
            public string Platform { get; set; }
    
            [Required]
            public string PushChannel { get; set; }
    
            public IList<string> Tags { get; set; } = Array.Empty<string>();
        }
    }
    
  8. 将另一个空类添加到名为 NotificationRequest.csModels 文件夹,然后添加以下实现。

    using System;
    
    namespace PushDemoApi.Models
    {
        public class NotificationRequest
        {
            public string Text { get; set; }
            public string Action { get; set; }
            public string[] Tags { get; set; } = Array.Empty<string>();
            public bool Silent { get; set; }
        }
    }
    
  9. 将另一个空类添加到名为 NotificationHubOptions.csModels 文件夹,然后添加以下实现。

    using System.ComponentModel.DataAnnotations;
    
    namespace PushDemoApi.Models
    {
        public class NotificationHubOptions
        {
            [Required]
            public string Name { get; set; }
    
            [Required]
            public string ConnectionString { get; set; }
        }
    }
    
  10. 将名为“服务”的新文件夹添加到 PushDemoApi 项目。

  11. 空接口添加到名为 INotificationService.cs的 Services 文件夹,然后添加以下实现。

    using System.Threading;
    using System.Threading.Tasks;
    using PushDemoApi.Models;
    
    namespace PushDemoApi.Services
    {
        public interface INotificationService
        {
            Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token);
            Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token);
            Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token);
        }
    }
    
  12. 空类添加到名为 NotificationHubsService.cs的 Services 文件夹,然后添加以下代码来实现 INotificationService 接口:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Azure.NotificationHubs;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using PushDemoApi.Models;
    
    namespace PushDemoApi.Services
    {
        public class NotificationHubService : INotificationService
        {
            readonly NotificationHubClient _hub;
            readonly Dictionary<string, NotificationPlatform> _installationPlatform;
            readonly ILogger<NotificationHubService> _logger;
    
            public NotificationHubService(IOptions<NotificationHubOptions> options, ILogger<NotificationHubService> logger)
            {
                _logger = logger;
                _hub = NotificationHubClient.CreateClientFromConnectionString(
                    options.Value.ConnectionString,
                    options.Value.Name);
    
                _installationPlatform = new Dictionary<string, NotificationPlatform>
                {
                    { nameof(NotificationPlatform.Apns).ToLower(), NotificationPlatform.Apns },
                    { nameof(NotificationPlatform.Fcm).ToLower(), NotificationPlatform.Fcm }
                };
            }
    
            public async Task<bool> CreateOrUpdateInstallationAsync(DeviceInstallation deviceInstallation, CancellationToken token)
            {
                if (string.IsNullOrWhiteSpace(deviceInstallation?.InstallationId) ||
                    string.IsNullOrWhiteSpace(deviceInstallation?.Platform) ||
                    string.IsNullOrWhiteSpace(deviceInstallation?.PushChannel))
                    return false;
    
                var installation = new Installation()
                {
                    InstallationId = deviceInstallation.InstallationId,
                    PushChannel = deviceInstallation.PushChannel,
                    Tags = deviceInstallation.Tags
                };
    
                if (_installationPlatform.TryGetValue(deviceInstallation.Platform, out var platform))
                    installation.Platform = platform;
                else
                    return false;
    
                try
                {
                    await _hub.CreateOrUpdateInstallationAsync(installation, token);
                }
                catch
                {
                    return false;
                }
    
                return true;
            }
    
            public async Task<bool> DeleteInstallationByIdAsync(string installationId, CancellationToken token)
            {
                if (string.IsNullOrWhiteSpace(installationId))
                    return false;
    
                try
                {
                    await _hub.DeleteInstallationAsync(installationId, token);
                }
                catch
                {
                    return false;
                }
    
                return true;
            }
    
            public async Task<bool> RequestNotificationAsync(NotificationRequest notificationRequest, CancellationToken token)
            {
                if ((notificationRequest.Silent &&
                    string.IsNullOrWhiteSpace(notificationRequest?.Action)) ||
                    (!notificationRequest.Silent &&
                    (string.IsNullOrWhiteSpace(notificationRequest?.Text)) ||
                    string.IsNullOrWhiteSpace(notificationRequest?.Action)))
                    return false;
    
                var androidPushTemplate = notificationRequest.Silent ?
                    PushTemplates.Silent.Android :
                    PushTemplates.Generic.Android;
    
                var iOSPushTemplate = notificationRequest.Silent ?
                    PushTemplates.Silent.iOS :
                    PushTemplates.Generic.iOS;
    
                var androidPayload = PrepareNotificationPayload(
                    androidPushTemplate,
                    notificationRequest.Text,
                    notificationRequest.Action);
    
                var iOSPayload = PrepareNotificationPayload(
                    iOSPushTemplate,
                    notificationRequest.Text,
                    notificationRequest.Action);
    
                try
                {
                    if (notificationRequest.Tags.Length == 0)
                    {
                        // This will broadcast to all users registered in the notification hub
                        await SendPlatformNotificationsAsync(androidPayload, iOSPayload, token);
                    }
                    else if (notificationRequest.Tags.Length <= 20)
                    {
                        await SendPlatformNotificationsAsync(androidPayload, iOSPayload, notificationRequest.Tags, token);
                    }
                    else
                    {
                        var notificationTasks = notificationRequest.Tags
                            .Select((value, index) => (value, index))
                            .GroupBy(g => g.index / 20, i => i.value)
                            .Select(tags => SendPlatformNotificationsAsync(androidPayload, iOSPayload, tags, token));
    
                        await Task.WhenAll(notificationTasks);
                    }
    
                    return true;
                }
                catch (Exception e)
                {
                    _logger.LogError(e, "Unexpected error sending notification");
                    return false;
                }
            }
    
            string PrepareNotificationPayload(string template, string text, string action) => template
                .Replace("$(alertMessage)", text, StringComparison.InvariantCulture)
                .Replace("$(alertAction)", action, StringComparison.InvariantCulture);
    
            Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, CancellationToken token)
            {
                var sendTasks = new Task[]
                {
                    _hub.SendFcmNativeNotificationAsync(androidPayload, token),
                    _hub.SendAppleNativeNotificationAsync(iOSPayload, token)
                };
    
                return Task.WhenAll(sendTasks);
            }
    
            Task SendPlatformNotificationsAsync(string androidPayload, string iOSPayload, IEnumerable<string> tags, CancellationToken token)
            {
                var sendTasks = new Task[]
                {
                    _hub.SendFcmNativeNotificationAsync(androidPayload, tags, token),
                    _hub.SendAppleNativeNotificationAsync(iOSPayload, tags, token)
                };
    
                return Task.WhenAll(sendTasks);
            }
        }
    }
    

    注意

    提供给 SendTemplateNotificationAsync 的标记表达式限制为 20 个标记。 对于大多数运算符,它限制为 6,但表达式仅包含 (||在本例中) 。 如果请求中有 20 个以上的标记,则必须将其拆分为多个请求。 有关更多详细信息,请参阅 路由和标记表达式 文档。

  13. Startup.cs 中,更新 ConfigureServices 方法,将 NotificationHubsService 添加为 INotificationService 的单一实例实现。

    
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddSingleton<INotificationService, NotificationHubService>();
    
        services.AddOptions<NotificationHubOptions>()
            .Configure(Configuration.GetSection("NotificationHub").Bind)
            .ValidateDataAnnotations();
    }
    

创建通知 API

  1. 控制 + 单击“控制器”文件夹,然后从“添加”菜单中选择“新建文件...”

  2. 选择“ASP.NET Core>Web API 控制器类”,输入“通知”“控制器”,输入“名称”,然后单击“新建”。

    注意

    如果要关注 Visual Studio 2019,请选择 具有读/写操作的 API 控制器 模板。

  3. 将以下命名空间添加到文件顶部。

    using System.ComponentModel.DataAnnotations;
    using System.Net;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using PushDemoApi.Models;
    using PushDemoApi.Services;
    
  4. 更新模板化控制器,使其派生自 ControllerBase 并使用 ApiController 属性进行修饰。

    [ApiController]
    [Route("api/[controller]")]
    public class NotificationsController : ControllerBase
    {
        // Templated methods here
    }
    

    注意

    Controller 基类提供对视图的支持,但在这种情况下不需要这样做,因此可以改用 ControllerBase。 如果要关注 Visual Studio 2019,则可以跳过此步骤。

  5. 如果选择完成“使用 API 密钥对客户端进行身份验证”部分,还应使用 Authorize 属性修饰 NotificationsController

    [Authorize]
    
  6. 更新构造函数以接受 已注册的 INotificationService 实例作为参数,并将其分配给只读成员。

    readonly INotificationService _notificationService;
    
    public NotificationsController(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }
    
  7. “属性”文件夹) launchSettings.json (中,将 launchUrlweatherforecast 更改为 api/notifications,以匹配 RegistrationsControllerRoute 属性中指定的 URL。

  8. 开始调试 (Command + Enter) 以验证应用是否使用新的 NotificationsController 并返回 “401 未授权” 状态。

    注意

    Visual Studio 可能不会在浏览器中自动启动应用。 此时,你将使用 Postman 测试 API。

  9. 在新的 Postman 选项卡上,将请求设置为 GET。 输入以下地址,将占位符 <applicationUrl> 替换为属性>launchSettings.json中的 https applicationUrl

    <applicationUrl>/api/notifications
    

    注意

    默认配置文件的 applicationUrl 应为“https://localhost:5001”。 如果在 Windows) 上使用 Visual Studio 2019 中默认 (IIS,则应改用 iisSettings 项中指定的 applicationUrl。 如果地址不正确,你将收到 404 响应。

  10. 如果选择完成 “使用 API 密钥对客户端进行身份验证” 部分,请确保将请求标头配置为包含 apikey 值。

    apikey <your_api_key>
  11. 单击“ 发送 ”按钮。

    注意

    应会收到包含某些 JSON 内容的“200 正常”状态。

    如果收到 SSL 证书验证警告,则可以在“设置”中关闭请求 SSL 证书验证 Postman 设置。

  12. NotificationsController.cs 中的模板化类方法替换为以下代码。

    [HttpPut]
    [Route("installations")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<IActionResult> UpdateInstallation(
        [Required]DeviceInstallation deviceInstallation)
    {
        var success = await _notificationService
            .CreateOrUpdateInstallationAsync(deviceInstallation, HttpContext.RequestAborted);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    
    [HttpDelete()]
    [Route("installations/{installationId}")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<ActionResult> DeleteInstallation(
        [Required][FromRoute]string installationId)
    {
        var success = await _notificationService
            .DeleteInstallationByIdAsync(installationId, CancellationToken.None);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    
    [HttpPost]
    [Route("requests")]
    [ProducesResponseType((int)HttpStatusCode.OK)]
    [ProducesResponseType((int)HttpStatusCode.BadRequest)]
    [ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
    public async Task<IActionResult> RequestPush(
        [Required]NotificationRequest notificationRequest)
    {
        if ((notificationRequest.Silent &&
            string.IsNullOrWhiteSpace(notificationRequest?.Action)) ||
            (!notificationRequest.Silent &&
            string.IsNullOrWhiteSpace(notificationRequest?.Text)))
            return new BadRequestResult();
    
        var success = await _notificationService
            .RequestNotificationAsync(notificationRequest, HttpContext.RequestAborted);
    
        if (!success)
            return new UnprocessableEntityResult();
    
        return new OkResult();
    }
    

创建 API 应用

现在,在 Azure 应用服务 中创建 API 应用,用于托管后端服务。

  1. 登录 Azure 门户

  2. 单击“ 创建资源”,搜索并选择“ API 应用”,然后单击“ 创建”。

  3. 更新以下字段,然后单击“ 创建”。

    应用名称:
    输入 API 应用的全局唯一名称

    订阅:
    选择在其中创建通知中心的同一目标 订阅

    资源组:
    选择创建通知中心的同一 资源组

    App 服务计划/位置:
    创建新的App 服务计划

    注意

    从默认选项更改为包含 SSL 支持的计划。 否则,在使用移动应用时,需要采取适当的步骤,以防止 http 请求被阻止。

    Application Insights:
    保留建议选项 (将使用该名称创建新资源) 或选取现有资源。

  4. 预配 API 应用 后,导航到该资源。

  5. 记下“概述”顶部的“概要”摘要中的 URL 属性。 此 URL 是 后端终结点 ,稍后将在本教程中使用。

    注意

    URL 使用前面指定的 API 应用名称,格式 https://<app_name>.azurewebsites.net为 。

  6. 从“设置) ”下 (列表中选择“配置”。

  7. 对于以下每个设置,请单击“ 新建应用程序设置 ”以输入 “名称” 和“ ”,然后单击“ 确定”。

    名称
    Authentication:ApiKey <api_key_value>
    NotificationHub:Name <hub_name_value>
    NotificationHub:ConnectionString <hub_connection_string_value>

    注意

    这些设置与之前在用户设置中定义的设置相同。 你应该能够复制这些内容。 仅当选择完成使用 API 密钥对客户端进行身份验证部分时,才需要 Authentication:ApiKey 设置。 对于生产方案,可以查看 Azure KeyVault 等选项。 为了简单起见,这些已添加为应用程序设置,在这种情况下。

  8. 添加所有应用程序设置后,单击“ 保存”,然后单击 “继续”。

发布后端服务

接下来,将应用部署到 API 应用,使其可从所有设备访问。

注意

以下步骤特定于Visual Studio for Mac。 如果你关注 Windows 上的 Visual Studio 2019 ,发布流程将有所不同。 请参阅发布到 Windows 上的Azure 应用服务

  1. 将配置从 “调试” 更改为 “发布 ”(如果尚未这样做)。

  2. 控制 + 单击PushDemoApi 项目,然后从“发布”菜单中选择“发布到 Azure...”

  3. 如果系统提示,请遵循身份验证流。 使用在上一 个“创建 API 应用” 部分中使用的帐户。

  4. 从列表中选择之前创建的Azure 应用服务 API 应用作为发布目标,然后单击“发布”。

完成向导后,它会将应用发布到 Azure,然后打开应用。 请记下 URL (如果尚未这样做)。 此 URL 是本教程后面部分使用的 后端终结点

验证已发布的 API

  1. Postman 中打开一个新选项卡,将请求设置为 PUT ,然后输入以下地址。 将 占位符替换为在上一 个发布后端服务 部分中记下的基址。

    https://<app_name>.azurewebsites.net/api/notifications/installations
    

    注意

    基址的格式应为 https://<app_name>.azurewebsites.net/

  2. 如果选择完成 “使用 API 密钥对客户端进行身份验证” 部分,请确保将请求标头配置为包含 apikey 值。

    apikey <your_api_key>
  3. 选择“正文的原始选项,然后从格式选项列表中选择“JSON”,然后包括一些占位符 JSON 内容:

    {}
    
  4. 单击“Send”。

    注意

    应从服务收到 422 UnprocessableEntity 状态。

  5. 再次执行步骤 1-4,但这次指定请求终结点以验证收到 400 错误请求 响应。

    https://<app_name>.azurewebsites.net/api/notifications/requests
    

注意

目前无法使用有效的请求数据测试 API,因为这需要客户端移动应用中特定于平台的信息。

创建跨平台React Native应用程序

在本部分中,将构建一个React Native移动应用程序,以跨平台的方式实现推送通知。

它使你能够通过创建的后端服务从通知中心注册和取消注册。

指定操作且应用位于前台时,将显示警报。 否则,通知将显示在通知中心。

注意

通常在应用程序生命周期 (的适当时间点执行注册 (和取消注册) 操作,或者作为首次运行体验的一部分,) 无需显式用户注册/取消注册输入。 但是,此示例需要显式用户输入,以便更轻松地浏览和测试此功能。

创建React Native解决方案

  1. 在 中Terminal,更新环境工具,需要使用以下命令处理React Native:

    # install node
    brew install node
    # or update
    brew update node
    # install watchman
    brew install watchman
    # or update
    brew upgrade watchman
    # install cocoapods
    sudo gem install cocoapods
    
  2. 在 中 Terminal,如果 React Native 已安装 CLI,请运行以下命令以将其卸载。 使用 npx 自动访问可用的最新 React Native CLI 版本:

    npm uninstall -g react-native-cli
    

    注意

    React Native具有内置的命令行接口。 建议在运行时使用 npx附带的 Node.js 访问当前版本,而不是全局安装和管理 CLI 的特定版本。 使用 npx react-native <command>,将在运行命令时下载并执行 CLI 的当前稳定版本。

  3. 导航到要在其中创建新应用程序的项目文件夹。 通过指定 --template 参数,使用基于 Typescript 的模板:

    # init new project with npx
    npx react-native init PushDemo --template react-native-template-typescript
    
  4. 运行 metro 服务器,它生成 JavaScript 捆绑包并监视任何代码更新以实时刷新捆绑包:

    cd PushDemo
    npx react-native start
    
  5. 运行 iOS 应用以验证设置。 在执行以下命令之前,请确保已启动 iOS 模拟器或连接了 iOS 设备:

    npx react-native run-ios
    
  6. 运行 Android 应用以验证设置。 配置 Android 仿真器或设备以能够访问React Native metro 服务器需要一些额外的步骤。 以下命令为 Android 生成初始 JavaScript 捆绑包,并将其放入 assets 文件夹。

    # create assets folder for the bundle
    mkdir android/app/scr/main/assets
    # build the bundle
    npx react-native bundle --platform android --dev true --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
    # enable ability for sim to access the localhost
    adb reverse tcp:8081 tcp:8081
    

    此脚本将与应用的初始版本一起预部署。 部署后,通过指定服务器 IP 地址将模拟器或设备配置为访问 metro 服务器。 执行以下命令生成并运行 Android 应用程序:

    npx react-native run-android
    

    进入应用后,点击 CMD+M (模拟器) 或摇动设备以填充开发人员设置,导航到Change Bundle LocationSettings> ,并使用默认端口指定 metro 服务器 IP 地址:。 <metro-server-ip-address>:8081

  7. 在 文件中 App.tsx ,对页面布局应用任何更改,将其保存,使更改自动反映在 iOS 和 Android 应用中。

    注意

    官方文档中提供了详细的开发环境设置指南

安装所需的包

此示例需要以下三个包才能正常工作:

  1. React Native推送通知 iOS - 项目 GitHub

    此包是在 PushNotificationIOS 从 React Native 的核心拆分时创建的。 包以本机方式实现 iOS 的推送通知,并提供访问它的React Native接口。 运行以下命令以安装包:

    yarn add @react-native-community/push-notification-ios
    
  2. 跨平台React Native推送通知

    此包以跨平台的方式在 iOS 和 Android 上实现本地和远程通知。 运行以下命令以安装包:

    yarn add react-native-push-notification
    
  3. 设备信息包 包提供有关运行时中的设备的信息。 使用它来定义设备标识符,该标识符用于注册推送通知。 运行以下命令以安装包:

    yarn add react-native-device-info
    

实现跨平台组件

  1. 创建并实现 DemoNotificationHandler

    import PushNotification from 'react-native-push-notification';
    
    class DemoNotificationHandler {
      private _onRegister: any;
      private _onNotification: any;
    
      onNotification(notification: any) {
        console.log('NotificationHandler:', notification);
    
        if (typeof this._onNotification === 'function') {
          this._onNotification(notification);
        }
      }
    
      onRegister(token: any) {
        console.log('NotificationHandler:', token);
    
        if (typeof this._onRegister === 'function') {
          this._onRegister(token);
        }
      }
    
      attachTokenReceived(handler: any) {
        this._onRegister = handler;
      }
    
      attachNotificationReceived(handler: any) {
        this._onNotification = handler;
      }
    }
    
    const handler = new DemoNotificationHandler();
    
    PushNotification.configure({
      onRegister: handler.onRegister.bind(handler),
      onNotification: handler.onNotification.bind(handler),
      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },
      popInitialNotification: true,
      requestPermissions: true,
    });
    
    export default handler;
    
  2. 创建并实现 DemoNotificationService

    import PushNotification from 'react-native-push-notification';
    import DemoNotificationHandler from './DemoNotificationHandler';
    
    export default class DemoNotificationService {
      constructor(onTokenReceived: any, onNotificationReceived: any) {
        DemoNotificationHandler.attachTokenReceived(onTokenReceived);
        DemoNotificationHandler.attachNotificationReceived(onNotificationReceived);
        PushNotification.getApplicationIconBadgeNumber(function(number: number) {
          if(number > 0) {
            PushNotification.setApplicationIconBadgeNumber(0);
          }
        });
      }
    
      checkPermissions(cbk: any) {
        return PushNotification.checkPermissions(cbk);
      }
    
      requestPermissions() {
        return PushNotification.requestPermissions();
      }
    
      cancelNotifications() {
        PushNotification.cancelLocalNotifications();
      }
    
      cancelAll() {
        PushNotification.cancelAllLocalNotifications();
      }
    
      abandonPermissions() {
        PushNotification.abandonPermissions();
      }
    }
    
  3. 创建并实现 DemoNotificationRegistrationService

    export default class DemoNotificationService {
        constructor(
            readonly apiUrl: string,
            readonly apiKey: string) {
        }
    
    async registerAsync(request: any): Promise<Response> {
            const method = 'PUT';
            const registerApiUrl = `${this.apiUrl}/notifications/installations`;
            const result = await fetch(registerApiUrl, {
                method: method,
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                    'apiKey': this.apiKey
                },
                body: JSON.stringify(request)
            });
    
            this.validateResponse(registerApiUrl, method, request, result);
            return result;
        }
    
        async deregisterAsync(deviceId: string): Promise<Response> {
            const method = 'DELETE';
            const deregisterApiUrl = `${this.apiUrl}/notifications/installations/${deviceId}`;
            const result = await fetch(deregisterApiUrl, {
                method: method,
                headers: {
                    Accept: 'application/json',
                    'Content-Type': 'application/json',
                    'apiKey': this.apiKey
                }
            });
    
            this.validateResponse(deregisterApiUrl, method, null, result);
            return result;
        }
    
        private validateResponse(requestUrl: string, method: string, requestPayload: any, response: Response) {
            console.log(`Request: ${method} ${requestUrl} => ${JSON.stringify(requestPayload)}\nResponse: ${response.status}`);
            if (!response || response.status != 200) {
                throw `HTTP error ${response.status}: ${response.statusText}`;
            }
        }
    }
    
  4. 配置应用。 打开 package.json 并添加以下脚本定义:

    "configure": "cp .app.config.tsx src/config/AppConfig.tsx"
    

    然后执行此脚本,该脚本会将默认配置复制到 config 文件夹。

    yarn configure
    

    最后一步是使用 API 访问信息更新在上一步复制的配置文件。 指定 apiKeyapiUrl 参数:

    module.exports = {
        appName: "PushDemo",
        env: "production",
        apiUrl: "https://<azure-push-notifications-api-url>/api/",
        apiKey: "<api-auth-key>",
    };
    

实现跨平台 UI

  1. 定义页面布局

    <View style={styles.container}>
      {this.state.isBusy &&
        <ActivityIndicator></ActivityIndicator>
      }
      <View style={styles.button}>
        <Button title="Register" onPress={this.onRegisterButtonPress.bind(this)} disabled={this.state.isBusy} />
      </View>
      <View style={styles.button}>
        <Button title="Deregister" onPress={this.onDeregisterButtonPress.bind(this)} disabled={this.state.isBusy} />
      </View>
    </View>
    
  2. 应用样式

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        alignItems: "center",
        justifyContent: 'flex-end',
        margin: 50,
      },
      button: {
        margin: 5,
        width: "100%",
      }
    });
    
  3. 初始化页面组件

      state: IState;
      notificationService: DemoNotificationService;
      notificationRegistrationService: DemoNotificationRegistrationService;
      deviceId: string;
    
      constructor(props: any) {
        super(props);
        this.deviceId = DeviceInfo.getUniqueId();
        this.state = {
          status: "Push notifications registration status is unknown",
          registeredOS: "",
          registeredToken: "",
          isRegistered: false,
          isBusy: false,
        };
    
        this.notificationService = new DemoNotificationService(
          this.onTokenReceived.bind(this),
          this.onNotificationReceived.bind(this),
        );
    
        this.notificationRegistrationService = new DemoNotificationRegistrationService(
          Config.apiUrl,
          Config.apiKey,
        );
      }
    
  4. 定义按钮单击处理程序

      async onRegisterButtonPress() {
        if (!this.state.registeredToken || !this.state.registeredOS) {
          Alert.alert("The push notifications token wasn't received.");
          return;
        }
    
        let status: string = "Registering...";
        let isRegistered = this.state.isRegistered;
        try {
          this.setState({ isBusy: true, status });
          const pnPlatform = this.state.registeredOS == "ios" ? "apns" : "fcm";
          const pnToken = this.state.registeredToken;
          const request = {
            installationId: this.deviceId,
            platform: pnPlatform,
            pushChannel: pnToken,
            tags: []
          };
          const response = await this.notificationRegistrationService.registerAsync(request);
          status = `Registered for ${this.state.registeredOS} push notifications`;
          isRegistered = true;
        } catch (e) {
          status = `Registration failed: ${e}`;
        }
        finally {
          this.setState({ isBusy: false, status, isRegistered });
        }
      }
    
      async onDeregisterButtonPress() {
        if (!this.notificationService)
          return;
    
        let status: string = "Deregistering...";
        let isRegistered = this.state.isRegistered;
        try {
          this.setState({ isBusy: true, status });
          await this.notificationRegistrationService.deregisterAsync(this.deviceId);
          status = "Deregistered from push notifications";
          isRegistered = false;
        } catch (e) {
          status = `Deregistration failed: ${e}`;
        }
        finally {
          this.setState({ isBusy: false, status, isRegistered });
        }
      }
    
  5. 处理收到的令牌注册和推送通知

      onTokenReceived(token: any) {
        console.log(`Received a notification token on ${token.os}`);
        this.setState({ registeredToken: token.token, registeredOS: token.os, status: `The push notifications token has been received.` });
    
        if (this.state.isRegistered && this.state.registeredToken && this.state.registeredOS) {
          this.onRegisterButtonPress();
        }
      }
    
      onNotificationReceived(notification: any) {
        console.log(`Received a push notification on ${this.state.registeredOS}`);
        this.setState({ status: `Received a push notification...` });
    
        if (notification.data.message) {
          Alert.alert(AppConfig.appName, `${notification.data.action} action received`);
        }
      }
    };
    

为推送通知配置本机 Android 项目

配置所需的 Android 包

生成应用时 会自动链接 包。 若要完成配置过程,请在下面执行一些其他步骤。

配置 Android 清单

在“android/app/src/main/AndroidManifest.xml”中,验证包名称、权限和所需的服务。 请确保注册 RNPushNotificationPublisherRNPushNotificationBootEventReceiver 接收方,并注册了 RNPushNotificationListenerService 服务。 通知元数据可用于自定义推送通知的外观。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="YOUR_PACKAGE_NAME">

      <uses-permission android:name="android.permission.INTERNET" />
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      <uses-permission android:name="android.permission.VIBRATE" />
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

      <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme">

        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_name"
                    android:value="PushDemo Channel"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_channel_description"
                    android:value="PushDemo Channel Description"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_foreground"
                    android:value="true"/>
        <meta-data  android:name="com.dieam.reactnativepushnotification.notification_color"
                    android:resource="@android:color/white"/>

        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
        <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
            android:exported="false" >
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <activity
          android:name=".MainActivity"
          android:label="@string/app_name"
          android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
          android:launchMode="singleTask"
          android:windowSoftInputMode="adjustResize">
          <intent-filter>
              <action android:name="android.intent.action.MAIN" />
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
      </application>

</manifest>

配置 Google 服务

在“android/app/build.gradle”中注册 Google Services:

dependencies {
  ...
  implementation 'com.google.firebase:firebase-analytics:17.3.0'
  ...
}

apply plugin: 'com.google.gms.google-services'

将 FCM 设置期间下载的“google-services.json”文件复制到项目文件夹“android/app/”。

处理 Android 的推送通知

你配置了现有 RNPushNotificationListenerService 服务来处理传入的 Android 推送通知。 此服务是前面在应用程序清单中注册的。 它处理传入通知并将其代理到跨平台React Native部件。 无需执行其他步骤。

为推送通知配置本机 iOS 项目

配置所需的 iOS 包

生成应用时 会自动链接 包。 只需安装本机 Pod:

npx pod-install

配置 Info.plist 和 Entitlements.plist

  1. 转到“PushDemo/ios”文件夹,打开“PushDemo.xcworkspace”工作区,选择顶部项目“PushDemo”,然后选择“签名 & 功能”选项卡。

  2. 更新捆绑标识符以匹配预配配置文件中使用的值。

  3. 使用“+”按钮添加两个新功能:

    • 后台模式功能,并勾选远程通知。
    • 推送通知功能

处理 iOS 的推送通知

  1. 打开“AppDelegate.h”并添加以下导入:

    #import <UserNotifications/UNUserNotificationCenter.h>
    
  2. 通过添加 UNUserNotificationCenterDelegate来更新“AppDelegate”支持的协议列表:

    @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
    
  3. 打开“AppDelegate.m”并配置所有必需的 iOS 回调:

    #import <UserNotifications/UserNotifications.h>
    #import <RNCPushNotificationIOS.h>
    
    ...
    
    // Required to register for notifications
    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
    {
     [RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings];
    }
    
    // Required for the register event.
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
     [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
    }
    
    // Required for the notification event. You must call the completion handler after handling the remote notification.
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
    fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
    {
      [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
    }
    
    // Required for the registrationError event.
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
    {
     [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
    }
    
    // IOS 10+ Required for localNotification event
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center
    didReceiveNotificationResponse:(UNNotificationResponse *)response
             withCompletionHandler:(void (^)(void))completionHandler
    {
      [RNCPushNotificationIOS didReceiveNotificationResponse:response];
      completionHandler();
    }
    
    // IOS 4-10 Required for the localNotification event.
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
    {
     [RNCPushNotificationIOS didReceiveLocalNotification:notification];
    }
    
    //Called when a notification is delivered to a foreground app.
    -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
    {
      completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
    }
    

测试解决方案

现在可以测试通过后端服务发送通知。

发送测试通知

  1. Postman 中打开新选项卡。

  2. 将请求设置为 POST,并输入以下地址:

    https://<app_name>.azurewebsites.net/api/notifications/requests
    
  3. 如果选择完成 “使用 API 密钥对客户端进行身份验证” 部分,请确保将请求标头配置为包含 apikey 值。

    apikey <your_api_key>
  4. 选择“正文的原始选项,然后从格式选项列表中选择“JSON”,然后包括一些占位符 JSON 内容:

    {
        "text": "Message from Postman!",
        "action": "action_a"
    }
    
  5. 选择窗口右上角的“保存”按钮下的“代码”按钮。 如果为 HTML (显示请求,则请求应类似于以下示例,具体取决于是否包含 apikey 标头) :

    POST /api/notifications/requests HTTP/1.1
    Host: https://<app_name>.azurewebsites.net
    apikey: <your_api_key>
    Content-Type: application/json
    
    {
        "text": "Message from backend service",
        "action": "action_a"
    }
    
  6. AndroidiOS) (一个或两个目标平台上运行 PushDemo 应用程序。

    注意

    如果在 Android 上测试,请确保未在 调试中运行,或者如果应用已通过运行应用程序部署,则强制关闭应用,然后从启动器重新启动它。

  7. PushDemo 应用中,点击“ 注册 ”按钮。

  8. 返回到 Postman,关闭“ 生成代码片段 ”窗口 ((如果尚未这样做),) 然后单击“ 发送 ”按钮。

  9. 验证是否在 Postman 中收到“200 正常”响应,并且警报显示在显示已接收 ActionA 操作的应用中。

  10. 关闭 PushDemo 应用,然后在 Postman 中再次单击“发送”按钮。

  11. 验证是否再次在 Postman 中收到“200 正常”响应。 验证 PushDemo 应用的通知区域中是否显示正确的消息通知。

  12. 点击通知以确认它打开应用并显示 ActionA 操作收到的 警报。

  13. 返回到 Postman,修改上一个请求正文以发送指定action_b的无提示通知,而不是为操作action_a

    {
        "action": "action_b",
        "silent": true
    }
    
  14. 在应用仍处于打开状态的情况下,单击 Postman 中的“发送”按钮。

  15. 验证是否在 Postman 中收到“200 正常”响应,以及警报是否显示在显示已接收 ActionB 操作而不是已接收 ActionA 操作的应用中。

  16. 关闭 PushDemo 应用,然后在 Postman 中再次单击“发送”按钮。

  17. 验证 Postman 中是否收到“200 正常”响应,并且无提示通知未显示在通知区域中。

疑难解答

后端服务无响应

在本地测试时,请确保后端服务正在运行并使用正确的端口。

如果针对 Azure API 应用进行测试,检查服务正在运行,并且已部署并启动,且未出错。

请确保检查,在 Postman 中或通过客户端测试时,在移动应用配置中正确指定了基址。 在本地测试时,基址应指示为 https://<api_name>.azurewebsites.net/https://localhost:5001/

启动或停止调试会话后,Android 上未收到通知

请确保在启动或停止调试会话后再次注册。 调试器将导致生成新的 Firebase 令牌。 还必须更新通知中心安装。

从后端服务接收 401 状态代码

验证是否正在设置 apikey 请求标头,并且此值是否与为后端服务配置的值匹配。

如果在本地测试时收到此错误,请确保在客户端配置中定义的密钥值与 API 使用的 Authentication:ApiKey 用户设置值匹配。

如果要使用 API 应用进行测试,请确保客户端配置文件中的密钥值与 API 应用中使用的 Authentication:ApiKey 应用程序设置匹配。

注意

如果在部署后端服务后已创建或更改此设置,则必须重启该服务才能生效。

如果选择不完成 “使用 API 密钥对客户端进行身份验证” 部分,请确保未将 Authorize 属性应用于 NotificationsController 类。

从后端服务接收 404 状态代码

验证终结点和 HTTP 请求方法是否正确。 例如,终结点应指示为:

  • [PUT]https://<api_name>.azurewebsites.net/api/notifications/installations
  • [DELETE]https://<api_name>.azurewebsites.net/api/notifications/installations/<installation_id>
  • [POST]https://<api_name>.azurewebsites.net/api/notifications/requests

或者在本地测试时:

  • [PUT]https://localhost:5001/api/notifications/installations
  • [DELETE]https://localhost:5001/api/notifications/installations/<installation_id>
  • [POST]https://localhost:5001/api/notifications/requests

在客户端应用中指定基址时,请确保它以 /结尾。 在本地测试时,基址应指示为 https://<api_name>.azurewebsites.net/https://localhost:5001/

无法注册并显示通知中心错误消息

验证测试设备是否具有网络连接。 然后,通过设置断点来检查 HttpResponse 中的 StatusCode 属性值,确定 Http 响应状态代码。

根据状态代码查看前面的故障排除建议(如果适用)。

在返回相应 API 的这些特定状态代码的行上设置断点。 然后尝试在本地调试时调用后端服务。

使用适当的有效负载通过 Postman 验证后端服务是否按预期工作。 使用客户端代码为有问题的平台创建的实际有效负载。

查看特定于平台的配置部分,确保未错过任何步骤。 检查是否为 installation idtoken 和 变量解析了合适的值,以便为相应的平台。

显示设备错误消息无法解析 ID

查看特定于平台的配置部分,确保未错过任何步骤。

后续步骤

现在应该有一个基本的React Native应用通过后端服务连接到通知中心,并且可以发送和接收通知。

可能需要调整本教程中使用的示例,以适应自己的方案。 还建议实现更可靠的错误处理、重试逻辑和日志记录。

Visual Studio App Center 可以快速合并到移动应用中,提供分析和诊断以帮助进行故障排除。