使用 Firebase Data Connect 构建

1. 准备工作

FriendlyMovies 应用

在此 Codelab 中,您将 Firebase Data Connect 与 Cloud SQL 数据库集成,以构建电影评价 Web 应用。完成的应用展示了 Firebase Data Connect 如何简化构建依托 SQL 的应用的过程。它包含以下功能:

  • 身份验证:为应用的查询和更改实现自定义身份验证,确保只有已获授权的用户才能与您的数据互动。
  • GraphQL 架构:使用专为影评 Web 应用需求量身定制的灵活 GraphQL 架构创建和管理数据结构。
  • SQL 查询和变更:使用由 GraphQL 提供支持的查询和变更在 Cloud SQL 中检索、更新和管理数据。
  • 支持部分字符串匹配的高级搜索:使用过滤条件和搜索选项,根据标题、说明或标签等字段查找电影。
  • 可选:向量搜索集成:使用 Firebase Data Connect 的向量搜索功能添加内容搜索功能,以便根据输入和偏好设置提供丰富的用户体验。

前提条件

您需要对 JavaScript 有基本的了解。

学习内容

  • 使用本地模拟器设置 Firebase Data Connect。
  • 使用 Data Connect 和 GraphQL 设计数据架构。
  • 为影评应用编写和测试各种查询和更改。
  • 了解 Firebase Data Connect 如何在应用中生成和使用 SDK。
  • 高效部署架构并管理数据库。

所需条件

设置开发环境

本部分将指导您设置环境,以便开始使用 Firebase Data Connect 构建影评应用。

第 1 步:克隆项目代码库

首先克隆项目仓库并安装所需的依赖项:

git clone https://github.com/firebaseextended/codelab-dataconnect-web
cd codelab-dataconnect-web
cd ./app && npm i
npm run dev
  1. 运行这些命令后,在浏览器中打开 http://localhost:5173,以查看在本地运行的 Web 应用。它将用作构建电影评价应用和与其功能互动的前端。

93f6648a2532c606.png

第 2 步:在 Visual Studio Code 中打开项目

使用 Visual Studio Code 打开克隆的 codelab-dataconnect-web 文件夹。您将在此处定义架构、编写查询并测试应用的功能。

第 3 步:安装 Firebase Data Connect Visual Studio 扩展程序

如需使用 Data Connect 功能,请安装 Firebase Data Connect Visual Studio 扩展程序。或者:从 Visual Studio Code Marketplace 进行安装,或在 VS Code 中搜索。

  1. 或者:从 Visual Studio Code Marketplace 安装或在 VS Code 中搜索。

b03ee38c9a81b648.png

第 4 步:创建 Firebase 项目

前往 Firebase 控制台创建一个新的 Firebase 项目(如果您还没有此项目)。然后,在 Firebase Data Connect VSCode 扩展程序中执行以下操作:

  • 点击登录按钮。
  • 点击关联 Firebase 项目,然后选择您在 Firebase 控制台中创建的项目。

4bb2fbf8f9fac29b.png

第 5 步:启动 Firebase 模拟器

在 Firebase Data Connect VSCode 扩展程序中,点击“启动模拟器”,并确认模拟器正在终端中运行。

6d3d95f4cb708db1.png

2. 查看起始代码库

在本部分中,您将探索应用起始代码库的主要部分。虽然该应用缺少某些功能,但了解整体结构会很有帮助。

文件夹和文件结构

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

dataconnect/

包含 Firebase Data Connect 配置、连接器(定义查询和变更)和架构文件。

  • schema/schema.gql:定义 GraphQL 架构
  • connector/queries.gql:应用所需的查询。
  • connector/mutations.gql:应用所需的变更。
  • connector/connector.yaml: SDK 生成配置文件

app/src/

包含应用逻辑以及与 Firebase Data Connect 的交互。

  • firebase.ts:用于连接到控制台中 Firebase 应用的配置。
  • lib/dataconnect-sdk/:此文件夹包含生成的 SDK。您可以在 Connector/connector.yaml 文件中修改 SDK 生成位置,每次定义查询或变更时,SDK 都会自动生成。

3. 定义“电影评论”的架构

