2022年4月28日

使用 Remix、Prisma 和 MongoDB 构建全栈应用程序:参照完整性和图像上传

阅读时间12分钟

欢迎来到本系列的第四篇文章,您将学习如何使用 MongoDB、Prisma 和 Remix 从头开始构建一个全栈应用程序!在本部分中,您将构建应用程序的个人资料设置部分,包括图像上传组件,并配置您的架构以在数据中提供参照完整性。

Build A Fullstack App with Remix, Prisma & MongoDB: Referential Integrity & Image Uploads

目录

简介

在本系列的上一部分中,您构建了此应用程序的主要区域,包括Kudos动态、用户列表、最近Kudos列表和Kudos发送表单。

在本部分中,您将通过构建一种允许用户更新其个人资料信息和上传个人资料图片的方式来结束此应用程序的开发。您还将对架构进行一些更改,这将使您的数据库具有参照完整性。

注意:此项目的起点可在 GitHub 存储库的 part-3 分支中找到。如果您想查看本部分的最终结果,请转到 part-4 分支。

开发环境

为了遵循所提供的示例,您需要...

注意:可选扩展为 Tailwind 和 Prisma 增加了一些非常好的智能感知和语法高亮。

构建个人资料设置模态框

您应用程序的个人资料设置页面将显示在一个模态框中,通过点击页面右上角的个人资料设置按钮来访问。

app/components/search-bar.tsx

  • 向导出的组件添加一个名为 profile 的新 prop,其类型为 Prisma 生成的 Profile 类型
  • 导入 UserCircle 组件。
  • form 内容的末尾渲染 UserCircle 组件,并将新的 profile prop 数据传递给它。这将作为您的个人资料设置按钮。

如果您的开发服务器已经运行,这将导致您的主页抛出错误,因为 SearchBar 组件现在需要个人资料数据。

app/routes/home.tsx 文件中,使用本系列第二部分中编写的 app/utils/auth.server.ts 中的 getUser 函数。使用此函数在 loader 函数内部加载已登录用户的数据。然后将该数据提供给 SearchBar 组件。

您的 SearchBar 现在将可以访问其所需的 profile 数据。如果您之前因为缺少此数据而收到错误,刷新浏览器页面应该会显示个人资料按钮在页面右上角成功渲染。

创建模态框

目标是点击个人资料设置按钮时打开一个个人资料设置模态框。与本系列上一部分中构建的 kudos 模态框类似,您需要设置一个嵌套路由来渲染新的模态框。

app/routes/home 中,添加一个名为 profile.tsx 的新文件,其内容如下:

以上代码片段...

  • ...将一个模态框渲染到一个新的 ProfileSettings 组件中。
  • ...在 loader 函数中检索并返回已登录用户的数据。
  • ...使用 useLoaderData 钩子访问从 loader 函数返回的 user 数据。

要打开这个新的模态框,在 app/components/search-bar.tsx 中,向 UserCircle 组件添加一个 onClick 处理程序,该处理程序使用 Remix 的 useNavigate 钩子将用户导航到 /home/profile 子路由。

如果您现在点击个人资料设置按钮,您应该会看到新的模态框显示在屏幕上。

构建表单

您将构建的表单将有三个字段,允许用户修改其个人资料详细信息:名字、姓氏和部门。

通过添加名字和姓氏输入框来开始构建表单

以下是上面添加内容的概述

  1. 添加了所需更改所需的导入。
  2. 在状态中创建了一个 formData 对象,该对象保存表单的值。这会将这些值默认为已登录用户现有的个人资料数据。
  3. 创建了一个函数,该函数将HTML change 事件和字段名称作为参数。这些用于在组件中输入字段的值发生变化时更新 formData 状态。
  4. 渲染表单的基本布局以及两个输入字段。

目前,尚未进行错误处理,表单也未执行任何操作。在添加这些部分之前,您需要添加部门下拉菜单。

