Firebase 的云函数

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

一、概述

在此 Codelab 中,您将了解如何使用 Firebase SDK for Google Cloud Functions 来改进 Chat Web 应用,以及如何使用 Cloud Functions 向 Chat 应用的用户发送通知。

3b1284f5144b54f6.png

你会学到什么

  • 使用 Firebase SDK 创建 Google Cloud Functions。
  • 根据 Auth、Cloud Storage 和 Cloud Firestore 事件触发 Cloud Functions。
  • 将 Firebase 云消息传递支持添加到您的 Web 应用程序。

你需要什么

  • 一张信用卡。 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 的起始代码,其中包含一个功能齐全的 Chat Web App。

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

创建项目

Firebase Console中,单击Add Project并将其命名为FriendlyChat

单击创建项目

升级到 Blaze 计划

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

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

如果您无法使用信用卡或对继续使用 Blaze 计划感到不舒服,请考虑使用Firebase Emulator Suite ,它允许您在本地计算机上免费模拟 Cloud Functions。

启用谷歌身份验证

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

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

此外,您可以随意将您的应用程序的公开名称设置为Friendly Chat

8290061806aacb46.png

启用云存储

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

添加网络应用

在 Firebase 控制台上,添加一个 Web 应用。为此,请转到项目设置并向下滚动到添加应用程序。选择 web 作为平台并选中设置 Firebase Hosting 的复选框,然后注册应用程序并单击Next进行其余步骤,最后单击Continue to console

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。

使用SIGN-IN WITH GOOGLE按钮登录应用程序,然后随意添加一些消息和发布图片:

3b1284f5144b54f6.png

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

我们需要稍后启用通知。

如果您不小心点击了Block ,您可以通过点击 Chrome Omnibar 中 URL 左侧的🔒 Secure按钮并切换Notifications旁边的栏来更改此设置:

e926868b0546ed71.png

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

6.函数目录

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

使用 Firebase SDK for Cloud Functions 时,您的 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文件:

index.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替换为以下内容:

index.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.

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

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

8. 欢迎新用户

聊天消息结构

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

11f5a676fbb1a69a.png

这应该显示为:

fe6d1c020d0744cf.png

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

442c9c10b5e2b245.png

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

添加欢迎信息

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

index.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. 对于新用户,首次使用登录按钮在您的应用中登录。
  • 如果您已经登录到应用程序,您可以打开Firebase 控制台身份验证并从用户列表中删除您的帐户。然后,再次登录。

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. 将模糊图像上传到 Cloud Storage。

启用 Cloud Vision 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文件的顶部:

index.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文件中:

index.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函数:

index.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 云消息传递(FCM),您可以跨平台可靠地向用户发送通知。要向用户发送通知,您需要他们的 FCM 设备令牌。我们使用的聊天 Web 应用程序在用户首次在新浏览器或设备上打开应用程序时已经从用户那里收集了设备令牌。这些令牌存储在 Cloud Firestore 的fcmTokens集合中。

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

发送通知

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

index.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函数:

index.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. 恭喜!

您已使用 Firebase SDK for Cloud Functions 并将服务器端组件添加到聊天应用程序。

我们涵盖的内容

  • 使用 Firebase SDK for Cloud Functions 创作 Cloud Functions。
  • 根据 Auth、Cloud Storage 和 Cloud Firestore 事件触发 Cloud Functions。
  • 将 Firebase 云消息传递支持添加到您的 Web 应用程序。
  • 使用 Firebase CLI 部署 Cloud Functions。

下一步

学到更多