在本部分中,您将在架构中定义电影应用中关键实体的结构和这些实体之间的关系。MovieUserActorReview 等实体会映射到数据库表,并使用 Firebase Data Connect 和 GraphQL 架构指令建立关系。此映像准备就绪后,您的应用就可以处理各种任务了,从搜索高分电影和按类型过滤,到允许用户发表评价、标记收藏、探索类似电影,或根据通过矢量搜索输入的文本查找推荐的电影,不一而足。

核心实体和关系

Movie 类型包含标题、类型和标签等关键详细信息,应用会使用这些信息进行搜索和电影个人资料。User 类型用于跟踪用户互动,例如评价和收藏。Reviews 可将用户与电影相关联,让应用显示用户生成的评分和反馈。

电影、演员和用户之间的关系让应用变得更加动态。MovieActor 联接表有助于显示演员表详细信息和演员电影。FavoriteMovie 类型允许用户将电影设为收藏,以便应用显示个性化的收藏列表并突出显示热门选段。

影片桌

Movie 类型定义电影实体的主要结构,包括标题、类型、发行年份和评分等字段。

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type Movie
  @table {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
}

要点总结

  • id:每个电影的唯一 UUID,使用 @default(expr: "uuidV4()") 生成。

MovieMetadata 表

MovieMetadata 类型与该电影类型建立一对一关系。其中包含其他数据,例如电影的导演。

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type MovieMetadata
  @table {
  # @ref creates a field in the current table (MovieMetadata)
  # It is a reference that holds the primary key of the referenced type
  # In this case, @ref(fields: "movieId", references: "id") is implied
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
  director: String
}

要点总结

  • 电影!@ref:引用 Movie 类型,建立外键关系。

“Actor”表

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type Actor @table {
  id: UUID!
  imageUrl: String! 
  name: String! @col(name: "name", dataType: "varchar(30)")
}

Actor 类型表示电影数据库中的演员,其中每位演员都可以参与多部电影,从而形成多对多关系。

MovieActor 表

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type MovieActor @table(key: ["movie", "actor"]) {
  # @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
  # In this case, @ref(fields: "id") is implied
  movie: Movie!
  # movieId: UUID! <- this is created by the implied @ref, see: implicit.gql

  actor: Actor!
  # actorId: UUID! <- this is created by the implied  @ref, see: implicit.gql

  role: String! # "main" or "supporting"
}

要点总结:

  • movie:引用 Movie 类型,隐式生成外键 movieId: UUID!。
  • actor:引用 Actor 类型,隐式生成外键 actorId: UUID!。
  • role:定义电影中的演员的角色(例如,“main”或“supporting”)。

“用户”表

User 类型定义了通过发表评价或收藏电影来与电影互动的用户实体。

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type User
  @table {
  id: String! @col(name: "auth_uid")
  username: String! @col(dataType: "varchar(50)")
  # The following are generated from the @ref in the Review table
  # reviews_on_user 
  # movies_via_Review
}

FavoriteMovie 表格

FavoriteMovie 类型是一个联接表,用于处理用户与其喜爱的电影或演员之间的多对多关系。每个表都将 UserMovie 相关联。

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type FavoriteMovie
  @table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
  # @ref is implicit
  user: User!
  movie: Movie!
}

要点总结:

  • movie:引用 Movie 类型,隐式生成外键 movieId: UUID!。
  • user:引用用户类型,隐式生成外键 userId: UUID!。

查看表格

Review 类型代表评价实体,并以多对多关系将 User 和 Movie 类型相关联(一位用户可以留下许多评价,每部电影也可以有许多评价)。

将以下代码段复制粘贴dataconnect/schema/schema.gql 文件中:

type Review @table(name: "Reviews", key: ["movie", "user"]) {
  id: UUID! @default(expr: "uuidV4()")
  user: User!
  movie: Movie!
  rating: Int
  reviewText: String
  reviewDate: Date! @default(expr: "request.time")
}

要点总结

  • user:提及发表评价的用户。
  • movie:引用要评价的电影。
  • reviewDate:自动设置为使用 @default(expr: "request.time") 创建评价的时间。

自动生成的字段和默认值

该架构使用 @default(expr: "uuidV4()") 等表达式自动生成唯一 ID 和时间戳。例如,在创建新记录时,Movie 和 Review 类型的 id 字段会自动填充 UUID。

