将 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 相关知识

学习内容

  • 如何将 Firebase 与 Next.js 应用路由器和服务器端渲染搭配使用。
  • 如何在 Cloud Storage for Firebase 中保留图片。
  • 如何在 Cloud Firestore 数据库中读取和写入数据。
  • 如何将“使用 Google 账号登录”用于 Firebase JavaScript SDK。

所需条件

  • 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 commit -a -m "codelab starting point"
    
    git branch -M main
    
  4. 创建新的 GitHub 代码库:https://github.com/new。您可以随意为其命名。
    1. GitHub 会为您提供一个新的代码库网址,网址类似于 https://github.com//.gitgit@github.com:/.git。复制此网址。
  5. 将本地更改推送到新的 GitHub 代码库。运行以下命令,使用您的代码库网址替换 占位符。
    git remote add origin <your-repository-url>
    
    git push -u origin main
    
  6. 您现在应该会在 GitHub 代码库中看到起始代码。

安装或更新 Firebase CLI

运行以下命令,验证是否已安装 Firebase CLI 及其版本是否为 13.9.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. Firebase 控制台中,点击添加项目
  2. 输入您的项目名称文本框中,输入 FriendlyEats Codelab(或您所选的项目名称),然后点击继续
  3. 确认 Firebase 结算方案模态窗口中,确认方案为 Blaze,然后点击确认方案
  4. 对于此 Codelab,您不需要使用 Google Analytics,因此请关闭为此项目启用 Google Analytics 选项。
  5. 点击 Create project
  6. 等待您的项目完成预配,然后点击继续
  7. 在 Firebase 项目中,前往项目设置。请记下您的项目 ID,因为稍后需要用到。此唯一标识符用于识别项目(例如在 Firebase CLI 中)。

升级您的 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 项目中,前往项目概览,然后点击 e41f2efdd9539c31.png Web

    如果您已在项目中注册了应用,请点击添加应用以显示 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. 数据库 ID 设置为 (default)
  4. 为数据库选择一个位置,然后点击下一步
    对于真实应用,您需要选择靠近用户的位置。
  5. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在此 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。
  6. 点击创建

设置 Cloud Storage for Firebase

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

4. 查看起始代码库

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

文件夹和文件结构

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

文件夹和文件

说明

src/components

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

src/lib

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

src/lib/firebase

Firebase 专属代码和 Firebase 配置

public

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

src/app

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

src/app/restaurant

API 路由处理程序

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 分支时,该后端都会自动重新构建并发布应用的新版本。

部署安全规则

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

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

将 Firebase 配置添加到 Web 应用代码中

  1. 在 Firebase 控制台中,前往项目设置
  2. SDK 设置和配置窗格中,点击“添加应用”,然后点击代码括号图标 以注册新的 Web 应用。
  3. 在 Web 应用创建流程结束时,复制 firebaseConfig 变量,然后复制其属性和值。
  4. 在代码编辑器中打开 apphosting.yaml 文件,然后使用 Firebase 控制台中的配置值填充环境变量值。
  5. 在文件中,将现有属性替换为您复制的属性。
  6. 保存文件。

创建一个后端

  1. 前往 Firebase 控制台中的“App Hosting”页面

App Hosting 控制台的零状态,带有“开始使用”按钮

  1. 点击“开始”以开始后端创建流程。按如下方式配置您的后端:
  2. 按照第一步中的提示连接您之前创建的 GitHub 代码库。
  3. 设置部署设置:
    1. 将根目录保留为 /
    2. 将正式版分支设置为 main
    3. 启用自动发布
  4. 将后端命名为 friendlyeats-codelab
  5. 在“创建或关联 Firebase Web 应用”中,从“选择现有的 Firebase Web 应用”下拉菜单中选择您之前配置的 Web 应用。
  6. 点击“完成并部署”。片刻后,您会进入一个新页面,在其中查看新的 App Hosting 后端的状态!
  7. 部署完成后,在“domains”(域名)下点击您的免费域名。由于 DNS 传播的影响,这可能需要几分钟才能开始工作。

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

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

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

实现登录和退出功能

  1. src/lib/firebase/auth.js 文件中,将 onAuthStateChangedsignInWithGooglesignOut 函数替换为以下代码:
