使用云消息传递和云函数发送 Web 应用程序的通知

1. 概述

在此 Codelab 中,您将了解如何使用 Cloud Functions for Firebase 通过向聊天应用的用户发送通知来向聊天 Web 应用添加功能。

3b1284f5144b54f6.png

你将学到什么

  • 使用 Firebase SDK 创建 Google Cloud Functions。
  • 基于身份验证、Cloud Storage 和 Cloud Firestore 事件触发 Cloud Functions。
  • 向您的 Web 应用程序添加 Firebase 云消息传递支持。

你需要什么

  • 一张信用卡。 Cloud Functions for Firebase 需要 Firebase Blaze 计划,这意味着您必须使用信用卡对 Firebase 项目启用计费。
  • 您选择的 IDE/文本编辑器,例如WebStormAtomSublime
  • 安装了 NodeJS v9 后运行 shell 命令的终端。
  • Chrome 等浏览器。
  • 示例代码。请参阅下一步。

2. 获取示例代码

从命令行克隆GitHub 存储库

git clone https://github.com/firebase/friendlychat

导入入门应用程序

使用 IDE 打开或导入android_studio_folder.png示例代码目录中的cloud-functions-start目录。此目录包含 Codelab 的起始代码,其中包含功能齐全的聊天 Web 应用程序。

3. 创建 Firebase 项目并设置您的应用

创建项目

Firebase 控制台中,单击“添加项目”并将其命名为“FriendlyChat”

单击“创建项目”

升级至 Blaze 计划

为了使用 Cloud Functions for Firebase,您必须将 Firebase 项目升级到Blaze 计费计划。这需要您将信用卡或其他结算方式添加到您的 Google Cloud 帐户。

所有 Firebase 项目(包括 Blaze 计划中的项目)仍然可以使用 Cloud Functions 的免费使用配额。此 Codelab 中概述的步骤将属于免费套餐的使用限制范围。但是,您会看到用于托管 Cloud Functions 构建映像的 Cloud Storage 收取少量费用(约 0.03 美元)。

如果您无法使用信用卡或不愿意继续使用 Blaze 计划,请考虑使用Firebase 模拟器套件,它允许您在本地计算机上免费模拟 Cloud Functions。

启用谷歌身份验证

为了让用户登录应用程序,我们将使用需要启用的 Google 身份验证。

在 Firebase 控制台中,打开“构建”部分 > “身份验证” > “登录方法”选项卡(或单击此处转到此处)。然后,启用Google Sign-in Provider 并单击Save 。这将允许用户使用其 Google 帐户登录网络应用程序。

另外,请随意将您的应用程序的面向公众的名称设置为Friendly Chat

8290061806aacb46.png

启用云存储

该应用程序使用云存储上传图片。要在 Firebase 项目上启用 Cloud Storage,请访问“存储”部分并单击“开始”按钮。完成那里的步骤,对于云存储位置,将有一个要使用的默认值。之后单击“完成”

添加网络应用程序

在 Firebase 控制台上,添加一个 Web 应用。为此,请转到“项目设置”并向下滚动到“添加应用程序” 。选择 Web 作为平台并选中用于设置 Firebase 托管的复选框,然后注册应用程序并单击“下一步”执行其余步骤,最后单击“继续控制台”

4. 安装 Firebase 命令行界面

Firebase 命令行界面 (CLI) 将允许您在本地提供 Web 应用程序并部署 Web 应用程序和 Cloud Functions。

要安装或升级 CLI,请运行以下 npm 命令:

npm -g install firebase-tools

要验证 CLI 是否已正确安装,请打开控制台并运行:

firebase --version

确保 Firebase CLI 的版本高于4.0.0 ,以便它具有 Cloud Functions 所需的所有最新功能。如果没有,请运行npm install -g firebase-tools进行升级,如上所示。

通过运行以下命令来授权 Firebase CLI:

firebase login

确保您位于cloud-functions-start目录中,然后设置 Firebase CLI 以使用您的 Firebase 项目:

firebase use --add

接下来,选择您的项目 ID 并按照说明进行操作。出现提示时,您可以选择任何别名,例如codelab

5. 部署并运行Web应用程序

现在您已经导入并配置了项目,您可以首次运行 Web 应用程序了!打开终端窗口,导航到cloud-functions-start文件夹,然后使用以下命令将 Web 应用程序部署到 Firebase 托管:

firebase deploy --except functions

这是您应该看到的控制台输出:

i deploying database, storage, hosting
✔  database: rules ready to deploy.
i  storage: checking rules for compilation errors...
✔  storage: rules file compiled successfully
i  hosting: preparing ./ directory for upload...
✔  hosting: ./ folder uploaded successfully
✔ storage: rules file compiled successfully
✔ hosting: 8 files uploaded successfully
i starting release process (may take several minutes)...

✔ Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview
Hosting URL: https://friendlychat-1234.firebaseapp.com

打开网络应用程序

最后一行应显示托管 URL。现在应该从此 URL 提供 Web 应用程序,其格式应为 https://<project-id>.firebaseapp.com。打开它。您应该会看到聊天应用程序的功能 UI。

使用“使用 GOOGLE 登录”按钮登录应用程序,并随意添加一些消息和发布图像:

3b1284f5144b54f6.png

如果您首次在新浏览器上登录应用程序,请确保在出现提示时允许通知: 8b9d0c66dc36153d.png

我们稍后需要启用通知。

如果您不小心单击了“阻止” ,则可以通过单击 Chrome 多功能栏中 URL 左侧的“安全”按钮并切换“通知”旁边的栏来更改此设置:

e926868b0546ed71.png

现在,我们将使用 Firebase SDK for Cloud Functions 添加一些功能。

6. 函数目录

Cloud Functions 允许您轻松拥有在云中运行的代码,而无需设置服务器。我们将逐步介绍如何构建对 Firebase Auth、Cloud Storage 和 Firebase Firestore 数据库事件做出反应的函数。让我们从 Auth 开始。

使用适用于 Cloud Functions 的 Firebase SDK 时,您的 Functions 代码将位于functions目录下(默认情况下)。您的 Functions 代码也是Node.js应用程序,因此需要一个package.json来提供有关您的应用程序的一些信息并列出依赖项。

为了让您更轻松,我们已经创建了您的代码所在的functions/index.js文件。在继续之前请随意检查此文件。

cd functions
ls

如果您不熟悉Node.js ,在继续 Codelab 之前了解更多相关信息将会有所帮助。

package.json文件已列出两个必需的依赖项: Firebase SDK for Cloud FunctionsFirebase Admin SDK 。要在本地安装它们,请转到functions文件夹并运行:

npm install

现在让我们看一下index.js文件:

索引.js

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 * ...
 */

// TODO(DEVELOPER): Import the Cloud Functions for Firebase and the Firebase Admin modules here.

// TODO(DEVELOPER): Write the addWelcomeMessage Function here.

// TODO(DEVELOPER): Write the blurImages Function here.

// TODO(DEVELOPER): Write the sendNotification Function here.

我们将导入所需的模块,然后编写三个函数来代替 TODO。让我们从导入所需的 Node 模块开始。

7. 导入 Cloud Functions 和 Firebase 管理模块

在此 Codelab 期间将需要两个模块: firebase-functions支持编写 Cloud Functions 触发器和日志,而firebase-admin支持在具有管理员访问权限的服务器上使用 Firebase 平台来执行操作,例如写入 Cloud Firestore 或发送 FCM 通知。

index.js文件中,将第一个TODO替换为以下内容:

索引.js

/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 * ...
 */

// Import the Firebase SDK for Google Cloud Functions.
const functions = require('firebase-functions');
// Import and initialize the Firebase Admin SDK.
const admin = require('firebase-admin');
admin.initializeApp();

// TODO(DEVELOPER): Write the addWelcomeMessage Function here.

// TODO(DEVELOPER): Write the blurImages Function here.

// TODO(DEVELOPER): Write the sendNotification Function here.

当部署到 Cloud Functions 环境或其他 Google Cloud Platform 容器时,Firebase Admin SDK 可以自动配置,当我们不带参数调用admin.initializeApp()时,就会发生这种情况。

现在,让我们添加一个在用户首次登录聊天应用程序时运行的函数,并且我们将添加一条聊天消息来欢迎用户。

8. 欢迎新用户

聊天消息结构

发布到FriendlyChat聊天源的消息存储在Cloud Firestore中。让我们看一下用于消息的数据结构。为此,请在聊天中发布一条新消息,内容为“Hello World”:

11f5a676fbb1a69a.png

这应该显示为:

fe6d1c020d0744cf.png

在 Firebase 控制台中,单击“构建”部分下的“Firestore 数据库” 。您应该会看到消息集合和一份包含您所写消息的文档:

442c9c10b5e2b245.png

如您所见,聊天消息作为文档存储在 Cloud Firestore 中,并将nameprofilePicUrltexttimestamp属性添加到messages集合中。

添加欢迎消息

