在Rails应用程序中使用AWS S3进行文件存储的教程

282 阅读13分钟

亚马逊网络服务的S3是一种对象存储服务,拥有安全性、可扩展性、数据可用性和性能。S3是一个建立在桶上的云服务,你在其中存储文件并管理权限。任何技能的开发者都可以利用它来安全地存储与他们的应用程序相关的文件,同时实现近乎完美的正常运行时间。S3将文件存储在 "桶 "中,这类似于文件夹。存储在桶内的文件通常被称为 "对象"。

S3中的三个S代表 "简单存储服务",这是一个极具描述性的名字。S3很容易设置、使用和管理。S3是相对普遍的。它使各种规模的网站的开发者能够获得亚马逊大规模的优势。我们可以直接上传和下载文件,同时为其他人(无论是应用程序还是个人)管理权限,以进行同样的操作。

本文将指导你在你的Ruby on Rails应用程序中利用S3。S3提供了一种上传文件的方法,然后可以通过编程或直接通过URL进行检索。这直接集成到Rails自己的ActiveStorage中,因此在常见的使用情况下,对亚马逊网络服务的实际API调用被抽象出来了。我们将从创建一个AWS账户和一个S3桶开始,并迅速创建一个原始的Rails应用程序,用于执行我们的集成。你将学习如何使用AWS SDK Ruby Gem向S3传输数据,代表应用程序用户存储文件,以及管理文件权限。

开始学习

创建一个AWS账户

如果你没有亚马逊网络服务的账户,创建一个账户并不费力。免费层是慷慨的,将允许我们立即开始使用。

请在这里注册,并填写你的信息,以便开始工作

你会被提示填写更多信息,包括账户类型和联系信息,你将需要输入信用卡/借记卡信息以支付免费层级之外的任何使用。我们对Lambda的简单使用包括在免费层中,但如果你担心意外的超额使用,你可以设置一个预算来控制你的使用,防止意外的账单。

创建一个S3桶

在AWS控制台,执行 "S3 "搜索,并从下拉菜单中选择产品供应。

一旦你进入S3管理控制台,点击 "创建桶 "按钮。你将立即被带到一个向导,在那里你将设置桶的细节。创建一个令人难忘的名字,暂时保留默认区域。

A screenshot of the S3 Bucket Creation Wizard

  • 保持选择 "阻止所有公共访问",以确保你的水桶不对任何不应该使用它的人开放。
  • 你也可以把 "Bucket Versioning "设置为 "Disable"。在本教程中,我们不会使用它。
  • 最后,将所有的标签留空,并保持加密关闭。

在你点击 "创建 "之后,你就会有一个功能性的S3桶了!但是你仍然需要配置一个S3桶。然而,你仍然需要配置该桶的权限。首先,在AWS控制台的身份访问管理(IAM)。点击侧板上的 "用户"。

点击 "添加用户"。给你的新用户起个名字,比如active-storage-user ,并且给它程序性访问权限。在下一个屏幕上,你将需要设置权限。点击 "直接附加现有策略 "的标签。接下来,搜索S3并点击AmazonS3FullAccess 旁边的复选框。A screenshot of AWS IAM User Management Permissions

你可以跳过给用户添加标签,所以只需通过审查并完成创建用户的工作注意访问密钥ID秘密访问密钥,因为我们在后面的Rails配置中会用到它们。

接下来,我们需要创建一个基本的Ruby on Rails应用程序来与我们的水桶进行交互。如果你已经有一个Rails应用程序,你可以跳过这一步,但你仍然要注意AWS的配置。

创建一个基本的Rails应用程序

在这个例子中,我将使用以下版本:

  • Rails 6.1
  • Ruby 3.0.0

请注意,rbenv 是一种非常标准化的方式来管理不同的Ruby版本。如果你有homebrew ,你可以用brew install rbenv 安装它。

如果你使用rbenv,你可以用rbenv install 3.0.0 来安装Ruby 3.0.0。

然后,你可以用rbenv local 3.0.0 将你的当前目录切换到Ruby 3.0.0。

如果是新版本的Ruby,你会想用gem install rails

现在你已经准备好了,为了配合Rails 6.1和Ruby 3.0,你可以通过运行rails new s3-example 来创建一个新的Rails应用程序。用另一个项目名称替换s3-example ,但记住你也必须在任何其他代码/外壳中引用项目名称时将其替换掉。

接下来,把它改成新的项目目录:cd s3-example