现在,架构已定义完毕,您的电影应用的数据结构和关系已打下坚实的基础!

4. 检索热门电影和最新电影

FriendlyMovies 应用

在本部分中,您会将模拟影片数据插入本地模拟器,然后实现连接器(查询)和 TypeScript 代码,以便在 Web 应用中调用这些连接器。最后,您的应用将能够直接从数据库中动态提取并显示评分最高和最新的电影。

插入模拟电影、演员和评论数据

  1. 在 VSCode 中,打开 dataconnect/moviedata_insert.gql。确保 Firebase Data Connect 扩展程序中的模拟器正在运行。
  2. 您应该会在文件顶部看到一个 Run (local) 按钮。点击此按钮可将模拟电影数据插入数据库。

e424f75e63bf2e10.png

  1. 检查 Data Connect Execution 终端,确认数据是否已成功添加。

e0943d7704fb84ea

实现连接器

  1. 打开 dataconnect/movie-connector/queries.gql。 您会在注释中看到一个基本的 ListMovies 查询:
query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

此查询会提取所有电影及其详细信息(例如 ID、标题、发行年份)。不过,它不会对电影进行排序。

  1. ListMovies 查询替换为下面的查询,以添加排序和限制选项:
# List subset of fields for movies
query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) {
  movies(
    orderBy: [
      { rating: $orderByRating },
      { releaseYear: $orderByReleaseYear }
    ]
    limit: $limit
  ) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    tags
    description
  }
}

点击 Run (local) 按钮,对本地数据库执行查询。您也可以在运行前在“配置”窗格中输入查询变量。

c4d947115bb11b16.png

要点总结:

  • movies():用于从数据库中提取电影数据的 GraphQL 查询字段。
  • orderByRating:用于按评分对电影进行排序(升序/降序)的参数。
  • orderByReleaseYear:用于按上映年份(升序/降序)对电影进行排序的参数。
  • limit:限制返回的电影数量。

在 Web 应用中集成查询

在本部分中,您将在 Web 应用中使用上一部分中定义的查询。Firebase Data Connect 模拟器会根据 .gql 文件 (schema.gql、queries.gql、mutations.gql) 和 connector.yaml 中的信息生成 SDK。这些 SDK 可以直接在您的应用中调用。

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释顶部的 import 语句:
import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";

函数 listMovies、响应类型 ListMoviesData 和枚举 OrderDirection 都是 Firebase Data Connect 模拟器根据您之前定义的架构和查询生成的 SDK。

  1. handleGetTopMovieshandleGetLatestMovies 函数替换为以下代码:
// Fetch top-rated movies
export const handleGetTopMovies = async (
  limit: number
): Promise<ListMoviesData["movies"] | null> => {
  try {
    const response = await listMovies({
      orderByRating: OrderDirection.DESC,
      limit,
    });
    return response.data.movies;
  } catch (error) {
    console.error("Error fetching top movies:", error);
    return null;
  }
};

// Fetch latest movies
export const handleGetLatestMovies = async (
  limit: number
): Promise<ListMoviesData["movies"] | null> => {
  try {
    const response = await listMovies({
      orderByReleaseYear: OrderDirection.DESC,
      limit,
    });
    return response.data.movies;
  } catch (error) {
    console.error("Error fetching latest movies:", error);
    return null;
  }
};

要点总结

  • listMovies:一个自动生成的函数,可调用 listMovies 查询来检索电影列表。它包含按评分或发行年份排序以及限制结果数量的选项。
  • ListMoviesData:用于在首页上显示前 10 部电影和最新电影的结果类型。

看看实际运用情况

重新加载 Web 应用,查看查询的实际运作情况。现在,首页会动态显示电影列表,直接从本地数据库提取数据。您会看到系统会顺畅地显示评分最高的电影和最新电影,这些数据反映了您刚刚设置的数据。

5. 显示电影和演员详情

在本部分中,您将实现使用电影或演员的唯一 ID 检索其详细信息的功能。这不仅涉及从各自的表中提取数据,还涉及联接相关表以显示全面的详细信息,例如电影评价和演员的电影作品目录。

ac7fefa7ff779231