app/utils/constants.ts 中,添加一个新的 departments 常量来保存 Prisma 架构中定义的可能选项。向该文件添加以下导出:

departments 导入到您的 app/routes/home/profile.tsx 文件中,以及 SelectBox 组件,并使用它们向您的表单添加一个新的输入字段

此时,您的表单应呈现正确的输入及其选项。它将把它们的值默认为与登录用户个人资料关联的当前值。

允许用户提交表单

您将构建的下一部分是 action 函数,它将使此表单具有功能性。

在您的 app/routes/home/profile.tsx 中,添加一个 action 函数,该函数从 request 对象中检索表单数据并验证 firstNamelastNamedepartment 字段

上述 action 函数执行以下操作:

  1. request 对象中提取所需的表单数据点。
  2. 确保您关心的每个数据片段都是 string 数据类型。
  3. 使用之前编写的 validateName 函数验证数据。
  4. 重定向到 /home 路由,关闭设置模态框。

上面的代码片段还会在各种验证失败时抛出相关错误。为了使用经过验证的数据,请编写一个允许您更新用户的函数。

app/utils/user.server.ts 中,导出以下函数

此函数将允许您传入任何 profile 数据并更新 id 与提供的 userId 匹配的用户。

回到 app/routes/home/profile.tsx 文件中,导入该新函数并在 action 函数中使用它来更新已登录用户

现在,当用户点击保存按钮时,他们更新后的个人资料数据将被存储,模态框也将关闭。

添加图像上传组件

设置AWS账户

您的用户现在可以更新其个人资料中的一些关键信息,但是,如果能添加允许用户设置个人资料图片的功能,以便其他用户更容易识别他们,那将是很好的。

为此,您将设置一个 AWS S3 文件存储桶来存储上传的图像。如果您还没有AWS账户,可以在这里注册。

注意:Amazon 提供了一个免费套餐,让您可以免费访问 S3。

创建IAM用户

拥有帐户后,您需要设置一个 AWS Identity Access Management (IAM) 用户,以便生成访问密钥 ID秘密密钥,这两者都是与 S3 交互所必需的。

注意:如果您已经拥有 IAM 用户及其密钥,请跳过此部分。

前往AWS控制台主页。在页面右上角,点击标有您用户名的下拉菜单,然后选择安全凭证

进入该部分后,在左侧菜单“访问管理”下,点击用户选项。

在此页面上,点击页面右上角的添加用户按钮。

这将引导您完成一个简短的向导,允许您配置用户。请按照以下步骤操作

这第一部分需要

  1. 用户名:提供任意用户名。
  2. 选择 AWS 访问类型:选择访问密钥 - 编程访问选项,这将启用访问密钥 ID秘密密钥的生成。

在向导的第二步,进行以下选择:

  1. 选择“直接附加现有策略”选项。
  2. 搜索“S3”一词。
  3. 点击标有AmazonS3FullAccess的选项旁边的复选标记。
  4. 点击表单底部的“下一步”。

如果您想为您的用户添加标签以帮助更轻松地管理和组织您账户中的用户,请在向导的第三步在此处添加。完成此页面后,点击下一步

如果此页面的摘要看起来不错,请点击页面底部的创建用户按钮。

点击该按钮后,您将进入一个包含您的访问密钥 ID秘密密钥的页面。复制它们并将其存储在您可以轻松访问的地方,因为您很快就会用到它们。

设置S3存储桶

现在您已经拥有了用户和访问密钥,请前往 AWS S3 控制台,在那里您将设置文件存储桶。

在该页面的右上角,点击创建存储桶按钮。

系统将要求您提供存储桶的名称和区域。填写这些详细信息,并将您选择的值与您之前保存的访问密钥 ID秘密密钥一起保存。您稍后也将需要这些信息。

填写完毕后,点击表单最底部的创建存储桶

存储桶创建完成后,您将被带到存储桶的仪表板页面,位于“对象”选项卡。导航到权限选项卡。