最后,在本地为你的项目提供服务,以验证Rails服务器上的一切工作是否正常。然后,在你的浏览器中导航到localhost:3000 ,看看Rails的欢迎页面如果你看到类似这样的东西,你就走对了路。A screenshot of the Rails welcome page

现在我们的Rails应用已经启动并运行,我们需要设置UI来处理一些文件上传。

传输数据

配置

我们将利用ActiveStorage来代表我们的应用程序与S3对话。要设置它,请到config/storage.yml 首先,取消对以amazon 为首的部分的comment。接下来,填入你的桶的名称和区域。对于我的应用程序,config/storage.yml 的亚马逊部分看起来像这样。

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-2
  bucket: honeybadger-rails-files

接下来,在config/environments/production.rb 中改变一个类似的设置。读作config.active_storage.service = :amazon 的那一行应该改为config.active_storage.service = :amazon

如果你想确保它在你的开发环境中工作,你需要在config/environments/development.rb 中设置同样的行。

你可以使用类似dotenv 的东西来管理你的秘密API密钥,但我们将使用Rails内置的加密凭证管理器。Rails会提取AWS的访问密钥,并将其加密的凭证秘密取出。首先,你必须在Rails凭证文件中设置访问密钥。

在你的shell中,运行EDITOR=vim bin/rails credentials:edit ,以解密并在vim中打开凭证文件。按下键盘上的'i'键,切换到插入模式。取消对aws 这一行的注释,以及access_key_idsecret_access_key 。现在,用你创建IAM用户后列出的内容替换access_key_id 的占位符值。对secret_access_key 做同样的处理。

接下来,保存该文件并退出vim。有几种方法可以做到这一点,但我最喜欢的方法是按下escape键(离开插入模式),然后输入:wq ,然后按回车键。如果你想了解更多关于Rails如何处理加密凭证的信息,这是一个很好的资源

现在,你的Rails应用程序已经准备好使用ActiveStorage进行文件存储,并在幕后完全使用AWS S3!

为了完全利用S3的力量,我们需要安装相关的Ruby Gem。将以下内容添加到你的Gemfile

gem 'aws-sdk-s3'

随后运行bundle install ,安装该宝石(以及你的宝石文件中尚未安装的其他东西)。

使用ActiveStorage需要运行以下程序。rails active_storage:install

这将创建一个迁移,所以用下面的方法运行它。rails db:migrate

你的应用程序现在已经准备好与S3一起工作了!

对一个用户的范围

为了用S3做一些有趣的事情,我们首先要给我们的应用程序提供认证功能。用户将能够建立一个账户,签入和签出。这样,他们就可以把文件上传到应用程序中,并将其范围扩大到他们。Devise gem是为Ruby on Rails应用程序添加简单认证的简单方法,所以我将在这里使用它。首先,用这个把宝石添加到你的Gemfile中。

gem 'devise'

接下来,通过运行bundle install ,安装Ruby gem。

接下来,利用Devise的生成器,运行rails generate devise:install

Devise需要一个用户模型来工作,所以通过运行rails generate devise User 来创建一个。

通过运行数据库迁移来完成这一切:rails db:migrate

如果你想看到和编辑devise的视图文件(我们就是这样做的!),那么你需要运行rails generate devise:views

你的应用程序现在应该被设置成可以验证用户。 用rails server 来运行服务器,然后导航到localhost:3000/users/sign_up ,就可以看到你的新的注册页面了!如果你做得很好,它应该看起来很像这样:

A screenshot of the new sign-up page

先不要费心创建一个用户,因为我们还没有完全完成表单本身。如果你已经创建了一个用户,我们就有点麻烦了。我们没有添加 "退出 "按钮,所以你必须手动删除会话cookie并进行硬刷新。

比方说,我们希望我们的用户能够为他们的个人资料上传一个头像。我们将首先在模型中添加一个附件。在app/models/user.rb ,添加以下一行。

has_one_attached :avatar

你可以把图片称为任何你想要的东西,但在这里,我把它称为'avatar'。下一个合乎逻辑的步骤是在表单中添加头像字段。在app/views/devise/registrations/devise/new.html.erb ,在表单中添加以下内容(在提交按钮的上方)。

<div class="field">
  <%= f.label :avatar %>
  <%= f.file_field :avatar %>
</div>

你需要允许:avatar这个参数。同样,我们不能直接编辑Devise控制器,所以在app/controllers.html.erb ,添加以下内容。

  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    attributes = [:email, :password, :avatar]
    devise_parameter_sanitizer.permit(:sign_up, keys: attributes)
  end