实现连接器

  1. 在项目中打开 dataconnect/movie-connector/queries.gql
  2. 添加以下查询以检索电影和演员详细信息:
# Get movie by id
query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
 movie(id: $id) {
    id
    title
    imageUrl
    releaseYear
    genre
    rating
    description
    tags
    metadata: movieMetadatas_on_movie {
      director
    }
    mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      name
      imageUrl
    }
    supportingActors: actors_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      name
      imageUrl
    }
    reviews: reviews_on_movie {
      id
      reviewText
      reviewDate
      rating
      user {
        id
        username
      }
    }
  }
 }

# Get actor by id
query GetActorById($id: UUID!) @auth(level: PUBLIC) {
  actor(id: $id) {
    id
    name
    imageUrl
    mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) {
      id
      title
      genre
      tags
      imageUrl
    }
    supportingActors: movies_via_MovieActor(
      where: { role: { eq: "supporting" } }
    ) {
      id
      title
      genre
      tags
      imageUrl
    }
  }
}
  1. 保存更改并查看查询。

要点总结:

  • movie()/actor():用于从“电影”或“演员”表中提取单个电影或演员的 GraphQL 查询字段。
  • _on_:这样,您就可以直接访问具有外键关系的关联类型中的字段。例如,reviews_on_movie 会提取与特定电影相关的所有评价。
  • _via_:用于通过联接表导航多对多关系。例如,actors_via_MovieActor 通过 MovieActor 联接表访问演员类型,where 条件根据演员的角色(例如,“main”或“supporting”)。

在 Data Connect 执行窗格中,您可以通过输入模拟 ID 来测试查询,例如:

{"id": "550e8400-e29b-41d4-a716-446655440000"}

点击 GetMovieById运行(本地),以检索“Quantum Paradox”(上述 ID 对应的模拟电影)的详细信息。

1b08961891e44da2.png

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
import { getMovieById, GetMovieByIdData } from "@movie/dataconnect";
import { GetActorByIdData, getActorById } from "@movie/dataconnect";
  1. handleGetMovieByIdhandleGetActorById 函数替换成以下代码:
// Fetch movie details by ID
export const handleGetMovieById = async (
  movieId: string
) => {
  try {
    const response = await getMovieById({ id: movieId });
    if (response.data.movie) {
      return response.data.movie;
    }
    return null;
  } catch (error) {
    console.error("Error fetching movie:", error);
    return null;
  }
};

// Calling generated SDK for GetActorById
export const handleGetActorById = async (
  actorId: string
): Promise<GetActorByIdData["actor"] | null> => {
  try {
    const response = await getActorById({ id: actorId });
    if (response.data.actor) {
      return response.data.actor;
    }
    return null;
  } catch (error) {
    console.error("Error fetching actor:", error);
    return null;
  }
};

要点总结

  • getMovieById/getActorById:这些是自动生成的函数,用于调用您定义的查询,以检索特定电影或演员的详细信息。
  • GetMovieByIdData / GetActorByIdData:这些是结果类型,用于在应用中显示电影和演员详细信息。

看看实际运用情况

现在,转到您的 Web 应用的首页。点击一部电影,您就可以查看其所有详细信息,包括演员和评价,这些信息都是从相关表格中提取的。同样,点击某位演员即可显示其出演的电影。

6. 处理用户身份验证

在本部分中,您将使用 Firebase Authentication 实现用户登录和退出功能。您还可以使用 Firebase Authentication 数据直接检索或插入 Firebase DataConnect 中的用户数据,从而确保应用内的用户管理安全。

9890838045d5a00e.png

实现连接器

  1. dataconnect/movie-connector/打开 mutations.gql
  2. 添加以下变更以创建或更新当前经过身份验证的用户:
# Create or update the current authenticated user
mutation UpsertUser($username: String!) @auth(level: USER) {
  user_upsert(
    data: {
      id_expr: "auth.uid"
      username: $username
    }
  )
}

要点总结:

  • id_expr: "auth.uid":此方法使用 auth.uid(由 Firebase Authentication 直接提供,而不是由用户或应用提供),这样可以确保安全自动地处理 User-ID,从而额外增加一层安全性。

接下来,在 dataconnect/movie-connector/打开 queries.gql”。

添加以下查询以获取当前用户:

# Get user by ID
query GetCurrentUser @auth(level: USER) {
  user(key: { id_expr: "auth.uid" }) {
    id
    username
    reviews: reviews_on_user {
      id
      rating
      reviewDate
      reviewText
      movie {
        id
        title
      }
    }
    favoriteMovies: favorite_movies_on_user {
      movie {
        id
        title
        genre
        imageUrl
        releaseYear
        rating
        description
        tags
        metadata: movieMetadatas_on_movie {
          director
        }
      }
    }
  }
}

要点总结:

  • auth.uid:此数据直接从 Firebase Authentication 检索,可确保安全访问特定于用户的数据。
  • _on_ 字段:这些字段表示联接表:
  • reviews_on_user:获取与用户相关的所有评价,包括电影的 ID 和标题。
  • favorite_movies_on_user:检索用户标记为收藏的所有电影,包括类型、发行年份、评分和元数据等详细信息。

在 Web 应用中集成查询

  1. 在 MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入内容:
import { upsertUser } from "@movie/dataconnect";
import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
  1. handleAuthStateChangehandleGetCurrentUser 函数替换为以下代码:
// Handle user authentication state changes and upsert user
export const handleAuthStateChange = (
  auth: any,
  setUser: (user: User | null) => void
) => {
  return onAuthStateChanged(auth, async (user) => {
    if (user) {
      setUser(user);
      const username = user.email?.split("@")[0] || "anon";
      await upsertUser({ username });
    } else {
      setUser(null);
    }
  });
};

// Fetch current user profile
export const handleGetCurrentUser = async (): Promise<
  GetCurrentUserData["user"] | null
> => {
  try {
    const response = await getCurrentUser();
    return response.data.user;
  } catch (error) {
    console.error("Error fetching user profile:", error);
    return null;
  }
};

要点总结:

  • handleAuthStateChange:此函数会监听身份验证状态变化。当用户登录时,它会设置用户的数据并调用 upsertUser 变更,以在数据库中创建或更新用户的信息。
  • handleGetCurrentUser:使用 getCurrentUser 查询获取当前用户的个人资料,此查询会检索用户的评价和喜爱的电影。

看看实际运用情况

现在,点击“使用 Google 账号登录”按钮。您可以使用 Firebase Auth 模拟器登录。登录后,点击“我的个人资料”。它目前是空的,但您已为在应用中处理特定于用户的数据奠定了基础。

7. 实现用户互动

在本部分中,您将在影评应用中实现用户互动,让用户能够管理他们喜欢的影片以及发表或删除评价。

b3d0ac1e181c9de9.png

实现连接器

  1. dataconnect/movie-connector/打开 mutations.gql
  2. 添加以下更改以处理将电影添加为收藏的操作:
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}

# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

要点总结

  • userId_expr: "auth.uid":使用由 Firebase Authentication 直接提供的 auth.uid,确保只有经过身份验证的用户的数据会被访问或修改。
  1. 接下来,在 dataconnect/movie-connector/打开 queries.gql”。
  2. 添加以下查询,以检查某部电影是否已收藏:
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) {
  favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) {
    movieId
  }
}

要点总结:

  • auth.uid:确保使用 Firebase Authentication 安全地访问特定于用户的数据。
  • favorite_movie:检查 favorite_movies 联接表,以确定当前用户是否已将特定电影标记为收藏。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
  1. handleAddFavoritedMoviehandleDeleteFavoritedMoviehandleGetIfFavoritedMovie 函数替换成以下代码:
// Add a movie to user's favorites
export const handleAddFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await addFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error adding movie to favorites:", error);
    throw error;
  }
};

// Remove a movie from user's favorites
export const handleDeleteFavoritedMovie = async (
  movieId: string
): Promise<void> => {
  try {
    await deleteFavoritedMovie({ movieId });
  } catch (error) {
    console.error("Error removing movie from favorites:", error);
    throw error;
  }
};

// Check if the movie is favorited by the user
export const handleGetIfFavoritedMovie = async (
  movieId: string
): Promise<boolean> => {
  try {
    const response = await getIfFavoritedMovie({ movieId });
    return !!response.data.favorite_movie;
  } catch (error) {
    console.error("Error checking if movie is favorited:", error);
    return false;
  }
};

