如何在 Rails 6 中使用 ActiveStorage 和 DigitalOcean Spaces

作者选择了 多样性在技术基金作为 写给捐款计划的一部分接受捐款。

介绍

当你正在构建允许用户上传和存储文件的网络应用程序时,你会想要使用可扩展的文件存储解决方案. 这样,如果你你的应用程序变得非常受欢迎,你不会面临失去空间的危险。 毕竟,这些上传可以从个人资料图片到家庭照片到PDF报告。 你还希望你的文件存储解决方案是可靠的,这样你就不会失去重要的客户文件,而且很快,所以你的访客不会等待文件传输。

DigitalOcean Spaces可以满足所有这些需求,因为它与亚马逊的S3服务兼容,所以您可以使用新的ActiveStorage(https://guides.rubyonrails.org/active_storage_overview.html)库快速集成到Ruby on Rails应用程序中。

在本指南中,您将配置 Rails 应用程序,以便使用 ActiveStorage 与 DigitalOcean Spaces. 然后,您将通过必要的配置来使用直接上传和 Spaces 内置的 CDN (内容交付网络) 快速实现上传和下载。

当你完成时,你将准备好将DigitalOcean空间的文件存储集成到自己的Rails应用程序中。

前提条件

在您开始本指南之前,您将需要以下内容:

步骤 1 – 获取样本应用程序运行

而不是从头开始构建一个完整的 Rails 应用程序,你会克隆使用 ActiveStorage 的现有 Rails 6 应用程序,并将其修改为使用 DigitalOcean Spaces 作为其图像存储后端。

The Space Puppies application running in a web browser

打开您的终端并用以下命令从 GitHub 克隆应用程序:

1git clone https://github.com/do-community/space-puppies

您将看到类似于此的输出:

1[secondary_label Output]
2Cloning into 'space-puppies'...
3remote: Enumerating objects: 122, done.
4remote: Counting objects: 100% (122/122), done.
5remote: Compressing objects: 100% (103/103), done.
6remote: Total 122 (delta 3), reused 122 (delta 3), pack-reused 0
7Receiving objects: 100% (122/122), 163.17 KiB | 1018.00 KiB/s, done.
8Resolving deltas: 100% (3/3), done.

Space Puppies 使用 Ruby 2.7.1,所以运行rbenv 版本来检查你安装了哪个版本:

1rbenv versions

如果您遵循了先决条件的教程,那么在该列表中只会有 Ruby 2.5.1,您的输出将看起来如下:

1[secondary_label Output]
2*  system
3   2.5.1

如果您没有 Ruby 2.7.1 在该列表中,请使用ruby-build安装它:

1rbenv install 2.7.1

取决于您的机器的速度和操作系统,这可能需要一段时间。

1[secondary_label Output]
2Downloading ruby-2.7.1.tar.bz2...
3-> https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.bz2
4Installing ruby-2.7.1...
5Installed ruby-2.7.1 to /root/.rbenv/versions/2.7.1

转到空间小孩目录:

1cd space-puppies

「rbenv」會在您輸入目錄時自動更改您的 Ruby 版本。

1ruby --version

您将看到类似于以下的输出:

1[secondary_label Output]
2ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

接下来,您将安装应用程序需要运行的 Ruby 宝石和 JavaScript 包,然后您将运行 Space Puppies 应用程序所需的数据库迁移。

使用bundle命令安装所有必要的宝石:

1bundle install

然后,要告诉rbenv关于Bundler安装的任何新二进制,请使用rehash命令:

1rbenv rehash

接下来,告诉yarn安装必要的JavaScript依赖:

1yarn install

现在使用 Rails 内置迁移工具创建数据库方案:

1rails db:migrate

安装所有库和创建数据库后,使用以下命令启动内置的 Web 服务器:

1rails s

<$>[注] 注: 默认情况下,‘rails s’ 仅连接到本地路由地址,这意味着您只能从运行命令的同一台计算机访问服务器。

1rails s -b 0.0.0.0

美元

您的服务器启动,您将收到这样的输出:

 1[secondary_label Output]
 2=> Booting Puma
 3=> Rails 6.0.3.2 application starting in development
 4=> Run `rails server --help` for more startup options
 5Puma starting in single mode...
 6* Version 4.3.5 (ruby 2.7.1-p83), codename: Mysterious Traveller
 7* Min threads: 5, max threads: 5
 8* Environment: development
 9* Listening on tcp://127.0.0.1:3000
10* Listening on tcp://[::1]:3000
11Use Ctrl-C to stop

现在您可以在 Web 浏览器中访问您的应用程序. 如果您在本地计算机上运行该应用程序,请导航到 http://localhost:3000. 如果您在 Droplet 或其他远程服务器上运行,请导航到 http://your_server_ip:3000

您将看到应用程序的界面,但这次没有任何小狗. 尝试通过点击 新小狗按钮添加几张图像。

The Space Puppies application running in a web browser

如果您需要用来测试的狗狗照片,您可以使用 Unsplash 有广泛的列表用于测试,如果您计划在项目中使用这些图像,请查看 Unsplash 许可证

在继续前,让我们走过应用程序的每个层,看看ActiveStorage如何与每个部分工作,这样你就可以对DigitalOcean Spaces做出必要的更改。

首先,看看模型,它代表你在数据库中存储的应用程序中的一个对象. 你会在app/models/puppy.rb中找到Puppy模型. 在文本编辑器中打开这个文件,你会看到这个代码:

1[label app/models/puppy.rb]
2class Puppy < ApplicationRecord
3
4  has_one_attached :photo
5
6end

您将在模型中找到ha_one_attached宏,表示每个Puppy模型实例都附有照片,这些照片将通过ActiveStorage::Attached::One代理存储为ActiveStorage::Blob实例。

关闭这个文件。

在 Rails 应用程序中,控制器负责控制访问数据库模型并响应用户的请求。对于Puppy模型,相应的控制器是PuppiesController,您将在app/controllers/puppies_controller.rb中找到。在编辑器中打开此文件,您将看到以下代码:

 1[label app/controllers/puppies_controller.rb]
 2class PuppiesController < ApplicationController
 3
 4  def index
 5    @puppies = Puppy.with_attached_photo
 6  end
 7
 8  # ... snipped other actions ...
 9
10end

文件中的所有内容都是标准的 Rails 代码,除了with_attached_photo呼叫之外,此呼叫会导致ActiveRecord 在收集Puppy模型列表时加载所有相关的ActiveStorage::Blob关联。

最后,让我们来看看如何生成您的应用程序将发送给用户的浏览器的HTML视图。在这个应用程序中有几个视图,但你会想专注于负责显示上传的狗狗图片的视图。你会找到这个文件在app/views/puppies/_puppy.html.erb。 打开它在你的编辑器中,你会看到这样的代码:

1[label app/views/puppies/_puppy.html.erb]
2<div class="puppy">
3  <%= image_tag puppy.photo.variant(resize_to_fill: [250, 250]) %>
4</div>

ActiveStorage 旨在与 Rails 一起工作,因此您可以使用内置的 image_tag 帮助器生成指向附加照片的 URL,无论它被存储在何处。在这种情况下,应用程序正在使用 变量支持用于图像。当用户首次请求此变量时,ActiveStorage 将自动使用 ImageMagick 通过 image_processing宝石,生成符合我们的要求的修改图像。

<$>[注] 注: 生成图像变量可能很慢,你可能不想让用户等待。

1puppy.photo.variant(resize_to_fill: [250, 250]).processed

当你部署到生产时,在背景工作中进行这种类型的处理是很好的想法. 探索 Active Job并创建一个任务,以便提前生成您的图像 <$>

现在你的应用程序在本地运行,你知道所有代码片段是如何相匹配的。接下来,是时候设置一个新的DigitalOcean Space,这样你就可以将上传移动到云。

步骤 2 – 设置您的数字海洋空间

目前,您的Space Puppies应用程序在本地存储图像,这对于开发或测试是很好的,但您几乎肯定不想在生产中使用这种模式。

在此步骤中,您将创建一个DigitalOcean空间,用于您的应用程序的图像。

登录您的 DigitalOcean 管理控制台,点击右上角的 Create,然后选择 Spaces

选择任何数据中心,并暂时关闭 CDN;您将在稍后返回此处。

請記住,這必須在所有 Spaces 用戶中都是獨一無二的,所以選擇一個獨特的名稱,例如「你的名字 - 空間 - 小孩」。

A screenshot of the DigitalOcean create space form with a name filled in

<$>[警告] **警告:**要小心您代表客户存储的文件。由于错误配置的文件存储,有许多数据泄露和黑客事件(https://threatpost.com/experts-warn-too-often-aws-s3-buckets-are-misconfigured-leak-data/126826/)。

然后你会看到你的品牌新空间。

点击设置选项卡,并记录您的空间终端点,您在配置 Rails 应用程序时需要这样做。

接下来,您将配置 Rails 应用程序以将 ActiveStorage 文件存储在这个空间中. 要安全地做到这一点,您需要创建一个新的 Spaces 访问密钥和秘密。

在左侧的导航中,点击 API,然后点击 Generate New Key在右下角。给你的新密钥一个描述性名称,如开发机器。你的秘密只会出现一次,所以请确保在某个安全的地方复制它一会儿。

A screenshot showing a Spaces access key

在您的 Rails 应用程序中,您需要一个安全的方式来存储该访问令牌,因此您将使用 Rails 的安全身份管理功能。

1EDITOR="nano -w" rails credentials:edit

这会生成主键并启动nano编辑器,以便您可以编辑值。

nano中,将以下内容添加到您的credentials.yml文件中,使用您来自 DigitalOcean 的 API 密钥和秘密:

1[label config/credentials.yml]
2digitalocean:
3  access_key: YOUR_API_ACCESS_KEY
4  secret: YOUR_API_ACCESS_SCRET

保存和关闭文件(‘Ctrl+X’,然后‘Y’,然后‘Enter’),然后Rails将存储一个加密的版本,该版本可以安全地承诺在‘config/credentials.yml.enc’中对源进行控制。

你会看到这样的输出:

 1[secondary_label Output]
 2
 3Adding config/master.key to store the encryption key: RANDOM_HASH_HERE
 4
 5Save this in a password manager your team can access.
 6
 7If you lose the key, no one, including you, can access anything encrypted with it.
 8
 9      create config/master.key
10
11File encrypted and saved.

现在你已经配置了你的身份证,你已经准备好将你的应用指向你的新的空间桶。

在您的编辑器中打开config/storage.yml文件,并在该文件的底部添加以下定义:

1[label config/storage.yml]
2digitalocean:
3  service: S3
4  endpoint: https://your-spaces-endpoint-here
5  access_key_id: <%= Rails.application.credentials.dig(:digitalocean, :access_key) %>
6  secret_access_key: <%= Rails.application.credentials.dig(:digitalocean, :secret) %>
7  bucket: your-space-name-here
8  region: unused

请注意,该服务表示S3而不是Spaces。Spaces具有S3兼容的API,Rails本地支持S3。您的终端点是https://,其次是您先前复制的Space终端,而bucket名称是您在创建时输入的Space名称。

此配置文件将未加密存储,因此,而不是输入您的访问密钥和秘密,您正在引用您刚刚在 `credentials.yml.enc 中安全输入的密码。

<$>[注] :DigitalOcean 使用终端点来指定区域,但您需要提供该区域,否则 ActiveStorage 会投诉。由于 DigitalOcean 会忽略它,您可以将其设置为您想要的任何值。示例代码中的未使用值清楚地表明您不使用它。

保存配置文件。

现在,您需要告诉 Rails 将 Spaces 用于您的文件存储后端,而不是本地文件系统。在编辑器中打开 config/environments/development.rb,并将 config.active_storage.service 条目从 :local: 改为 :digitalocean:

1[label config/environments/development.rb]
2
3  # ...
4
5  # Store uploaded files on the local file system (see config/storage.yml for options).
6  config.active_storage.service = :digitalocean
7
8  # ...

保存文件并离开编辑器. 现在重新启动服务器:

1rails s -b 0.0.0.0

在浏览器中再次访问http://localhost:3000http://your server ip:3000

上传一些图像,应用程序会将它们存储在您的DigitalOcean空间中。您可以通过访问您的DigitalOcean控制台(https://cloud.digitalocean.com/spaces)的空间来查看。

files uploaded to a Space

ActiveStorage 默认使用随机文件名,在保护上传的客户数据时有帮助。

<$>[注] 注: 如果您收到一个Aws::S3::Errors::SignatureDoesNotMatch,这可能意味着您的凭证不正确。 再次运行rails credentials:edit并双重检查它们。

Rails 将您的文件的名称和某些元数据存储为ActiveStorage::Blob记录,您可以通过将附件命名为附件的方法来访问任何记录的ActiveStorage::Blob

在您的终端中启动 Rails 控制台:

1rails c

摘下您上传的最后一张小狗照片的blob:

1> Puppy.last.photo.blob
2#=> => #<ActiveStorage::Blob ...>

您现在有 Rails 应用程序存储上传到可扩展、可靠和负担得起的对象商店。

在接下来的两个步骤中,您将探索两项可选的应用程序添加,这将有助于改善该解决方案的性能和用户的速度。

步骤 3 — 配置空间 CDN (可选)

<$>[注] **注:**对于此步骤,您将需要一个域名服务器指向DigitalOcean的域名服务器。

使用 内容交付网络(CDN)将允许您通过更接近用户的文件的副本来为用户提供更快的文件下载。

您可以使用 [Uptrends CDN Performance Check] (https://www.uptrends.com/tools/cdn-performance-check) 等工具来调查 CDN 性能。 如果您在上一步上传的照片中添加 URL,您将看到情况很快,如果您碰巧在附近,但事情会随着您在地理位置上移动而减慢一些。

1> Puppy.last.photo.service_url

以下是一个示例 Uptrends 报告,其中包含位于旧金山数据中心的文件。 请注意,时间取决于从旧金山的距离而减少。

An example Uptrends CDN Performance Report

您可以通过启用 Spaces 的内置 CDN 来提高速度。在您的 DigitalOcean 控制面板中,请转到 Spaces,然后单击您在步骤 2 中创建的空间的名称。

现在您需要为您的 CDN 选择一个域名,并为该域创建 SSL 证书。您可以使用 Let's Encrypt自动执行此操作。

查找你想要使用的域名,然后选择创建子域的选项。类似于‘cdn.yourdomain.com’是一种标准命名惯例,然后可以给证书一个名字,然后点击生成证书和使用子域按钮。

The filled-in Add Custom Subdomain form

在CDN(内容交付网络)中按下保存按钮。

您的 CDN 現在已啟用,但您需要告訴您的 Rails 應用程式使用它. 在此版本的 Rails 中,此功能並未集成到 ActiveStorage 中,因此您將忽略某些內建的 Rails 框架方法來使其工作。

创建一个名为config/initializers/active_storage_cdn.rb的新 Rails 初始化程序,并添加以下代码来重写 URL:

 1[label config/initializers/active_storage_cdn.rb]
 2Rails.application.config.after_initialize do
 3  require "active_storage/service/s3_service"
 4
 5  module SimpleCDNUrlReplacement
 6    CDN_HOST = "cdn.yourdomain.com"
 7
 8    def url(...)
 9      url = super
10      original_host = "#{bucket.name}.#{client.client.config.endpoint.host}"      
11      url.gsub(original_host, CDN_HOST)
12    end
13  end
14
15  ActiveStorage::Service::S3Service.prepend(SimpleCDNUrlReplacement)
16end

此初始化器每次运行,您的应用程序请求来自一个ActiveStorage::Service::S3Service提供商的URL,然后将原始的非CDN主机替换为您的CDN主机,定义为CDN_HOST常数。

您现在可以重新启动服务器,您会注意到您的每个照片都来自CDN,您不需要重新上传它们,因为DigitalOcean将负责将内容从您设置的数据中心传输到边缘节点。

您可能想将 Uptrends 的 性能检查站点上的照片访问速度与 CDN 之前的速度进行比较。

The Uptrends CDN Performance Report after enabling the CDN

接下来,您将配置应用程序直接从浏览器接收文件。

步骤 4 — 设置直接上传(可选)

您可能想考虑的 ActiveStorage 最后一个功能叫做 直接上传。现在,当您的用户上传文件时,数据会被发送到您的服务器,由 Rails 处理,然后转发到您的空间。

相比之下,直接上传将直接到您的DigitalOcean Space,之间没有Rails服务器跳跃。 要做到这一点,您将启用一些内置的JavaScript,并配置跨源资源共享([CORS]((https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)在您的空间,以便您可以安全地发送请求直接到空间,尽管它们来自不同的地方。

首先,您将为您的空间配置 CORS. 您将使用 `s3cmd' 来执行此操作,如果您尚未配置此功能以与 Spaces 工作,则可以遵循 Setting Up s3cmd 2.x with DigitalOcean Spaces

创建一个名为「cors.xml」的新文件,并将以下代码添加到文件中,以您正在开发的域代替「your_domain」。 如果您正在开发,您将使用「http://localhost:3000」。 如果您正在开发,这将是您的 Droplet IP 地址:

 1[label cors.xml]
 2<CORSConfiguration>
 3 <CORSRule>
 4   <AllowedOrigin>your_domain</AllowedOrigin>
 5   <AllowedMethod>PUT</AllowedMethod>
 6   <AllowedHeader>*</AllowedHeader>
 7   <ExposeHeader>Origin</ExposeHeader>
 8   <ExposeHeader>Content-Type</ExposeHeader>
 9   <ExposeHeader>Content-MD5</ExposeHeader>
10   <ExposeHeader>Content-Disposition</ExposeHeader>
11   <MaxAgeSeconds>3600</MaxAgeSeconds>
12 </CORSRule>
13</CORSConfiguration>

然后,您可以使用「s3cmd」来将此设置为您的空间的 CORS 配置:

1s3cmd setcors cors.xml s3://your-space-name-here

当此命令成功运行时,没有输出,但您可以通过在DigitalOcean控制面板中查看您的空间来检查它是否奏效。选择 Spaces,然后选择您的空间名称,然后选择 Settings 选项卡。

A successful CORS configuration for direct uploads

<$>[注] **注:**目前您需要使用s3cmd而不是控制面板来配置localhost域的CORS,因为控制面板将这些域视为无效。

现在你需要告诉 Rails 使用直接上传,你通过将direct_upload选项传递给file_field帮助器。

 1[label app/views/puppies/new.html.erb]
 2<h2>New Puppy</h2>
 3
 4<%= form_with(model: @puppy) do |f| %>
 5
 6  <div class="form-item">
 7    <%= f.label :photo %>
 8    <%= f.file_field :photo, accept: "image/*", direct_upload: true %>
 9  </div>
10
11  <div class="form-item">
12    <%= f.submit "Create puppy", class: "btn", data: { disable_with: "Creating..." } %>
13  </div>
14
15<% end %>

保存文件并重新启动服务器:

1rails s -b 0.0.0.0

当您上传一张新照片时,您的照片会被直接上传到DigitalOcean Spaces。您可以通过点击创建小狗按钮进行的PUT请求来验证此情况。您可以通过浏览器的网页控制台或阅读 Rails 服务器日志来找到这些请求。

结论

在本文中,您使用 ActiveStorage 修改了基本的 Rails 应用程序,以便在 DigitalOcean Spaces 上存储安全、快速和可扩展的文件。

您现在可以采用此代码和配置,并将其适应自己的 Rails 应用程序。

Published At
Categories with 技术
comments powered by Disqus