你的新注册页面将看起来像这样:A screenshot of the even newer sign-up page

在你提交表单后,该文件将由应用程序处理,并对特定用户进行范围控制。

Devise没有内置的用户认证,也没有一个我们可以编辑的公共控制器。用rails generate controller Users index 创建一个。

app/controllers/users_controller.rb 的索引方法中,粘贴@users = User.all 。现在,在app/views/users/index.html.erb ,粘贴以下内容。

<h1>Users#index</h1>
<% @users.each do |user| %>
    <%= user.email %><br/>
    <%= user.avatar %><br/>
<% end %>

接下来,在你的routes.rb 文件中添加以下内容,以匹配控制器动作的URL。

match '/users',   to: 'users#index',   via: 'get'

现在,点击localhost:3000/users ,看到每个用户的附件图片都能正常显示。

你永远不需要直接调用AWS S3。Rails自己的ActiveStorage将其抽象化了,就像ActiveRecord将数据库抽象化一样。

你可以直接检索附加在用户(或你使用的任何其他模型)上的文件,就像下面这样:

url_for(@user.avatar)

在你的代码中,你也可以直接下载一个用以下方式存储的文件。

file = @user.avatar.download

权限(Permissions

默认情况下,你的S3桶中的所有文件都是私有的。我们在IAM中创建了一个用户,他有权限代表我们的应用程序对这些文件做任何事情。只要我们保持API密钥的私密性,我们的应用程序将是唯一能够访问这些文件的东西。因此,我们应该根据需要来编写我们的应用程序,以确保文件的安全。

S3文件是通过一个链接下载的。你可能会想,这种方法是非常不安全的。毕竟,你可能不希望有人从你的应用程序中搜刮文件,无论它们是什么。S3的权限在某种程度上解决了这个问题。因为我们只授予我们应用程序的用户角色访问权,所以陌生人无法下载文件,即使他们能找到高度混淆的URL。在我们目前的配置中,只有我们的应用程序可以访问S3桶。

这使得私人文件的范围由用户决定。在我们上面的例子中,我们让一个用户上传了一张头像,然后在用户索引页上公开显示。这对一个面向公众的功能来说是很好的,但你可能需要一个用户存储文件,并让其他用户无法访问。如果是这种情况,你要确保你只把每个文件上的url_for 暴露给与之相关的用户。Devise有内置的方法可以帮助解决这个问题。

S3也可以被用来公开托管文件。由于其令人难以置信的便宜的访问调用,许多应用程序使用它来托管网络资产。你可以通过开放存储文件的桶的权限来做到这一点,这样就可以通过其URL直接到达。然而,这种方法会使你的S3桶对整个互联网开放。更明智的做法是将其限制在你的应用程序中,让应用程序代表客户加载图片,就像我们对用户头像所做的那样。

为更高的流量水平提供托管服务

虽然你在技术上可以使用S3来托管任何文件,但它并不是托管网络资产的一个好选择。除非你有一些严肃的复制逻辑,否则S3上托管的文件只在你最初选择的区域。托管在us-east-2 地区的文件,对于例如在弗吉尼亚州的人来说,可能加载得比较快。然而,当客户在世界的另一端时,加载时间很快就成为一个问题。这对某些类型的文件来说是可以处理的,例如用于实用的文件。像背景图像和图标这样的东西会迅速降低你网站的用户体验。

最常见的传递网络资产的方法是直接从服务器上传递。从S3加载的资产可能需要大约两倍的时间来加载,这取决于与可用区的距离。S3主要是为存储而设计的,所以使用内容交付网络(CDN)来交付需要不断检索的文件是非常普遍的。CDN以低延迟和高传输速度提供内容,这意味着你的用户可以更快地获得你的网站。除此之外,它们还可以提供额外的安全和DOS保护,从而改善正常运行时间,甚至在你的网站成为目标时减少成本。

CloudFront是AWS专门为此目的而设计的另一个产品。它与AWS紧密结合,所以用我们的S3桶设置它是非常简单的。通过这种配置,网络资产被存储在S3中,并由CloudFront交付给你的应用程序。

只需进入AWS控制台,搜索 "CloudFront"。创建一个新的分布,将交付方式设置为 "Web"。通过在config/environments/production.rb ,Rails可以被设置为从CloudFront和S3提供静态资产(预编译):config.action_controller.asset_host = "<YOUR DISTRIBUTION SUBDOMAIN>.cloudfront.net"


S3是一个强大的工具,用于托管和检索文件,包括静态资产、公共文件或个别用户的私人文件。