在此选项卡中,点击阻止公共访问部分下的编辑按钮。在此表单中,取消选中阻止所有公共访问框,然后点击保存更改。这将您的存储桶设置为公共,这将允许您的应用程序访问图像。

在该部分下方,您将看到一个存储桶策略部分。粘贴以下策略,并确保将<bucket-name>替换为您的存储桶名称。此策略将允许您的图像被公开读取

您现在已经设置好了 AWS 用户和 S3 存储桶。接下来,您需要将密钥和存储桶配置保存到您的 .env 文件中,以便以后使用。

更新Prisma架构

您现在将在数据库中创建一个字段,用于存储上传图像的链接。这些链接应存储在 Profile 嵌入文档中,因此请向您的 Profile 类型块添加一个新字段。

要使用这些更改更新 Prisma Client,请运行 npx prisma generate

构建图像上传组件

app/components 中创建一个名为 image-uploader.tsx 的新文件,其内容如下:

上面的代码片段是完整的图像上传组件。以下是正在发生的事情的概述:

  1. 定义了一个 preventDefault 函数来处理组件中文件输入的更改。
  2. 定义了一个 handleDrop 函数来处理组件中文件输入的 drop 事件。
  3. 定义了一个 handleChange 函数来处理组件中文件输入的任何 change 事件。
  4. 渲染了一个带有各种事件处理程序的 div,允许它对文件拖放、拖动事件和点击做出反应。这些用于触发图像上传和样式更改,这些更改仅在元素接收到拖动事件时才出现。

每当此组件中的 input 值发生更改时,将调用 props 中的 onChange 函数,并传递文件数据。这些数据将上传到 S3。

接下来创建将处理图像上传的服务。

构建图像上传服务

要构建您的图像上传服务,您需要两个新的 npm 包:

  • aws-sdk:公开了一个 JavaScript API,允许您与 AWS 服务进行交互。
  • cuid:一个用于生成唯一 ID 的工具。您将使用它来生成随机文件名。

您的图像上传服务将位于一个新的实用程序文件中。在 app/utils 中创建一个名为 s3.server.ts 的文件。

为了处理上传,您将使用 Remix 的 unstable_parseMultipartFormData 函数,该函数处理 request 对象的 multipart/form-data 值。

注意multipart/form-data 是在表单中发布整个文件时的表单数据类型。

unstable_parseMultipartFormData 将接受两个参数

  1. 从表单提交中检索到的 request 对象。
  2. 一个 uploadHandler 函数,它流式传输文件数据并处理上传。

注意unstable_parseMultipartFormData 函数的使用方式类似于我们过去使用过的 Remix 的 request.formData 函数。

将以下函数和导入添加到您创建的新文件中

此代码设置了您的 S3 API,以便您可以与存储桶交互。它还添加了 uploadHandler 函数。此函数

  1. 使用您在设置 AWS 用户和 S3 存储桶时存储的环境变量来设置 S3 SDK。
  2. 只要数据键的名称为 'profile-pic',就从 request 中流式传输文件数据。
  3. 将文件上传到S3。
  4. 返回 S3 返回的 Location 数据,其中包含新文件在 S3 中的 URL 位置。

现在 uploadHandler 完成了,添加另一个函数,它实际接收 request 对象并将其与 uploadHandler 一起传递给 unstable_parseMultipartFormData 函数。

此函数会传入一个 request 对象,该对象稍后会从 action 函数发送过来。

文件数据通过 uploadHandler 函数传递,该函数处理上传到 S3 的操作,formData 则返回表单数据对象中新文件的位置。然后从该对象中提取 'profile-pic' URL,并由该函数返回。

使用组件和服务

现在,实现个人资料图片上传所需的两个部分已经完成,将它们组合起来。

添加一个资源路由,通过在 app/routes 中创建一个名为 avatar.ts 的新文件,其中包含以下 action 函数来处理您的上传表单数据

