将 Firebase 与 Next.js 应用集成

1. 准备工作

在此 Codelab 中,您将了解如何将 Firebase 与名为 Friends Eats 的 Next.js Web 应用相集成,该应用是一个餐厅评价网站。

FRIEND Eats Web 应用

完成后的 Web 应用提供了一些实用功能,展示了 Firebase 如何帮助您构建 Next.js 应用。这些功能包括:

  • 自动构建和部署:此 Codelab 使用 Firebase App Hosting 在您每次推送到已配置的分支时自动构建和部署 Next.js 代码。
  • 登录和退出:完成后的 Web 应用支持您使用 Google 账号登录和退出账号。用户登录和保留功能完全通过 Firebase Authentication 进行管理。
  • 图片:完成后的 Web 应用可让登录用户上传餐馆图片。图片素材资源存储在 Cloud Storage for Firebase 中。Firebase JavaScript SDK 提供了已上传图片的公开网址。然后,此公开网址将存储在 Cloud Firestore 内的相关餐馆文档中。
  • 评价:完成后的 Web 应用支持登录用户发布对餐厅的评价,其中包含星级评分和基于文本的消息。评价信息存储在 Cloud Firestore 中。
  • 过滤条件:完成后的 Web 应用可让登录用户根据类别、位置和价格过滤餐馆列表。您还可以自定义所使用的排序方法。系统会从 Cloud Firestore 访问数据,并根据所使用的过滤条件应用 Firestore 查询。

前提条件

  • GitHub 账号
  • 了解 Next.js 和 JavaScript 相关知识

学习内容

所需条件

  • Git
  • 最新的稳定版 Node.js
  • 您所选的浏览器(例如 Google Chrome)
  • 包含代码编辑器和终端的开发环境
  • 一个用于创建和管理 Firebase 项目的 Google 账号
  • 能够将 Firebase 项目升级为 Blaze 定价方案

2. 设置开发环境和 GitHub 代码库

此 Codelab 提供了应用的起始代码库,并依赖于 Firebase CLI。

创建一个 GitHub 代码库。

您可以在 https://github.com/firebase/friendlyeats-web 找到 Codelab 源代码。该代码库包含适用于多个平台的示例项目。不过,此 Codelab 仅使用 nextjs-start 目录。请注意以下目录:

* `nextjs-start`: contains the starter code upon which you build. * `nextjs-end`: contains the solution code for the finished web app. 

nextjs-start 文件夹复制到您自己的代码库中:

  1. 使用终端在计算机上创建一个新文件夹,然后切换到该新目录:
    mkdir codelab-friendlyeats-web  cd codelab-friendlyeats-web 
  2. 使用 giget npm 软件包仅提取 nextjs-start 文件夹:
    npx giget@latest "gh:firebase/friendlyeats-web/nextjs-start#master" . --install 
  3. 使用 Git 在本地跟踪更改:
    git init  git add .  git commit -m "Codelab starting point"  git branch -M main 
  4. 创建一个新的 GitHub 代码库:https://github.com/new。您可以随意为其命名。
  5. 根据您向 GitHub 进行身份验证的方式(HTTPS 或 SSH),复制 GitHub 为您创建的新网址:
    • https://github.com/<USER_NAME>/<REPOSITORY_NAME>.git
    • [email protected]:<USER_NAME>/<REPOSITORY_NAME>.git
  6. 运行以下命令,将本地更改推送到新的 GitHub 代码库。将 <REPOSITORY_URL> 占位符替换为您的实际代码库网址。
    git remote add origin <REPOSITORY_URL>  git push -u origin main 
  7. 您现在应该会在 GitHub 代码库中看到起始代码。

安装或更新 Firebase CLI

运行以下命令,验证是否已安装 Firebase CLI 且版本为 14.1.0 或更高版本:

firebase --version 

如果您看到的是较低版本,或者您尚未安装 Firebase CLI,请运行安装命令:

npm install -g firebase-tools@latest 

如果由于权限错误而无法安装 Firebase CLI,请参阅 npm 文档或使用其他安装选项