要点总结:

  • handleAddFavoritedMoviehandleDeleteFavoritedMovie:使用变更安全地向用户收藏夹添加或移除电影。
  • handleGetIfFavoritedMovie:使用 getIfFavoritedMovie 查询检查电影是否已被用户标记为收藏。

查看实际用例

现在,您可以通过点击电影卡片和电影详情页面上的心形图标收藏或取消收藏电影。此外,您还可以在个人资料页面上查看喜爱的电影。

实现用户评价

接下来,您将在应用中实现用于管理用户评价的部分。

实现连接器

  1. mutations.gql (dataconnect/movie-connector/mutations.gql) 中:添加以下更改:
# Add a review for a movie
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
@auth(level: USER) {
  review_insert(
    data: {
      userId_expr: "auth.uid"
      movieId: $movieId
      rating: $rating
      reviewText: $reviewText
      reviewDate_date: { today: true }
    }
  )
}

# Delete a user's review for a movie
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
  review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}

要点总结

  • userId_expr: "auth.uid":确保评价与经过身份验证的用户相关联。
  • reviewDate_date: { today: true }:使用 DataConnect 自动生成评价的当前日期,无需手动输入。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
import { addReview, deleteReview } from "@movie/dataconnect";
  1. handleAddReviewhandleDeleteReview 函数替换成以下代码:
// Add a review to a movie
export const handleAddReview = async (
  movieId: string,
  rating: number,
  reviewText: string
): Promise<void> => {
  try {
    await addReview({ movieId, rating, reviewText });
  } catch (error) {
    console.error("Error adding review:", error);
    throw error;
  }
};

// Delete a review from a movie
export const handleDeleteReview = async (movieId: string): Promise<void> => {
  try {
    await deleteReview({ movieId });
  } catch (error) {
    console.error("Error deleting review:", error);
    throw error;
  }
};

要点总结:

  • handleAddReview:调用 addReview 变更以添加对指定影片的评价,并将其安全地与经过身份验证的用户相关联。
  • handleDeleteReview:使用 deleteReview 变更移除经过身份验证的用户对电影的评价。

看看实际运用情况

用户现在可以在电影详情页面上为电影留下评价。他们还可以在个人资料页面上查看和删除自己的评价,从而完全掌控与应用的互动。

8. 高级过滤器和部分文字匹配

在本部分中,您将实现高级搜索功能,让用户能够根据一系列评分和上映年份搜索电影、按类型和标签进行过滤、在标题或说明中进行部分文本匹配,甚至结合使用多个过滤条件以获得更精确的结果。

ece70ee0ab964e28

实现连接器

  1. dataconnect/movie-connector/ 中打开 queries.gql
  2. 添加以下查询以支持各种搜索功能:
# Search for movies, actors, and reviews
query SearchAll(
  $input: String
  $minYear: Int!
  $maxYear: Int!
  $minRating: Float!
  $maxRating: Float!
  $genre: String!
) @auth(level: PUBLIC) {
  moviesMatchingTitle: movies(
    where: {
      _and: [
        { releaseYear: { ge: $minYear } }
        { releaseYear: { le: $maxYear } }
        { rating: { ge: $minRating } }
        { rating: { le: $maxRating } }
        { genre: { contains: $genre } }
        { title: { contains: $input } }
      ]
    }
  ) {
    id
    title
    genre
    rating
    imageUrl
  }
  moviesMatchingDescription: movies(
    where: {
      _and: [
        { releaseYear: { ge: $minYear } }
        { releaseYear: { le: $maxYear } }
        { rating: { ge: $minRating } }
        { rating: { le: $maxRating } }
        { genre: { contains: $genre } }
        { description: { contains: $input } }
      ]
    }
  ) {
    id
    title
    genre
    rating
    imageUrl
  }
  actorsMatchingName: actors(where: { name: { contains: $input } }) {
    id
    name
    imageUrl
  }
  reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) {
    id
    rating
    reviewText
    reviewDate
    movie {
      id
      title
    }
    user {
      id
      username
    }
  }
}