第一个云功能添加了一条欢迎新用户加入聊天的消息。为此,我们可以使用触发器functions.auth().onCreate ,它会在用户每次在Firebase应用程序中首次登录时运行该函数。将addWelcomeMessages函数添加到您的index.js文件中:

索引.js

// Adds a message that welcomes new users into the chat.
exports.addWelcomeMessages = functions.auth.user().onCreate(async (user) => {
  functions.logger.log('A new user signed in for the first time.');
  const fullName = user.displayName || 'Anonymous';

  // Saves the new welcome message into the database
  // which then displays it in the FriendlyChat clients.
  await admin.firestore().collection('messages').add({
    name: 'Firebase Bot',
    profilePicUrl: '/images/firebase-logo.png', // Firebase logo
    text: `${fullName} signed in for the first time! Welcome!`,
    timestamp: admin.firestore.FieldValue.serverTimestamp(),
  });
  functions.logger.log('Welcome message written to database.');
});

将此函数添加到特殊exports对象是 Node 使该函数可在当前文件外部访问的方法,并且是 Cloud Functions 所必需的。

在上面的函数中,我们将“Firebase Bot”发布的新欢迎消息添加到聊天消息列表中。我们通过在 Cloud Firestore 中的messages集合上使用add方法来实现此目的,这是存储聊天消息的位置。

由于这是一个异步操作,我们需要返回Promise来指示 Cloud Firestore 何时完成写入,以便 Cloud Functions 不会过早执行。

部署云函数

Cloud Functions 仅在部署后才会激活。为此,请在命令行上运行以下命令:

firebase deploy --only functions

这是您应该看到的控制台输出:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
⚠  functions: missing necessary APIs. Enabling now...
i  env: ensuring necessary APIs are enabled...
⚠  env: missing necessary APIs. Enabling now...
i  functions: waiting for APIs to activate...
i  env: waiting for APIs to activate...
✔  env: all necessary APIs are enabled
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
✔  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: creating function addWelcomeMessages...
✔  functions[addWelcomeMessages]: Successful create operation. 
✔  functions: all functions deployed successfully!

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlypchat-1234/overview

测试功能

