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

下载示例 下载示例

本教程使用 azure 通知中心 将通知推送到面向 android AndroidiOSReact Native 应用程序。

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

这些操作使用 通知中心 SDK 来处理后端操作从应用后端注册 文档中提供了有关总体方法的更多详细信息。

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

先决条件

若要继续操作,需要:

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

对于 Android,必须具备:

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

对于 iOS,必须具备:

注意

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

可以按照此第一原则示例中的步骤操作,无需事先体验。 但是,你将受益于熟悉以下方面。

提供的步骤适用于 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. 切换到顶部的“Cloud Messaging”选项卡。 复制并保存 服务器密钥 供以后使用。 使用此值配置通知中心。

    复制服务器密钥

注册 iOS 应用以获取推送通知

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

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

    iOS 预配门户应用 ID 页

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

    iOS 预配门户 注册新 ID 页

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

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

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

      iOS 预配门户注册应用 ID 页

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

      表单以注册新的应用 ID

      此操作将生成应用 ID 和确认信息的请求。 选择 继续,然后选择 注册 以确认新的应用 ID。

      确认新的应用 ID

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

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

为通知中心创建证书

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

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

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

较新的方法在 APNS的基于令牌的身份验证(HTTP/2)身份验证中记录了许多优势。 需要更少的步骤,但也要求针对特定方案执行这些步骤。 但是,已为这两种方法提供步骤,因为两种方法都适用于本教程。

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

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

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

    注意

    默认情况下,Keychain Access 选择列表中的第一项。 如果你位于 证书 类别中,Apple Worldwide Developer Relations Certification Authority 不是列表中的第一项,则此问题可能是个问题。 在生成 CSR(证书签名请求)之前,请确保已选择非密钥项或 Apple 全球开发人员关系证书颁发机构 密钥。

  3. 选择 用户电子邮件地址,输入 公用名 值,确保指定保存到磁盘,然后选择 继续。 将 CA 电子邮件地址 保留为空,因为不需要。

    预期的证书信息

  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 推送服务 前缀,并具有与之关联的相应捆绑标识符。

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

    将证书导出为 p12 格式

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

    注意

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

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

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

    注意

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

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

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

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

    注意

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

  6. 密钥上,单击创建的密钥(或者选择使用该密钥的现有密钥)。

  7. 记下 密钥 ID 值。

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

    -----BEGIN 私钥-----
    <key_value>
    -----END PRIVATE KEY-----

    注意

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

在这些步骤结束时,应该有以下信息供稍后在 使用 APNS 信息配置通知中心

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

为应用创建预配配置文件

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

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

    配置文件列表

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

    选择应用 ID

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

    注意

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

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

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

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

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

    选择预配配置文件名称

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

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

创建通知中心

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

  1. 登录到 Azure

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

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

    基本详细信息

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

    命名空间详细信息

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

    注意

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

    通知中心详细信息

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

    注意

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

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

  5. 导航到新的 通知中心

  6. 从列表中选择 访问策略MANAGE下)。

  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. 开始调试(命令 + Enter)以测试模板化应用。

    注意

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

    如果系统提示你出现 找不到开发证书 消息:

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

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

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

  7. 删除 WeatherForecast.cs

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

    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 密钥不如令牌那么安全,但对于本教程而言,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.cs身份验证 文件夹中,然后添加以下实现。

    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身份验证 文件夹中,然后添加以下实现。

    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. Control + Click on the PushDemoApi project, choose New Folder from the Add menu, then click Add using Models as the Folder Name.

  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. 将新文件夹添加到名为 ServicesPushDemoApi 项目。

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

    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.csServices 文件夹中,然后添加以下代码来实现 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,但在这种情况下,表达式仅包含 OU(||)。 如果请求中有 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 控制器类,输入 NotificationsController名称,然后单击 “新建”。

    注意

    如果使用 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> 替换为在 Properties>launchSettings.json中找到的 https applicationUrl

    <applicationUrl>/api/notifications
    

    注意

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

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

    钥匙 价值
    apikey <your_api_key>
  11. 单击“发送”按钮

    注意

    应收到一些 JSON 内容 200 正常 状态。

    如果收到 SSL 证书验证 警告,则可以在 设置中切换 postman 请求 SSL 证书 验证。

  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 应用 的全局唯一名称

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

    资源组:
    在其中创建了通知中心,请选择相同的 资源组。

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

    注意

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

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

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

  5. 记下 概述顶部 Essentials 摘要中的 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 密钥 部分完成 对客户端进行身份验证时,才需要 身份验证: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. 单击 发送

    注意

    应从服务收到 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 捆绑包,并将其放入资产文件夹中。

    # 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(仿真器)或摇动设备以填充开发人员设置,导航到 Settings>Change Bundle Location,并使用默认端口指定 metro 服务器 IP 地址:<metro-server-ip-address>:8081

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

    注意

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

安装所需的包

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

  1. React 本机推送通知 iOS - Project GitHub

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

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

    此包以跨平台的方式在 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. 在一个或两个目标平台上运行 PushDemo 应用程序(AndroidiOS)。

    注意

    如果在 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 请求标头,此值与为后端服务配置的 apikey 匹配。

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

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

注意

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

如果选择不使用 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 可以快速合并到移动应用中,提供 分析诊断 来帮助进行故障排除。