要点总结

  • _and 运算符:将多个条件合并到一个查询中,以便按 releaseYearratinggenre 等多个字段过滤搜索。
  • contains 运算符:搜索字段中的部分文本匹配项。在此查询中,它会在 titledescriptionnamereviewText 中查找匹配项。
  • where 子句:指定用于过滤数据的条件。每个部分(电影、演员、评价)都使用 where 子句来定义搜索的具体条件。

在 Web 应用中集成查询

  1. MovieService (app/src/lib/MovieService.tsx) 中,取消注释以下导入:
import { searchAll, SearchAllData } from "@movie/dataconnect";
  1. handleSearchAll 函数替换成以下代码:
// Function to perform the search using the query and filters
export const handleSearchAll = async (
  searchQuery: string,
  minYear: number,
  maxYear: number,
  minRating: number,
  maxRating: number,
  genre: string
): Promise<SearchAllData | null> => {
  try {
    const response = await searchAll({
      input: searchQuery,
      minYear,
      maxYear,
      minRating,
      maxRating,
      genre,
    });

    return response.data;
  } catch (error) {
    console.error("Error performing search:", error);
    return null;
  }
};

要点总结:

  • handleSearchAll:此函数使用 searchAll 查询,根据用户输入执行搜索,并按年份、评分、类型和部分文本匹配等参数过滤结果。

查看实际用例

前往网页应用的侧边导航栏,然后前往“高级搜索”页面。现在,您可以使用各种过滤条件和输入内容搜索电影、演员和评价,获得详细且量身定制的搜索结果。

9. 可选:部署到云端(需要结算)

现在,您已经完成本地开发迭代,是时候将架构、数据和查询部署到服务器了。您可以使用 Firebase Data Connect VS Code 扩展程序或 Firebase CLI 完成此操作。

在 Firebase 控制台中添加 Web 应用

  1. Firebase 控制台创建 Web 应用,并记下应用 ID

7030822793e4d75b

  1. 点击“添加应用”,在 Firebase 控制台中设置 Web 应用。您现在可以放心地忽略 SDK 设置和配置设置,但请记下生成的 firebaseConfig 对象。
  2. 替换 app/src/lib/firebase.tsx 中的 firebaseConfig
const firebaseConfig = {
  apiKey: "API_KEY",
  authDomain: "PROJECT_ID.firebaseapp.com",
  projectId: "PROJECT_ID",
  storageBucket: "PROJECT_ID.appspot.com",
  messagingSenderId: "SENDER_ID",
  appId: "APP_ID"
};
  1. 构建 Web 应用:在 app 文件夹中,使用 Vite 构建 Web 应用以进行托管部署:
cd app
npm run build

在控制台中设置 Firebase Authentication

  1. 设置带有 Google 登录功能的 Firebase Auth