上述函数执行以下步骤来处理上传表单:

  1. 获取请求用户的 id
  2. 上传请求数据中传递的文件。
  3. 使用新的 profilePicture URL 更新请求用户的个人资料数据。
  4. 使用 imageUrl 变量响应 POST 请求。

现在您可以使用 ImageUploader 组件处理文件上传并将文件数据发送到这个新的 /avatar 路由。

app/routes/home/profile.tsx 中,导入 ImageUploader 组件并将其添加到您的表单中,位于输入字段的左侧。

还要添加一个新函数来处理 ImageUploader 组件发出的 onChange 事件,以及在 formData 变量中添加一个新字段来存储个人资料图片数据。

现在,如果您转到该表单并尝试上传文件,数据应该会正确地保存到 S3、数据库和您的表单状态中。

显示个人资料图片

这太棒了!图像上传运行顺利,现在您只需要在网站上任何显示用户头像的地方显示这些图像即可。

打开 app/components/user-circle.tsx 中的 UserCircle 组件,并进行以下更改,以便在可用时将圆形的背景图像设置为个人资料图片

如果您现在为几个用户设置了个人资料图片,您应该会看到它们在整个网站上显示!

添加删除账户功能

您的个人资料设置模态框所需的最后一块功能是删除账户的能力。

删除数据,尤其是在无模式数据库中,可能会创建“孤立文档”,即曾经与父文档相关联但其父文档在某个时候被删除的文档。

在本节中,您将设置防护措施以防止这种情况发生。

添加删除按钮

您将以类似于处理登录和注册表单的方式来处理此表单。此表单将发送一个 _action 键,让 action 函数知道它收到了哪种请求。

app/routes/home/profile.tsx 中,对 ProfileSettings 函数返回的 form 进行以下更改:

现在,根据点击的按钮,您可以在 action 函数中处理不同的 _action

更新 action 函数以使用 switch 语句执行不同的操作

现在,如果用户保存表单,将触发 'save' 情况,并执行现有功能。'delete' 情况目前不执行任何操作。

app/utils/user.server.ts 中添加一个新函数,该函数接受一个 id 并删除与之关联的用户

您现在可以填写个人资料页面上 "delete" 情况的其余部分。

您的用户现在可以删除他们的账户了!

更新数据模型以添加参照完整性

此删除用户功能唯一的问题是,当用户被删除时,他们撰写的所有点赞都会变成孤立的

您可以使用参照动作来触发在作者被删除时删除任何点赞。

运行 npx prisma db push 以传播这些更改并生成 Prisma Client。

现在,如果您删除一个帐户,该帐户撰写的所有 Kudos 都将随之删除!

添加表单验证

你快接近尾声了!最后一步是在个人资料设置表单中连接错误消息处理。

您的 action 函数已经返回了所有正确的错误消息;它们只需要被处理。

app/routes/home/profile.tsx 中进行以下更改以处理这些错误

上述代码片段进行了以下更改:

  1. 使用了 useActionData 钩子来检索错误消息。这些错误消息存储在状态变量中,并在用户提交了错误的表单后返回模态框时用于填充表单。
  2. 添加了错误输出以显示任何表单级别的错误。
  3. 错误数据被传递给 FormField 组件,以便它们可以在需要时显示其字段级别的错误。

进行上述更改后,您将在表单上看到任何表单和验证错误。

总结和下一步

通过本文所做的更改,您成功完成了 Kudos 应用程序!网站的所有部分都已功能完善,可以交付给您的用户。

在本节中您学到了

  • Remix 中的嵌套路由
  • AWS S3
  • Prisma 和 MongoDB 的参照操作和完整性

在本系列的下一部分中,您将通过将您构建的应用程序部署到 Vercel 来结束本系列!

不要错过下一篇文章!

订阅 Prisma 新闻通讯

© . This site is unofficial and not affiliated with Prisma Data, Inc.