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 中设置一个 身份和访问管理 (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 组件并将其添加到您的表单中,位于输入字段的左侧。

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

现在,如果您转到该表单并尝试上传文件,数据应该会正确保存在 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" 情况的其余部分。

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

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

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

您可以使用参照操作在作者被删除时触发任何 kudos 的删除。

运行 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 新闻通讯

© . All rights reserved.