62af2f225e790ef6.png

  1. (可选)在项目控制台中为 (Firebase Auth) [https://firebase.google.com/docs/auth/web/hosting] 授予网域访问权限(例如http://127.0.0.1):

c255098f12549886.png

使用 Firebase CLI 进行部署

  1. dataconnect/dataconnect.yaml 中,确保您的实例 ID、数据库和服务 ID 与项目匹配:
specVersion: "v1alpha"
serviceId: "your-service-id"
location: "us-central1"
schema:
  source: "./schema"
  datasource:
    postgresql:
      database: "your-database-id"
      cloudSql:
        instanceId: "your-instance-id"
connectorDirs: ["./movie-connector"]
  1. 确保您已为项目设置 Firebase CLI
npm i -g firebase-tools
firebase login --reauth
firebase use --add
  1. 在您的终端中,运行以下命令进行部署:
firebase deploy --only dataconnect,hosting
  1. 运行以下命令来比较架构更改:
firebase dataconnect:sql:diff
  1. 如果您接受这些更改,请使用以下命令应用更改:
firebase dataconnect:sql:migrate

您的 Cloud SQL for PostgreSQL 实例将更新为包含最终部署的架构和数据。您可以在 Firebase 控制台中监控状态。

现在,您应该能够在 your-project.web.app/ 上看到您的应用。此外,就像使用本地模拟器一样,您可以点击 Firebase Data Connect 面板中的 Run (Production) 以将数据添加到生产环境。

10. 可选:使用 Firebase Data Connect 进行矢量搜索

在本部分中,您将使用 Firebase Data Connect 在影评应用中启用矢量搜索。借助此功能,您可以进行基于内容的搜索,例如使用向量嵌入查找具有类似说明的电影。

4b5aca5a447d2feb.png

更新架构以包含字段的嵌入

  1. dataconnect/schema/schema.gql 中,将 descriptionEmbedding 字段添加到 Movie 表中:
type Movie
  # The below parameter values are generated by default with @table, and can be edited manually.
  @table {
  # implicitly calls @col to generates a column name. ex: @col(name: "movie_id")
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  imageUrl: String!
  releaseYear: Int
  genre: String
  rating: Float
  description: String
  tags: [String]
  descriptionEmbedding: Vector @col(size:768) # Enables vector search
}

要点总结:

  • descriptionEmbedding: Vector @col(size:768):此字段用于存储电影说明的语义嵌入,以便在您的应用中实现基于矢量的内容搜索。

激活 Vertex AI

  1. 按照前提条件指南使用 Google Cloud 设置 Vertex AI API。此步骤对于支持嵌入生成和矢量搜索功能至关重要。
  2. 使用 Firebase Data Connect VSCode 扩展程序,点击“部署到生产环境”,重新部署架构以启用 pgvector 和矢量搜索。

使用嵌入填充数据库

  1. 在 VSCode 中打开 dataconnect 文件夹,然后点击 optional_vector_embed.gql 中的 Run(local),以便使用电影嵌入填充您的数据库。

b858da780f6ec103.png

添加矢量搜索查询

  1. dataconnect/movie-connector/queries.gql 中,添加以下查询以执行矢量搜索:
# Search movie descriptions using L2 similarity with Vertex AI
query SearchMovieDescriptionUsingL2Similarity($query: String!)
@auth(level: PUBLIC) {
  movies_descriptionEmbedding_similarity(
    compare_embed: { model: "textembedding-gecko@003", text: $query }
    method: L2
    within: 2
    limit: 5
  ) {
    id
    title
    description
    tags
    rating
    imageUrl
  }
}

要点总结

  • compare_embed:指定要进行比较的嵌入模型 (textembedding-gecko@003) 和输入文本 ($query)。
  • method:指定相似性方法 (L2),表示欧几里得距离。
  • within:将搜索范围限制为 L2 距离不超过 2 的电影,重点搜索近似的内容匹配。
  • limit:将返回的结果数限制为 5。

在应用中实现矢量搜索功能

  1. app/src/lib/MovieService.ts 中,取消注释以下导入内容:

在应用中实现向量搜索函数

现在,架构和查询已设置完毕,接下来将矢量搜索集成到应用的服务层中。通过此步骤,您可以从 Web 应用调用搜索查询。

app/src/lib/ MovieService.ts 中,取消注释 SDK 中的以下导入,此操作的运作方式与任何其他查询一样。

import {
  searchMovieDescriptionUsingL2similarity,
  SearchMovieDescriptionUsingL2similarityData,
} from "@movie/dataconnect";

添加以下函数,将基于矢量的搜索集成到应用中:

// Perform vector-based search for movies based on description
export const searchMoviesByDescription = async (
  query: string
): Promise<
  | SearchMovieDescriptionUsingL2similarityData["movies_descriptionEmbedding_similarity"]
  | null
> => {
  try {
    const response = await searchMovieDescriptionUsingL2similarity({ query });
    return response.data.movies_descriptionEmbedding_similarity;
  } catch (error) {
    console.error("Error fetching movie descriptions:", error);
    return null;
  }
};


要点总结:

  • searchMoviesByDescription:此函数调用 searchMovieDescriptionUsingL2similarity 查询,传递输入文本以执行基于矢量的内容搜索。

看看实际运用情况

前往导航栏中的“矢量搜索”部分,然后输入“浪漫而现代”等字词。您会看到与您搜索的内容相符的电影列表,或者您也可以进入任何电影的电影详情页面,然后查看页面底部的“类似电影”部分。

7b71f1c75633c1be

11. 总结

恭喜,您应该可以使用 Web 应用了!如果您想播放自己的电影数据,也无需担心,您可以使用 FDC 扩展程序模拟 _insert.gql 文件来插入自己的数据,或者通过 Data Connect Execution 窗格添加这些数据。

了解详情