登录 Firebase

  1. 运行以下命令以登录 Firebase CLI:
    firebase login 
  2. 根据您是否希望 Firebase 收集数据,请输入 YN
  3. 在浏览器中,选择您的 Google 账号,然后点击允许

3. 设置您的 Firebase 项目

在本部分中,您将设置一个 Firebase 项目,并向其关联一个 Firebase Web 应用。您还将设置示例 Web 应用使用的 Firebase 服务。

创建 Firebase 项目

  1. 使用您在上一步中使用的同一 Google 账号登录 Firebase 控制台
  2. 点击相应按钮以创建新项目,然后输入项目名称(例如 FriendlyEats Codelab)。
  3. 点击继续
  4. 如果看到相关提示,请查看并接受 Firebase 条款,然后点击继续
  5. (可选)在 Firebase 控制台中启用 AI 辅助功能(称为“Gemini in Firebase”)。
  6. 在此 Codelab 中,您不需要使用 Google Analytics,因此请关闭 Google Analytics 选项。
  7. 点击创建项目,等待项目完成预配,然后点击继续

升级您的 Firebase 定价方案

如需使用 Firebase App Hosting 和 Cloud Storage for Firebase,您的 Firebase 项目必须采用随用随付 (Blaze) 定价方案,这意味着该项目与一个 Cloud Billing 账号相关联。

  • Cloud Billing 账号要求提供付款方式,例如信用卡。
  • 如果您刚开始接触 Firebase 和 Google Cloud,请确认您是否有资格获得 $300 赠金和免费试用 Cloud Billing 账号
  • 如果您是在活动中完成此 Codelab,请询问活动组织者是否有可用的 Cloud 积分。

如需将项目升级到 Blaze 方案,请按以下步骤操作:

  1. 在 Firebase 控制台中,选择升级您的方案
  2. 选择 Blaze 方案。按照屏幕上的说明将 Cloud Billing 账号与您的项目相关联。
    如果您需要在此升级过程中创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。

向 Firebase 项目添加 Web 应用

  1. 前往 Firebase 项目中的项目概览页面,点击添加应用,然后点击 Web
  2. 应用别名文本框中,输入一个容易记住的应用别名,例如 My Next.js app
  3. 请勿选中还为此应用设置 Firebase Hosting 复选框。
  4. 点击注册应用 > 继续前往控制台

在 Firebase 控制台中设置 Firebase 服务

设置身份验证

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择身份验证
  2. 点击开始使用
  3. 登录提供方列中,点击 Google > 启用
  4. 项目的公开名称文本框中,输入一个容易记住的名称,例如 My Next.js app
  5. 项目的支持电子邮件地址下拉列表中,选择您的电子邮件地址。
  6. 点击保存

设置 Cloud Firestore

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择 Firestore 数据库
  2. 点击创建数据库
  3. 选择标准版,然后点击下一步
  4. 请勿更改数据库 ID,将其保留为 (default)
  5. 为数据库选择一个位置,然后点击下一步
    对于真实应用,您需要选择靠近用户的位置。
  6. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。
  7. 点击创建

设置 Cloud Storage for Firebase

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择存储
  2. 点击开始使用
  3. 为默认存储分区选择位置。
    US-WEST1US-CENTRAL1US-EAST1 中的存储分区可为 Google Cloud Storage 使用“始终免费”层级。所有其他位置的存储分区都遵循 Google Cloud Storage 价格和用量
  4. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。
  5. 点击创建

部署安全规则

该代码已经为 Firestore 和 Cloud Storage for Firebase 设置了一组安全规则。部署安全规则后,数据库和存储桶中的数据可以得到更好的保护,避免遭到滥用。

  1. 在终端中,将 CLI 配置为使用您之前创建的 Firebase 项目:
    firebase use --add 
    当系统提示输入别名时,输入 friendlyeats-codelab
  2. 如需部署这些安全规则(以及稍后需要的索引),请在终端中运行以下命令:
    firebase deploy --only firestore,storage 
  3. 如果系统询问您:"Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?",请按 Enter 选择