成功部署该功能后,您将需要有一个首次登录的用户。

  1. 使用托管 URL(格式为https://<project-id>.firebaseapp.com )在浏览器中打开您的应用程序。
  2. 对于新用户,请使用“登录”按钮首次登录您的应用程序。

262535d1b1223c65.png

  1. 登录后,应自动显示欢迎消息:

1c70e0d64b23525b.png

9. 图片审核

用户可以在聊天中上传所有类型的图像,因此控制攻击性图像始终很重要,尤其是在公共社交平台上。在FriendlyChat中,发布到聊天的图像存储在Google Cloud Storage中。

借助 Cloud Functions,您可以使用functions.storage().onFinalize触发器检测新的图像上传。每次在 Cloud Storage 中上传或修改新文件时,都会运行该命令。

为了审核图像,我们将执行以下过程:

  1. 使用Cloud Vision API检查图像是否被标记为成人或暴力。
  2. 如果图像已被标记,请将其下载到正在运行的 Functions 实例上。
  3. 使用ImageMagick模糊图像。
  4. 将模糊图像上传到云存储。

启用云视觉API

由于我们将在此函数中使用 Google Cloud Vision API,因此您必须在 firebase 项目上启用该 API。点击此链接,然后选择您的 Firebase 项目并启用 API:

5c77fee51ec5de49.png

安装依赖项

为了审核图像,我们将使用适用于 Node.js 的 Google Cloud Vision 客户端库@google-cloud/vision ,通过 Cloud Vision API 运行图像以检测不当图像。

要将此包安装到您的 Cloud Functions 应用程序中,请运行以下npm install --save命令。确保从functions目录执行此操作。

npm install --save @google-cloud/vision@2.4.0

这将在本地安装包并将它们添加为package.json文件中声明的依赖项。

导入并配置依赖项

要导入已安装的依赖项以及我们在本节中需要的一些 Node.js 核心模块( pathosfs ),请将以下行添加到index.js文件的顶部:

索引.js

const Vision = require('@google-cloud/vision');
const vision = new Vision.ImageAnnotatorClient();
const {promisify} = require('util');
const exec = promisify(require('child_process').exec);

const path = require('path');
const os = require('os');
const fs = require('fs');

由于您的函数将在 Google Cloud 环境中运行,因此无需配置 Cloud Storage 和 Cloud Vision 库:它们将自动配置为使用您的项目。

检测不适当的图像

您将使用functions.storage.onChange Cloud Functions 触发器,该触发器会在 Cloud Storage 存储桶中创建或修改文件或文件夹后立即运行您的代码。将blurOffensiveImages函数添加到您的index.js文件中:

索引.js

// Checks if uploaded images are flagged as Adult or Violence and if so blurs them.
exports.blurOffensiveImages = functions.runWith({memory: '2GB'}).storage.object().onFinalize(
    async (object) => {
      const imageUri = `gs://${object.bucket}/${object.name}`;
      // Check the image content using the Cloud Vision API.
      const batchAnnotateImagesResponse = await vision.safeSearchDetection(imageUri);
      const safeSearchResult = batchAnnotateImagesResponse[0].safeSearchAnnotation;
      const Likelihood = Vision.protos.google.cloud.vision.v1.Likelihood;
      if (Likelihood[safeSearchResult.adult] >= Likelihood.LIKELY ||
          Likelihood[safeSearchResult.violence] >= Likelihood.LIKELY) {
        functions.logger.log('The image', object.name, 'has been detected as inappropriate.');
        return blurImage(object.name);
      }
      functions.logger.log('The image', object.name, 'has been detected as OK.');
    });

请注意,我们添加了将运行该函数的 Cloud Functions 实例的一些配置。使用.runWith({memory: '2GB'}) ,我们请求实例获得 2GB 内存而不是默认内存,因为此函数是内存密集型的。

当该功能被触发时,图像将通过 Cloud Vision API 运行,以检测其是否被标记为成人或暴力。如果根据这些标准检测到图像不合适,我们就会对图像进行模糊处理,这在我们接下来将看到的blurImage函数中完成。

图像模糊

在您的index.js文件中添加以下blurImage函数:

索引.js

// Blurs the given image located in the given bucket using ImageMagick.
async function blurImage(filePath) {
  const tempLocalFile = path.join(os.tmpdir(), path.basename(filePath));
  const messageId = filePath.split(path.sep)[1];
  const bucket = admin.storage().bucket();

  // Download file from bucket.
  await bucket.file(filePath).download({destination: tempLocalFile});
  functions.logger.log('Image has been downloaded to', tempLocalFile);
  // Blur the image using ImageMagick.
  await exec(`convert "${tempLocalFile}" -channel RGBA -blur 0x24 "${tempLocalFile}"`);
  functions.logger.log('Image has been blurred');
  // Uploading the Blurred image back into the bucket.
  await bucket.upload(tempLocalFile, {destination: filePath});
  functions.logger.log('Blurred image has been uploaded to', filePath);
  // Deleting the local file to free up disk space.
  fs.unlinkSync(tempLocalFile);
  functions.logger.log('Deleted local file.');
  // Indicate that the message has been moderated.
  await admin.firestore().collection('messages').doc(messageId).update({moderated: true});
  functions.logger.log('Marked the image as moderated in the database.');
}

在上述函数中,图像二进制文件是从 Cloud Storage 下载的。然后使用 ImageMagick 的convert工具对图像进行模糊处理,并将模糊版本重新上传到存储桶上。接下来,我们删除 Cloud Functions 实例上的文件以释放一些磁盘空间,这样做是因为同一个 Cloud Functions 实例可以重复使用,如果不清理文件,可能会耗尽磁盘空间。最后,我们在聊天消息中添加一个布尔值,指示图像已被审核,这将触发客户端上消息的刷新。

部署功能

该功能仅在部署后才会激活。在命令行上,运行firebase deploy --only functions

firebase deploy --only functions

这是您应该看到的控制台输出:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
✔  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: updating function addWelcomeMessages...
i  functions: creating function blurOffensiveImages...
✔  functions[addWelcomeMessages]: Successful update operation.
✔  functions[blurOffensiveImages]: Successful create operation.
✔  functions: all functions deployed successfully!

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview

测试功能

函数部署成功后:

  1. 使用托管 URL(格式为https://<project-id>.firebaseapp.com )在浏览器中打开您的应用程序。
  2. 登录应用程序后,上传图像: 4db9fdab56703e4a.png
  3. 选择要上传的最佳攻击性图像(或者您可以使用这个食肉僵尸!),过了一会儿,您应该会看到您的帖子刷新,并显示图像的模糊版本: 83dd904fbaf97d2b.png

10. 新消息通知

在此部分中,您将添加一个云函数,用于在发布新消息时向聊天参与者发送通知。

使用Firebase Cloud Messaging (FCM),您可以跨平台可靠地向用户发送通知。要向用户发送通知,您需要他们的 FCM 设备令牌。当用户在新的浏览器或设备上首次打开该应用程序时,我们正在使用的聊天 Web 应用程序已经收集了用户的设备令牌。这些令牌存储在 Cloud Firestore 中的fcmTokens集合中。

如果您想了解如何在 Web 应用程序上获取 FCM 设备令牌,可以访问Firebase Web Codelab

发送通知

要检测何时发布新消息,您将使用functions.firestore.document().onCreate Cloud Functions 触发器,该触发器会在 Cloud Firestore 的给定路径创建新对象时运行您的代码。将sendNotifications函数添加到您的index.js文件中:

索引.js

// Sends a notifications to all users when a new message is posted.
exports.sendNotifications = functions.firestore.document('messages/{messageId}').onCreate(
  async (snapshot) => {
    // Notification details.
    const text = snapshot.data().text;
    const payload = {
      notification: {
        title: `${snapshot.data().name} posted ${text ? 'a message' : 'an image'}`,
        body: text ? (text.length <= 100 ? text : text.substring(0, 97) + '...') : '',
        icon: snapshot.data().profilePicUrl || '/images/profile_placeholder.png',
        click_action: `https://${process.env.GCLOUD_PROJECT}.firebaseapp.com`,
      }
    };

    // Get the list of device tokens.
    const allTokens = await admin.firestore().collection('fcmTokens').get();
    const tokens = [];
    allTokens.forEach((tokenDoc) => {
      tokens.push(tokenDoc.id);
    });

    if (tokens.length > 0) {
      // Send notifications to all tokens.
      const response = await admin.messaging().sendToDevice(tokens, payload);
      await cleanupTokens(response, tokens);
      functions.logger.log('Notifications have been sent and tokens cleaned up.');
    }
  });

在上面的函数中,我们从 Cloud Firestore 数据库收集所有用户的设备令牌,并使用admin.messaging().sendToDevice函数向每个用户发送通知。

清理令牌

最后,我们要删除不再有效的令牌。当我们曾经从用户那里获得的令牌不再被浏览器或设备使用时,就会发生这种情况。例如,如果用户撤销了浏览器会话的通知权限,就会发生这种情况。为此,请在index.js文件中添加以下cleanupTokens函数:

索引.js

// Cleans up the tokens that are no longer valid.
function cleanupTokens(response, tokens) {
 // For each notification we check if there was an error.
 const tokensDelete = [];
 response.results.forEach((result, index) => {
   const error = result.error;
   if (error) {
     functions.logger.error('Failure sending notification to', tokens[index], error);
     // Cleanup the tokens that are not registered anymore.
     if (error.code === 'messaging/invalid-registration-token' ||
         error.code === 'messaging/registration-token-not-registered') {
       const deleteTask = admin.firestore().collection('fcmTokens').doc(tokens[index]).delete();
       tokensDelete.push(deleteTask);
     }
   }
 });
 return Promise.all(tokensDelete);
}

部署功能

该函数仅在部署后才会激活,要部署它,请在命令行中运行以下命令:

firebase deploy --only functions

这是您应该看到的控制台输出:

i  deploying functions
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (X.XX KB) for uploading
✔  functions: functions folder uploaded successfully
i  starting release process (may take several minutes)...
i  functions: updating function addWelcomeMessages...
i  functions: updating function blurOffensiveImages...
i  functions: creating function sendNotifications...
✔  functions[addWelcomeMessages]: Successful update operation.
✔  functions[blurOffensiveImages]: Successful updating operation.
✔  functions[sendNotifications]: Successful create operation.
✔  functions: all functions deployed successfully!

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/friendlychat-1234/overview

测试功能

  1. 函数部署成功后,使用托管 URL(格式为https://<project-id>.firebaseapp.com )在浏览器中打开您的应用程序。
  2. 如果您是第一次登录应用程序,请确保在出现提示时允许通知: 8b9d0c66dc36153d.png
  3. 关闭聊天应用程序选项卡或显示其他选项卡:仅当应用程序位于后台时才会显示通知。如果您想了解如何在应用程序位于前台时接收消息,请查看我们的文档
  4. 使用不同的浏览器(或隐身窗口)登录应用程序并发布消息。您应该会看到第一个浏览器显示的通知: 45282ab12b28b926.png

11. 恭喜!

您已使用适用于 Cloud Functions 的 Firebase SDK 并向聊天应用添加了服务器端组件。

我们涵盖的内容

  • 使用 Firebase SDK for Cloud Functions 编写 Cloud Functions。
  • 根据身份验证、Cloud Storage 和 Cloud Firestore 事件触发 Cloud Functions。
  • 向您的 Web 应用程序添加 Firebase 云消息传递支持。
  • 使用 Firebase CLI 部署 Cloud Functions。

下一步

了解更多