export function onAuthStateChanged(cb) {
	return _onAuthStateChanged(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

说明

GoogleAuthProvider

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

signInWithPopup

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

auth.signOut

退出用户账号。

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

  1. 创建一条提交消息为“Adding Google Authentication”的提交,并将其推送到您的 GitHub 代码库。1. 在 Firebase 控制台中打开“App Hosting”页面,然后等待新的发布完成。
  2. 在 Web 应用中,刷新页面,然后点击使用 Google 账号登录。该 Web 应用无法更新,因此不确定登录是否成功。

将身份验证状态发送到服务器

为了将身份验证状态传递给服务器,我们将使用服务工件。将 fetchWithFirebaseHeadersgetAuthIdToken 函数替换为以下代码:

async function fetchWithFirebaseHeaders(request) {
  const app = initializeApp(firebaseConfig);
  const auth = getAuth(app);
  const installations = getInstallations(app);
  const headers = new Headers(request.headers);
  const [authIdToken, installationToken] = await Promise.all([
    getAuthIdToken(auth),
    getToken(installations),
  ]);
  headers.append("Firebase-Instance-ID-Token", installationToken);
  if (authIdToken) headers.append("Authorization", `Bearer ${authIdToken}`);
  const newRequest = new Request(request, { headers });
  return await fetch(newRequest);
}

async function getAuthIdToken(auth) {
  await auth.authStateReady();
  if (!auth.currentUser) return;
  return await getIdToken(auth.currentUser);
}

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

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

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

export async function getAuthenticatedAppForUser() {
  const idToken = headers().get("Authorization")?.split("Bearer ")[1];
  console.log('firebaseConfig', JSON.stringify(firebaseConfig));
  const firebaseServerApp = initializeServerApp(
    firebaseConfig,
    idToken
      ? {
          authIdToken: idToken,
        }
      : {}
  );

  const auth = getAuth(firebaseServerApp);
  await auth.authStateReady();

  return { firebaseServerApp, currentUser: auth.currentUser };
}

订阅身份验证更改

如需订阅身份验证更改,请按以下步骤操作:

  1. 导航到 src/components/Header.jsx 文件。
  2. 将整个 useUserSession 函数替换为以下代码:
function useUserSession(initialUser) {
	// The initialUser comes from the server via a server component
	const [user, setUser] = useState(initialUser);
	const router = useRouter();

	// Register the service worker that sends auth state back to server
	// The service worker is built with npm run build-service-worker
	useEffect(() => {
		if ("serviceWorker" in navigator) {
			const serializedFirebaseConfig = encodeURIComponent(JSON.stringify(firebaseConfig));
			const serviceWorkerUrl = `/auth-service-worker.js?firebaseConfig=${serializedFirebaseConfig}`
		
		  navigator.serviceWorker
			.register(serviceWorkerUrl)
			.then((registration) => console.log("scope is: ", registration.scope));
		}
	  }, []);

	useEffect(() => {
		const unsubscribe = onAuthStateChanged((authUser) => {
			setUser(authUser)
		})

		return () => unsubscribe()
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, []);

	useEffect(() => {
		onAuthStateChanged((authUser) => {
			if (user === undefined) return

			// refresh when user changed to ease testing
			if (user?.email !== authUser?.email) {
				router.refresh()
			}
		})
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [user])

	return user;
}

onAuthStateChanged 函数指定身份验证状态发生变化时,此代码使用 React 状态钩子来更新用户。

验证更改

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

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

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

现在,您可以发布新 build 并验证您构建的内容。

  1. 创建一个包含提交消息“Show login state”(显示登录状态)的提交内容,并将其推送到您的 GitHub 代码库。
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
  3. 验证新的身份验证行为:
    1. 在浏览器中,刷新 Web 应用。您的显示名称会显示在标头中。
    2. 退出并重新登录。该网页会实时更新,且无需刷新网页。您可以针对不同用户重复此步骤。
    3. 可选:右键点击该 Web 应用,选择查看网页源代码,然后搜索显示名称。该名称会在从服务器返回的原始 HTML 源代码中显示。

7. 查看餐厅信息

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

添加一家或多家餐馆

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

  1. 在 Web 应用中,选择 2cf67d488d8e6332.png > 添加示例餐馆
  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. 创建一个包含提交消息“Read the list of 还可以在 Firestore 中读取餐馆列表”的提交内容,并将其推送到您的 GitHub 代码库。
  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(() => {
        const unsubscribe = getRestaurantsSnapshot(data => {
                setRestaurants(data);
        }, filters);

        return () => {
                unsubscribe();
        };
}, [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);

	const unsubscribe = 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);
	});

	return unsubscribe;
}

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

  1. 创建一个提交,并将提交消息设为“监听实时餐厅动态”,然后将其推送到您的 GitHub 代码库。
  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}>

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

  1. src/app/actions.js 文件中,将 handleReviewFormSubmission() 函数替换为以下代码:
// This is a next.js server action, which is an alpha feature, so
// use with caution.
// https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions
export async function handleReviewFormSubmission(data) {
        const { app } = await getAuthenticatedAppForUser();
        const db = getFirestore(app);

        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. 创建一个包含提交消息“允许用户提交餐馆评价”的提交内容,并将其推送到您的 GitHub 代码库。
  2. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
  3. 刷新 Web 应用,然后从首页选择一家餐馆。
  4. 在餐馆页面上,点击 3e19beef78bb0d0e.png
  5. 请选择星级评分。
  6. 撰写评价。
  7. 点击提交。您的评价会显示在评价列表的顶部。
  8. 在 Cloud Firestore 中,在添加文档窗格中搜索您评价过的餐馆的文档并将其选中。
  9. 启动集合窗格中,选择评分
  10. 添加文档窗格中,找到您的评价的文档,验证其是否已按预期插入。

Firestore 模拟器中的文档

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);
        setRestaurant({ ...restaurant, 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 代码库。
  2. 在 Firebase 控制台中打开“App Hosting”页面,然后等待新的发布完成。
  3. 在 Web 应用中,验证您已登录,并选择一家餐馆。
  4. 点击 7067eb41fea41ff0.png,然后从文件系统中上传图片。您的映像将离开本地环境并上传到 Cloud Storage。图片会在上传后立即显示。
  5. 前往 Cloud Storage for Firebase。
  6. 前往代表该餐馆的文件夹。您上传的图片已在文件夹中。

6cf3f9e2303c931c.png

10. 利用生成式 AI 生成餐厅评价摘要

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

将 Gemini API 密钥存储在 Cloud Secret Manager 中

  1. 如需使用 Gemini API,您需要 API 密钥。在 Google AI Studio 中创建密钥
  2. App Hosting 与 Cloud Secret Manager 集成,让您可以安全地存储 API 密钥等敏感值:
    1. 在终端中,运行以下命令以创建新的 Secret:
    firebase apphosting:secrets:set gemini-api-key
    
    1. 当系统提示您输入 Secret 值时,请从 Google AI Studio 复制并粘贴 Gemini API 密钥。
    2. 当系统询问是否应将新密钥添加到 apphosting.yaml 时,请输入 Y 以接受。

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

实现评价摘要组件

  1. src/components/Reviews/ReviewSummary.jsx 中,将 GeminiSummary 函数替换为以下代码:
    export async function GeminiSummary({ restaurantId }) {
        const { firebaseServerApp } = await getAuthenticatedAppForUser();
        const reviews = await getReviewsByRestaurantId(
            getFirestore(firebaseServerApp),
            restaurantId
        );
    
        const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
        const model = genAI.getGenerativeModel({ model: "gemini-pro"});
    
        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 {
            const result = await model.generateContent(prompt);
            const response = await result.response;
            const text = response.text();
    
            return (
                <div className="restaurant__review_summary">
                    <p>{text}</p>
                    <p> Summarized with Gemini</p>
                </div>
            );
        } catch (e) {
            console.error(e);
            return <p>Error contacting Gemini</p>;
        }
    }
    
  2. 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“使用 AI 总结评价”。
  3. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
  4. 打开某家餐厅的页面。您应在页面顶部看到一句话的摘要,其中包含该页面上的所有评价。
  5. 添加新的评价并刷新页面。您应该会看到摘要发生变化。

11. 总结

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

了解详情