1. 准备工作
在此 Codelab 中,您将了解如何将 Firebase 与名为 Friends Eats 的 Next.js 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
文件夹复制到您自己的代码库中:
- 使用终端在计算机上创建一个新文件夹,然后切换到该新目录:
mkdir codelab-friendlyeats-web cd codelab-friendlyeats-web
- 使用 giget npm 软件包仅提取
nextjs-start
文件夹:npx giget@latest gh:firebase/friendlyeats-web/nextjs-start#master . --install
- 使用 git 在本地跟踪更改:
git init git commit -a -m "codelab starting point" git branch -M main
- 创建新的 GitHub 代码库:https://github.com/new。您可以随意为其命名。
- GitHub 会为您提供一个新的代码库网址,网址类似于
https://github.com/
或/ .git git@github.com:
。复制此网址。/ .git
- GitHub 会为您提供一个新的代码库网址,网址类似于
- 将本地更改推送到新的 GitHub 代码库。运行以下命令,将您的代码库网址替换为
占位符。git remote add origin <your-repository-url> git push -u origin main
- 现在,您应该会在 GitHub 代码库中看到起始代码。
安装或更新 Firebase CLI
运行以下命令,验证是否已安装 Firebase CLI 且版本为 13.9.0 或更高版本:
firebase --version
如果您看到的版本较低或未安装 Firebase CLI,请运行安装命令:
npm install -g firebase-tools@latest
如果由于权限错误而无法安装 Firebase CLI,请参阅 npm 文档或使用其他安装选项。
登录 Firebase
- 运行以下命令以登录 Firebase CLI:
firebase login
- 根据您是否希望 Firebase 收集数据,请输入
Y
或N
。 - 在浏览器中,选择您的 Google 账号,然后点击允许。
3. 设置您的 Firebase 项目
在本部分中,您将设置一个 Firebase 项目,并向其关联一个 Firebase Web 应用。您还将设置示例 Web 应用使用的 Firebase 服务。
创建 Firebase 项目
- 在 Firebase 控制台中,点击添加项目。
- 在输入您的项目名称文本框中,输入
FriendlyEats Codelab
(或您所选的项目名称),然后点击继续。 - 在确认 Firebase 结算方案模态窗口中,确认方案为 Blaze,然后点击确认方案
- 对于此 Codelab,您不需要使用 Google Analytics,因此请关闭为此项目启用 Google Analytics 选项。
- 点击 Create project。
- 等待您的项目完成预配,然后点击继续。
- 在 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 方案,请按以下步骤操作:
- 在 Firebase 控制台中,选择升级您的方案。
- 选择 Blaze 方案。按照屏幕上的说明将 Cloud Billing 账号与您的项目相关联。
如果您需要在此升级过程中创建 Cloud Billing 账号,则可能需要返回 Firebase 控制台中的升级流程以完成升级。
向 Firebase 项目添加 Web 应用
- 前往 Firebase 项目中的项目概览,然后点击 Web。
如果您的项目中已有注册的应用,请点击添加应用以查看“Web”图标。 - 在应用别名文本框中,输入一个容易记住的应用别名,例如
My Next.js app
。 - 请勿选中还为此应用设置 Firebase Hosting 复选框。
- 点击注册应用 > 下一步 > 下一步 > 继续前往控制台。
在 Firebase 控制台中设置 Firebase 服务
设置身份验证
- 在 Firebase 控制台中,前往身份验证。
- 点击开始使用。
- 在其他提供商列中,点击 Google > 启用。
- 在项目的公开名称文本框中,输入一个容易记住的名称,例如
My Next.js app
。 - 从项目的支持电子邮件地址下拉列表中,选择您的电子邮件地址。
- 点击保存。
设置 Cloud Firestore
- 在 Firebase 控制台的左侧面板中,展开构建,然后选择 Firestore 数据库。
- 点击创建数据库。
- 将数据库 ID 设置为
(default)
。 - 为数据库选择一个位置,然后点击下一步。
对于真实应用,您需要选择靠近用户的位置。 - 点击以测试模式启动。阅读有关安全规则的免责声明。
在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在没有为数据库添加安全规则的情况下,请不要公开分发或公开应用。 - 点击创建。
设置 Cloud Storage for Firebase
- 在 Firebase 控制台的左侧面板中,展开构建,然后选择存储。
- 点击开始使用。
- 为默认的 Storage 存储分区选择位置。
US-WEST1
、US-CENTRAL1
和US-EAST1
中的存储分区可以使用 Google Cloud Storage 的“始终免费”层级。所有其他位置的存储分区均遵循 Google Cloud Storage 价格和使用量。 - 点击以测试模式启动。阅读有关安全规则的免责声明。
在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。 - 点击创建。
4. 查看起始代码库
在本部分中,您将复习应用起始代码库中的几个部分,以便在此 Codelab 中向这些部分添加功能。
文件夹和文件结构
下表简要介绍了应用的文件夹和文件结构:
文件夹和文件 | 说明 |
| 适用于过滤条件、标题、餐馆详细信息和评价的 React 组件 |
| 不一定绑定到 React 或 Next.js 的实用函数 |
| Firebase 专属代码和 Firebase 配置 |
| Web 应用中的静态资源,例如图标 |
| 使用 Next.js 应用路由器进行路由 |
| API 路由处理程序 |
| 使用 npm 的项目依赖项 |
| Next.js 专属配置(已启用服务器操作) |
| 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 设置了一组安全规则。部署安全规则后,数据库和存储桶中的数据可以得到更好的保护,避免遭到滥用。
- 在终端中,配置 CLI 以使用您之前创建的 Firebase 项目:
firebase use --add
当系统提示您输入别名时,请输入friendlyeats-codelab
。 - 如需部署这些安全规则,请在终端中运行以下命令:
firebase deploy --only firestore:rules,storage
- 如果系统询问您:
"Cloud Storage for Firebase needs an IAM Role to use cross-service rules. Grant the new role?"
,请按Enter
选择是。
将 Firebase 配置添加到 Web 应用代码中
- 在 Firebase 控制台中,前往项目设置。
- 在 SDK 设置和配置窗格中,点击“添加应用”,然后点击代码括号图标
以注册新的 Web 应用。
- 在 Web 应用创建流程结束时,复制
firebaseConfig
变量,然后复制其属性和值。 - 在代码编辑器中打开
apphosting.yaml
文件,然后使用 Firebase 控制台中的配置值填充环境变量值。 - 在文件中,将现有属性替换为您复制的属性。
- 保存文件。
创建一个后端
- 前往 Firebase 控制台中的“App Hosting”页面:
- 点击“开始”以开始后端创建流程。按如下方式配置后端:
- 按照第一步中的提示连接您之前创建的 GitHub 代码库。
- 设置部署设置:
- 将根目录保留为
/
- 将正式版分支设置为
main
- 启用自动发布
- 将根目录保留为
- 将后端命名为
friendlyeats-codelab
。 - 在“创建或关联 Firebase Web 应用”中,从“选择现有的 Firebase Web 应用”下拉菜单中选择您之前配置的 Web 应用。
- 点击“Finish and deploy”(完成并部署)。片刻后,您会进入一个新页面,在其中查看新的 App Hosting 后端的状态!
- 发布完成后,点击“网域”下的免费网域。由于 DNS 传播,此操作可能需要几分钟才能开始生效。
您已部署初始 Web 应用!每当您将新提交推送到 GitHub 代码库的 main
分支时,Firebase 控制台中都会显示开始进行新的构建和发布操作,并且您的网站会在发布操作完成后自动更新。
6. 向 Web 应用添加身份验证功能
在本部分中,您将向 Web 应用添加身份验证,以便登录该应用。
实现登录和退出功能
- 在
src/lib/firebase/auth.js
文件中,将onAuthStateChanged
、signInWithGoogle
和signOut
函数替换为以下代码:
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 | 说明 |
创建 Google 身份验证提供方实例。 | |
启动基于对话框的身份验证流程。 | |
退出用户账号。 |
在 src/components/Header.jsx
文件中,代码已经调用了 signInWithGoogle
和 signOut
函数。
- 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“添加 Google 身份验证”。1. 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 在 Web 应用中,刷新页面,然后点击使用 Google 账号登录。该 Web 应用无法更新,因此不确定登录是否成功。
将身份验证状态发送到服务器
为了将身份验证状态传递给服务器,我们将使用服务工件。将 fetchWithFirebaseHeaders
和 getAuthIdToken
函数替换为以下代码:
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 };
}
订阅身份验证更改
如需订阅身份验证更改,请按以下步骤操作:
- 导航到
src/components/Header.jsx
文件。 - 将整个
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 state 钩子来更新用户。
验证更改
src/app/layout.js
文件中的根布局会渲染该标头,并传入用户(如有)作为属性。
<Header initialUser={currentUser?.toJSON()} />
这意味着,<Header>
组件会在服务器运行时渲染用户数据(如有)。初始网页加载后,如果在页面生命周期内有任何身份验证更新,onAuthStateChanged
处理程序会处理这些更新。
现在,您可以发布新 build 并验证您构建的内容了。
- 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“Show signin state”。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 验证新的身份验证行为:
- 在浏览器中,刷新 Web 应用。您的显示名称会显示在标头中。
- 退出并重新登录。该网页会实时更新,且无需刷新网页。您可以针对不同用户重复此步骤。
- 可选:右键点击该 Web 应用,选择查看网页源代码,然后搜索显示名称。该名称会在从服务器返回的原始 HTML 源代码中显示。
7. 查看餐厅信息
该 Web 应用包含餐馆和评价的模拟数据。
添加一家或多家餐馆
如需将模拟餐厅数据插入本地 Cloud Firestore 数据库,请按以下步骤操作:
- 在 Web 应用中,选择 > 添加示例餐馆。
- 在 Firebase 控制台的 Firestore 数据库页面上,选择餐厅。您会在餐馆集合中看到顶级文档,其中每个文档都代表一家餐馆。
- 点击几个文档即可浏览餐馆文档的属性。
显示餐馆列表
您的 Cloud Firestore 数据库现在包含 Next.js Web 应用可以显示的餐馆。
如需定义数据提取代码,请按以下步骤操作:
- 在
src/app/page.js
文件中,找到<Home />
服务器组件,并查看对getRestaurants
函数的调用,该函数用于在服务器运行时检索餐馆列表。您将在以下步骤中实现getRestaurants
函数。 - 在
src/lib/firebase/firestore.js
文件中,将applyQueryFilters
和getRestaurants
函数替换为以下代码:
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(),
};
});
}
- 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“从 Firestore 读取餐厅列表”。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 在 Web 应用中,刷新页面。餐厅图片以图块的形式显示在页面上。
验证餐馆商家信息是否在服务器运行时加载
使用 Next.js 框架时,在服务器运行时或客户端运行时加载数据的时间可能不明显。
如需验证餐厅商家信息是否在服务器运行时加载,请按以下步骤操作:
- 在 Web 应用中,打开开发者工具并停用 JavaScript。
- 刷新 Web 应用。餐馆商家信息仍会加载。服务器响应中会返回餐馆信息。当 JavaScript 处于启用状态时,餐厅信息会通过客户端 JavaScript 代码进行水合处理。
- 在开发者工具中,重新启用 JavaScript。
使用 Cloud Firestore 快照监听器监听餐馆更新
在上一部分中,您了解了如何从 src/app/page.js
文件加载初始餐馆集。src/app/page.js
文件是一个服务器组件,并在服务器上呈现,包括 Firebase 数据提取代码。
src/components/RestaurantListings.jsx
文件是一个客户端组件,可配置为水合服务器渲染的标记。
如需配置 src/components/RestaurantListings.jsx
文件以水合服务器渲染的标记,请按以下步骤操作:
- 在
src/components/RestaurantListings.jsx
文件中,观察已经为您编写的以下代码:
useEffect(() => {
const unsubscribe = getRestaurantsSnapshot(data => {
setRestaurants(data);
}, filters);
return () => {
unsubscribe();
};
}, [filters]);
此代码会调用 getRestaurantsSnapshot()
函数,类似于您在上一步中实现的 getRestaurants()
函数。不过,此快照函数提供了一种回调机制,以便在每次餐馆的集合发生更改时都会调用该回调。
- 在
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 应用中。
- 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“监听实时餐厅动态”。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 在 Web 应用中,选择 > 添加示例餐馆。如果正确实现快照功能,餐馆会实时显示,而无需刷新网页。
8. 保存 Web 应用中的用户提交的评价
- 在
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 文档,使其包含评分数量和平均计算评分的更新后数据。
- 将整个
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 服务器操作处理评价表单提交,请按以下步骤操作:
- 在
src/components/ReviewDialog.jsx
文件中,找到<form>
元素中的action
属性。
<form action={handleReviewFormSubmission}>
action
属性值是指您将在下一步中实现的函数。
- 在
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,请按以下步骤操作:
- 创建一条提交消息为“允许用户提交餐厅评价”的提交,并将其推送到您的 GitHub 代码库。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 刷新 Web 应用,然后从首页选择一家餐馆。
- 在餐馆页面上,点击 。
- 请选择星级评分。
- 撰写评价。
- 点击提交。您的评价会显示在评价列表的顶部。
- 在 Cloud Firestore 中,在添加文档窗格中搜索您评价过的餐馆的文档并将其选中。
- 在启动集合窗格中,选择评分。
- 在添加文档窗格中,找到您的评价的文档,验证其是否已按预期插入。
9. 保存用户从 Web 应用上传的文件
在本部分中,您将添加一项功能,以便在登录时替换与餐馆关联的图片。您将图片上传到 Firebase Storage,并更新 Cloud Firestore 文档中代表该餐馆的图片网址。
如需保存用户通过该 Web 应用上传的文件,请按以下步骤操作:
- 在
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()
函数的行为。
- 在
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 中现有的餐馆文档。
验证图片上传功能
如需验证图片是否按预期上传,请按以下步骤操作:
- 创建一个提交,并设置提交消息为“允许用户更改每家餐厅的照片”,然后将其推送到您的 GitHub 代码库。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 在 Web 应用中,验证您已登录,并选择一家餐馆。
- 点击 ,然后从文件系统中上传图片。您的映像将离开本地环境并上传到 Cloud Storage。图片会在上传后立即显示。
- 前往 Cloud Storage for Firebase。
- 前往代表该餐馆的文件夹。您上传的图片已在文件夹中。
10. 利用生成式 AI 生成餐厅评价摘要
在本部分中,您将添加评价摘要功能,以便用户无需阅读每条评价,即可快速了解其他人对某家餐厅的看法。
在 Cloud Secret Manager 中存储 Gemini API 密钥
- 如需使用 Gemini API,您需要一个 API 密钥。在 Google AI Studio 中创建密钥。
- App Hosting 与 Cloud Secret Manager 集成,可让您安全地存储 API 密钥等敏感值:
- 在终端中,运行以下命令以创建新的 Secret:
firebase apphosting:secrets:set gemini-api-key
- 当系统提示您输入密文值时,请从 Google AI Studio 复制并粘贴您的 Gemini API 密钥。
- 当系统询问是否应将新密钥添加到
apphosting.yaml
时,输入Y
以接受。
您的 Gemini API 密钥现已安全地存储在 Cloud Secret Manager 中,并且可供您的应用托管后端访问。
实现评价摘要组件
- 在
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>; } }
- 创建一个提交,并将其提交到 GitHub 代码库,提交消息为“使用 AI 总结评价”。
- 在 Firebase 控制台中打开 App Hosting 页面,然后等待新的发布完成。
- 打开某家餐厅的页面。您应在页面顶部看到一句话的摘要,其中包含该页面上的所有评价。
- 添加新的评价并刷新页面。您应该会看到摘要发生变化。
11. 总结
恭喜!您了解了如何使用 Firebase 为 Next.js 应用添加特性和功能。具体来说,您使用了以下各项:
- Firebase App Hosting,以便在您每次推送到已配置的分支时自动构建和部署 Next.js 代码。
- Firebase Authentication,用于启用登录和退出功能。
- Cloud Firestore,用于处理餐厅数据和餐厅评价数据。
- Cloud Storage for Firebase,用于存储餐馆图片。