4. 查看起始代码库

在本部分中,您将复习应用起始代码库中的几个部分,以便在此 Codelab 中向这些部分添加功能。

文件夹和文件结构

下表简要介绍了应用的文件夹和文件结构:

文件夹和文件

说明

src/components

适用于过滤条件、标题、餐馆详细信息和评价的 React 组件

src/lib

不一定绑定到 React 或 Next.js 的实用函数

src/lib/firebase

Firebase 专属代码和 Firebase 配置

public

Web 应用中的静态资源,例如图标

src/app

使用 Next.js 应用路由器进行路由

package.jsonpackage-lock.json

使用 npm 的项目依赖项

next.config.js

Next.js 专属配置(已启用服务器操作)

jsconfig.json

JavaScript 语言服务配置

服务器和客户端组件

该应用是使用应用路由器的 Next.js Web 应用。整个应用都会使用服务器渲染。例如,src/app/page.js 文件是负责主页面的服务器组件。src/components/RestaurantListings.jsx 文件是一个客户端组件,由文件开头的 "use client" 指令表示。

Import 语句

您可能会注意到如下 import 语句:

import RatingPicker from "@/src/components/RatingPicker.jsx"; 

应用使用 @ 符号来避免复杂的相对导入路径,这是通过路径别名实现的。

Firebase 专属 API

所有 Firebase API 代码都封装在 src/lib/firebase 目录中。然后,各个 React 组件会从 src/lib/firebase 目录导入封装的函数,而不是直接导入 Firebase Functions 函数。

模拟数据

模拟餐厅和评价数据包含在 src/lib/randomData.js 文件中。该文件中的数据会汇编在 src/lib/fakeRestaurants.js 文件的代码中。

5. 创建 App Hosting 后端

在本部分中,您将设置一个 App Hosting 后端,以监控 Git 代码库中的分支。

在本部分结束时,您将拥有一个与 GitHub 中的代码库相关联的 App Hosting 后端,每当您将新提交推送到 main 分支时,该后端都会自动重新构建并推出新版应用。

创建一个后端

  1. 前往 Firebase 控制台中的 App Hosting 页面
  2. 点击开始以启动后端创建流程。
  3. 选择区域。对于真实应用,您应选择离用户最近的区域。
  4. 按照导入 GitHub 代码库步骤中的提示设置 GitHub 身份验证。
  5. 代码库中,选择授予对 GitHub 中新代码库的访问权限,然后按照提示操作,以启用对您之前创建的 GitHub 代码库的访问权限。
  6. 点击刷新列表以刷新列表,然后选择您的代码库并点击下一步
  7. 设置部署设置:
    1. 将正式版分支设置为 main
    2. 将根目录保留为 /
    3. 启用自动发布。
  8. 为后端命名 friendlyeats-codelab,然后点击下一步
  9. 关联 Firebase Web 应用中,选择选择现有的 Firebase Web 应用,然后从列表中选择您添加的应用
  10. 点击完成并部署。系统会将您重定向到新页面,您可以在其中查看新 App Hosting 后端的运行状态!
  11. 点击查看可查看有关 App Hosting 部署的更多信息,包括发布状态、日志和使用情况详情。
  12. 推出完成后,点击网域下的网站网址以打开您的网站。由于 DNS 传播,这可能需要几分钟时间才能开始工作。
  13. 糟糕!加载页面时,您会看到一条错误消息,内容为“应用错误:发生了服务器端异常(请参阅服务器日志了解详情)”。
  14. 在 Firebase 控制台中,查看 App Hosting 后端的日志标签页。您会看到“Error: not implemented”日志。我们将在下一步中添加身份验证,届时会修复此问题。

您已部署初始 Web 应用!每次将新提交推送到 GitHub 代码库的 main 分支时,您都会在 Firebase 控制台中看到新的构建和发布开始,并且您的网站会在发布完成后自动更新。

6. 向 Web 应用添加身份验证功能

在本部分中,您将向 Web 应用添加身份验证,以便登录该应用。

添加授权网域

Firebase Authentication 只会接受来自您允许的网域的登录请求。在此处,我们将您的 App Hosting 后端的网域添加到项目的已批准网域列表中。

  1. 打开应用托管页面,然后点击已部署网站下方的查看以访问概览页面。复制 App Hosting 后端的域名。
  2. 前往“授权设置”标签页,然后选择要向其添加授权网域的项目。然后,找到已获授权的网域部分,并点击该部分。
  3. 点击添加网域按钮。
  4. 输入 App Hosting 后端的网域。
  5. 点击添加

实现登录和退出功能

src/lib/firebase/auth.js 文件中,将 onAuthStateChangedonIdTokenChangedsignInWithGooglesignOut 函数替换为以下代码:

export function onAuthStateChanged(cb) {   return _onAuthStateChanged(auth, cb); }  export function onIdTokenChanged(cb) {   return _onIdTokenChanged(auth, cb); }  export async function signInWithGoogle() {   const provider = new GoogleAuthProvider();    try {     await signInWithPopup(auth, provider);   } catch (error) {     console.error("Error signing in with Google", error);   } }  export async function signOut() {   try {     return auth.signOut();   } catch (error) {     console.error("Error signing out with Google", error);   } } 

此代码使用以下 Firebase API:

Firebase API

说明

auth.onAuthStateChanged

添加了一个用于观测用户登录状态变化的观测器。

auth.onIdTokenChanged

为用户 ID 令牌的更改添加观察器。

GoogleAuthProvider

创建 Google 身份验证提供方实例。

signInWithPopup

启动基于对话框的身份验证流程。

auth.signOut

退出用户账号。

src/components/Header.jsx 文件中,代码已经调用了 signInWithGooglesignOut 函数。

向服务器发送身份验证状态

为了将身份验证状态传递给服务器,我们将使用 Cookie。每当客户端中的身份验证状态发生变化时,我们都会更新 __session cookie。

src/components/Header.jsx 中,将 useUserSession 函数替换为以下代码:

function useUserSession(initialUser) {   useEffect(() => {     return onIdTokenChanged(async (user) => {       if (user) {         const idToken = await user.getIdToken();         await setCookie("__session", idToken);       } else {         await deleteCookie("__session");       }       if (initialUser?.uid === user?.uid) {         return;       }       window.location.reload();     });   }, [initialUser]);    return initialUser; } 

在服务器上读取身份验证状态

我们将使用 FirebaseServerApp 在服务器上镜像客户端的身份验证状态。

打开 src/lib/firebase/serverApp.js,然后替换 getAuthenticatedAppForUser 函数:

export async function getAuthenticatedAppForUser() {   const authIdToken = (await cookies()).get("__session")?.value;    // Firebase Server App is a new feature in the JS SDK that allows you to   // instantiate the SDK with credentials retrieved from the client & has   // other affordances for use in server environments.   const firebaseServerApp = initializeServerApp(     // https://github.com/firebase/firebase-js-sdk/issues/8863#issuecomment-2751401913     initializeApp(),     {       authIdToken,     }   );    const auth = getAuth(firebaseServerApp);   await auth.authStateReady();    return { firebaseServerApp, currentUser: auth.currentUser }; } 

验证更改

src/app/layout.js 文件中的根布局会渲染该标头,并传入用户(如有)作为属性。

<Header initialUser={currentUser?.toJSON()} /> 

这意味着,<Header> 组件会在服务器运行时渲染用户数据(如有)。初始网页加载后,如果在页面生命周期内有任何身份验证更新,onAuthStateChanged 处理程序会处理这些更新。

接下来,您可以推出新 build 并验证您构建的内容。

  1. 创建提交,并添加提交消息“添加身份验证”,然后将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Add authentication"  git push 
  2. 打开应用托管页面,并在新版本发布完成后,点击网站网址以打开该网站。
  3. 测试身份验证:
    1. 使用您的 Google 账号登录,并验证您的显示名称是否在登录后显示在标题中。
    2. 退出并重新登录。您可以针对不同用户重复此步骤。
    3. 可选:右键点击该 Web 应用,选择查看网页源代码,然后搜索显示名称。该名称会在从服务器返回的原始 HTML 源代码中显示。

7. 查看餐厅信息

该 Web 应用包含餐馆和评价的模拟数据

添加一家或多家餐馆

如需将模拟餐厅数据插入本地 Cloud Firestore 数据库,请按以下步骤操作:

  1. 如果您尚未登录该 Web 应用,请先登录。然后,依次选择 2cf67d488d8e6332.png> 添加示例餐馆。请注意,我们没有看到任何餐厅显示在 Friendly Eats Web 应用中,因为我们尚未设置数据提取代码。我们将在下一步中解决此问题。
  2. 在 Firebase 控制台的 Firestore 数据库页面上,选择餐厅。您会在餐馆集合中看到顶级文档,其中每个文档都代表一家餐馆。
  3. 点击几个文档即可浏览餐馆文档的属性。

显示餐馆列表

您的 Cloud Firestore 数据库现在包含 Next.js Web 应用可以显示的餐馆。

如需定义数据提取代码,请按以下步骤操作:

  1. src/app/page.js 文件中,找到 <Home /> 服务器组件,并查看对 getRestaurants 函数的调用,该函数用于在服务器运行时检索餐馆列表。您将在以下步骤中实现 getRestaurants 函数。
  2. src/lib/firebase/firestore.js 文件中,将 applyQueryFiltersgetRestaurants 函数替换为以下代码:
function applyQueryFilters(q, { category, city, price, sort }) {   if (category) {     q = query(q, where("category", "==", category));   }   if (city) {     q = query(q, where("city", "==", city));   }   if (price) {     q = query(q, where("price", "==", price.length));   }   if (sort === "Rating" || !sort) {     q = query(q, orderBy("avgRating", "desc"));   } else if (sort === "Review") {     q = query(q, orderBy("numRatings", "desc"));   }   return q; }  export async function getRestaurants(db = db, filters = {}) {   let q = query(collection(db, "restaurants"));    q = applyQueryFilters(q, filters);   const results = await getDocs(q);   return results.docs.map((doc) => {     return {       id: doc.id,       ...doc.data(),       // Only plain objects can be passed to Client Components from Server Components       timestamp: doc.data().timestamp.toDate(),     };   }); } 
  1. 创建一个提交,并附上提交消息“从 Firestore 读取餐厅列表”,然后将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Read the list of restaurants from Firestore"  git push 
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新发布完成。
  3. 在 Web 应用中,刷新页面。餐馆图片以图块的形式显示在页面上。

验证餐馆商家信息是否在服务器运行时加载

使用 Next.js 框架时,在服务器运行时或客户端运行时加载数据的时间可能不明显。

如需验证餐厅商家信息是否在服务器运行时加载,请按以下步骤操作:

  1. 在 Web 应用中,打开开发者工具并停用 JavaScript

在开发者工具中停用 JavaScipt

  1. 刷新 Web 应用。餐馆商家信息仍会加载。服务器响应中会返回餐馆信息。当 JavaScript 处于启用状态时,餐厅信息会通过客户端 JavaScript 代码进行水合处理。
  2. 在开发者工具中,重新启用 JavaScript

使用 Cloud Firestore 快照监听器监听餐馆更新

在上一部分中,您了解了如何从 src/app/page.js 文件加载初始餐馆集。src/app/page.js 文件是一个服务器组件,并在服务器上呈现,包括 Firebase 数据提取代码。

src/components/RestaurantListings.jsx 文件是一个客户端组件,可配置为水合服务器渲染的标记。

如需配置 src/components/RestaurantListings.jsx 文件以水合服务器渲染的标记,请按以下步骤操作:

  1. src/components/RestaurantListings.jsx 文件中,观察已经为您编写的以下代码:
useEffect(() => {     return getRestaurantsSnapshot((data) => {       setRestaurants(data);     }, filters);   }, [filters]); 

此代码会调用 getRestaurantsSnapshot() 函数,类似于您在上一步中实现的 getRestaurants() 函数。不过,此快照函数提供了一种回调机制,以便在每次餐馆的集合发生更改时都会调用该回调。

  1. src/lib/firebase/firestore.js 文件中,将 getRestaurantsSnapshot() 函数替换为以下代码:
export function getRestaurantsSnapshot(cb, filters = {}) {   if (typeof cb !== "function") {     console.log("Error: The callback parameter is not a function");     return;   }    let q = query(collection(db, "restaurants"));   q = applyQueryFilters(q, filters);    return onSnapshot(q, (querySnapshot) => {     const results = querySnapshot.docs.map((doc) => {       return {         id: doc.id,         ...doc.data(),         // Only plain objects can be passed to Client Components from Server Components         timestamp: doc.data().timestamp.toDate(),       };     });      cb(results);   }); } 
  1. src/lib/firebase/firestore.js 文件中,将 getRestaurantSnapshotById() 函数替换为以下代码:
export function getRestaurantSnapshotById(restaurantId, cb) {   if (!restaurantId) {     console.log("Error: Invalid ID received: ", restaurantId);     return;   }    if (typeof cb !== "function") {     console.log("Error: The callback parameter is not a function");     return;   }    const docRef = doc(db, "restaurants", restaurantId);   return onSnapshot(docRef, (docSnap) => {     cb({       ...docSnap.data(),       timestamp: docSnap.data().timestamp.toDate(),     });   }); } 

通过 Firestore 数据库页面所做的更改现在会实时反映在 Web 应用中。

  1. 创建一条提交消息为“Listen for realtime restaurant updates”的提交,并将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Listen for realtime restaurant updates"  git push 
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新发布完成。
  3. 在 Web 应用中,依次选择 27ca5d1e8ed8adfe.png> 添加示例餐馆。如果正确实现快照功能,餐馆会实时显示,而无需刷新网页。

8. 保存用户从 Web 应用提交的评价

  1. src/lib/firebase/firestore.js 文件中,将 updateWithRating() 函数替换为以下代码:
const updateWithRating = async (   transaction,   docRef,   newRatingDocument,   review ) => {   const restaurant = await transaction.get(docRef);   const data = restaurant.data();   const newNumRatings = data?.numRatings ? data.numRatings + 1 : 1;   const newSumRating = (data?.sumRating || 0) + Number(review.rating);   const newAverage = newSumRating / newNumRatings;    transaction.update(docRef, {     numRatings: newNumRatings,     sumRating: newSumRating,     avgRating: newAverage,   });    transaction.set(newRatingDocument, {     ...review,     timestamp: Timestamp.fromDate(new Date()),   }); }; 

此代码会插入一个表示新评价的新 Firestore 文档。该代码还会更新代表餐馆的现有 Firestore 文档,使其包含评分数量和平均计算评分的更新后数据。

  1. 将整个 addReviewToRestaurant() 函数替换为以下代码:
export async function addReviewToRestaurant(db, restaurantId, review) {   if (!restaurantId) {     throw new Error("No restaurant ID has been provided.");   }    if (!review) {     throw new Error("A valid review has not been provided.");   }    try {     const docRef = doc(collection(db, "restaurants"), restaurantId);     const newRatingDocument = doc(       collection(db, `restaurants/${restaurantId}/ratings`),     );      // corrected line     await runTransaction(db, (transaction) =>       updateWithRating(transaction, docRef, newRatingDocument, review),     );   } catch (error) {     console.error(       "There was an error adding the rating to the restaurant",       error,     );     throw error;   } } 

实现 Next.js 服务器操作

Next.js 服务器操作提供了方便的 API 来访问表单数据(例如 data.get("text")),以从表单提交载荷中获取文本值。

如需使用 Next.js 服务器操作处理评价表单提交,请按以下步骤操作:

  1. src/components/ReviewDialog.jsx 文件中,找到 <form> 元素中的 action 属性。
<form   action={handleReviewFormSubmission}   onSubmit={() => {     handleClose();   }} > 

action 属性值是指您将在下一步中实现的函数。

  1. src/app/actions.js 文件中,将 handleReviewFormSubmission() 函数替换为以下代码:
export async function handleReviewFormSubmission(data) {   const { firebaseServerApp } = await getAuthenticatedAppForUser();   const db = getFirestore(firebaseServerApp);    await addReviewToRestaurant(db, data.get("restaurantId"), {     text: data.get("text"),     rating: data.get("rating"),      // This came from a hidden form field.     userId: data.get("userId"),   }); } 

添加对餐厅的评价

您实现了对评价提交功能的支持,因此现在可以验证您的评价是否已正确插入到 Cloud Firestore 中。

如需添加评价并验证其是否已插入 Cloud Firestore,请按以下步骤操作:

  1. 创建一条提交消息为“Allow users to submit restaurant reviews”(允许用户提交餐厅评价)的提交,并将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Allow users to submit restaurant reviews"  git push 
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新发布完成。
  3. 刷新 Web 应用,然后从首页选择一家餐馆。
  4. 在餐厅页面上,点击 3e19beef78bb0d0e.png
  5. 请选择星级评分。
  6. 撰写评价。
  7. 点击提交。您的评价会显示在评价列表的顶部。
  8. 在 Cloud Firestore 中,在添加文档面板中搜索您评价过的餐馆的文档并将其选中。
  9. 启动集合面板中,选择评分
  10. 添加文档面板中,找到您的评价的文档,验证其是否已按预期插入。

9. 保存用户从 Web 应用上传的文件

在本部分中,您将添加一项功能,以便在登录时替换与餐馆关联的图片。您将图片上传到 Firebase Storage,并更新 Cloud Firestore 文档中代表该餐馆的图片网址。

如需保存用户通过该 Web 应用上传的文件,请按以下步骤操作:

  1. src/components/Restaurant.jsx 文件中,观察用户上传文件时运行的代码:
async function handleRestaurantImage(target) {   const image = target.files ? target.files[0] : null;   if (!image) {     return;   }    const imageURL = await updateRestaurantImage(id, image);   setRestaurantDetails({ ...restaurantDetails, photo: imageURL }); } 

无需对此函数进行任何更改,但您将在以下步骤中实现 updateRestaurantImage() 函数的行为。

  1. src/lib/firebase/storage.js 文件中,将 updateRestaurantImage()uploadImage() 函数替换为以下代码:
export async function updateRestaurantImage(restaurantId, image) {   try {     if (!restaurantId) {       throw new Error("No restaurant ID has been provided.");     }      if (!image || !image.name) {       throw new Error("A valid image has not been provided.");     }      const publicImageUrl = await uploadImage(restaurantId, image);     await updateRestaurantImageReference(restaurantId, publicImageUrl);      return publicImageUrl;   } catch (error) {     console.error("Error processing request:", error);   } }  async function uploadImage(restaurantId, image) {   const filePath = `images/${restaurantId}/${image.name}`;   const newImageRef = ref(storage, filePath);   await uploadBytesResumable(newImageRef, image);    return await getDownloadURL(newImageRef); } 

我们已为您实现 updateRestaurantImageReference() 函数。此函数使用更新后的图片网址更新 Cloud Firestore 中现有的餐馆文档。

验证图片上传功能

如需验证图片是否按预期上传,请按以下步骤操作:

  1. 创建一条提交消息为“允许用户更改每家餐厅的照片”的提交,并将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Allow users to change each restaurants' photo"  git push 
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新发布完成。
  3. 在 Web 应用中,验证您已登录,并选择一家餐馆。
  4. 点击 7067eb41fea41ff0.png,然后从文件系统中上传图片。您的映像将离开本地环境并上传到 Cloud Storage。图片会在上传后立即显示。
  5. 前往 Cloud Storage for Firebase。
  6. 前往代表该餐馆的文件夹。您上传的图片已在文件夹中。

6cf3f9e2303c931c.png

10. 利用生成式 AI 总结餐厅评价

在本部分中,您将添加评价摘要功能,以便用户无需阅读每条评价,即可快速了解大家对某家餐厅的看法。

在 Cloud Secret Manager 中存储 Gemini API 密钥

  1. 如需使用 Gemini API,您需要一个 API 密钥。访问 Google AI Studio,然后点击 Create API Key
  2. 随意为该密钥命名。如果您的项目未列在选择导入的项目下,请点击导入项目,在列表中选中您的项目,然后点击导入。最后,在选择导入的项目下选择该项目,然后点击创建密钥
  3. App Hosting 与 Cloud Secret Manager 集成,可让您安全地存储 API 密钥等敏感值:
    1. 在终端中,运行命令以创建新的 Secret:
    firebase apphosting:secrets:set GEMINI_API_KEY 
    1. 当系统提示您输入 Secret 值时,请从 Google AI Studio 复制并粘贴您的 Gemini API 密钥。
    2. 当系统询问新密钥是用于正式版还是本地测试时,请选择“正式版”。
    3. 当系统询问您是否要授予访问权限,以便后端服务账号可以访问 Secret 时,请选择“是”。
    4. 当系统询问是否应将新密钥添加到 apphosting.yaml 时,输入 Y 以接受。

您的 Gemini API 密钥现已安全地存储在 Cloud Secret Manager 中,并且可供 App Hosting 后端访问。

实现评价摘要组件

  1. src/components/Reviews/ReviewSummary.jsx 中,将 GeminiSummary 函数替换为以下代码:
    export async function GeminiSummary({ restaurantId }) {   const { firebaseServerApp } = await getAuthenticatedAppForUser();   const reviews = await getReviewsByRestaurantId(     getFirestore(firebaseServerApp),     restaurantId   );    const reviewSeparator = "@";   const prompt = `     Based on the following restaurant reviews,      where each review is separated by a '${reviewSeparator}' character,      create a one-sentence summary of what people think of the restaurant.       Here are the reviews: ${reviews.map((review) => review.text).join(reviewSeparator)}   `;    try {     if (!process.env.GEMINI_API_KEY) {       // Make sure GEMINI_API_KEY environment variable is set:       // https://firebase.google.com/docs/genkit/get-started       throw new Error(         'GEMINI_API_KEY not set. Set it with "firebase apphosting:secrets:set GEMINI_API_KEY"'       );     }      // Configure a Genkit instance.     const ai = genkit({       plugins: [googleAI()],       model: gemini20Flash, // set default model     });     const { text } = await ai.generate(prompt);      return (       <div className="restaurant__review_summary">         <p>{text}</p>         <p> Summarized with Gemini</p>       </div>     );   } catch (e) {     console.error(e);     return <p>Error summarizing reviews.</p>;   } } 
  2. 创建一条提交消息为“使用 AI 总结评价”的提交,并将其推送到您的 GitHub 代码库。
    git add .  git commit -m "Use AI to summarize reviews"  git push 
  3. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新发布完成。
  4. 打开餐厅页面。在顶部,您应该会看到一句话的摘要,其中包含页面上的所有评价。
  5. 添加新评价并刷新页面。您应该会看到摘要发生变化。

11. 取消发布 App Hosting 网站

完成此 Codelab 后,如果您不打算继续使用该应用,可以将其取消发布,以确保无人访问您的 Firestore、Storage 和 Gemini 资源。您可以随时重新发布。

如需取消发布 App Hosting 网站,请执行以下操作:

  1. 在 Firebase 控制台中打开 App Hosting
  2. 找到应用的后端,然后点击查看
  3. 后端信息部分,点击网域旁边的管理。系统会加载网域页面。
  4. 在域名旁边,点击更多图标(三个垂直点),选择停用域名,然后点击停用进行确认。

12. 总结

恭喜!您了解了如何使用 Firebase 为 Next.js 应用添加特性和功能。具体来说,您使用了以下各项:

了解详情