1 Active Storage 是什麼?
Active Storage 有助於將檔案上傳到雲端儲存服務,例如 Amazon S3、Google Cloud Storage 或 Microsoft Azure Storage 並附加這些 檔案到 Active Record 物件。它帶有基於本地磁碟的服務,用於 開發測試,支援映象檔案到下級服務 備份和 migrations。
使用 Active Storage,應用程式可以轉換影象上傳或生成影象 非影象上傳(如 PDF 和影片)的表示,並從中提取元資料 任意檔案。
1.1 要求
Active Storage 的各種功能依賴於 Rails 的第三方軟體 不會安裝,必須單獨安裝:
- libvips 或 ImageMagick v8.6+ 用於影象分析和轉換
- ffmpeg v3.4+ 用於影片/音訊分析和影片 previews
- poppler 或 muPDF 用於 PDF previews
影象分析和轉換也需要 image_processing
gem。在您的 Gemfile
中取消註釋,或者在必要時新增它:
gem "image_processing", ">= 1.2"
提示:與 libvips 相比,ImageMagick 更為人所知且使用範圍更廣。但是,libvips 可以最多快 10 倍並消耗 1/10 的記憶體。對於 JPEG 檔案,這可以透過將 libjpeg-dev
替換為 libjpeg-turbo-dev
來進一步改進,這是 快 2-7 倍。
警告:在安裝和使用第三方軟體之前,請確保您瞭解這樣做的許可含義。特別是 MuPDF,根據 AGPL 獲得許可,並且某些用途需要商業許可。
2 設定
Active Storage 使用應用程式資料庫中名為的三個表
active_storage_blobs
, active_storage_variant_records
和 active_storage_attachments
。建立新的後
應用程式(或將您的應用程序升級到 Rails 5.2),執行
bin/rails active_storage:install
生成一個 migration 來建立這些
表。使用 bin/rails db:migrate
執行 migration。
警告:active_storage_attachments
是一個多型連線表,用於儲存您的 model 的類名。如果您的 model 的類名更改,您將需要在此表上執行 migration 以將基礎 record_type
更新為您的 model 的新類名。
警告:如果您在 models 上使用 UUID 而不是整數作為主要 key,則需要相應地更改生成的 migration 中 active_storage_attachments.record_id
和 active_storage_variant_records.id
的列型別。
在 config/storage.yml
中宣告 Active Storage 服務。對於每項服務,您的
應用程式使用,提供名稱和必要的配置。這個例子
下面聲明瞭三個名為 local
、test
和 amazon
的服務:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
amazon:
service: S3
access_key_id: ""
secret_access_key: ""
bucket: ""
region: "" # e.g. 'us-east-1'
透過設定告訴 Active Storage 使用哪個服務
Rails.application.config.active_storage.service
。因為每個環境都會
可能使用不同的服務,建議在
每個環境的基礎。要使用前面示例中的磁碟服務
開發環境,您將新增以下內容
config/environments/development.rb
:
# 在本地儲存檔案。
config.active_storage.service = :local
要在生產中使用 S3 服務,請將以下內容新增到
config/environments/production.rb
:
# 在 Amazon S3 上儲存檔案。
config.active_storage.service = :amazon
要在測試時使用測試服務,將以下內容新增到
config/environments/test.rb
:
# 將本地檔案系統上上傳的檔案儲存在一個臨時目錄中。
config.active_storage.service = :test
繼續閱讀有關內建服務介面卡的更多資訊(例如
Disk
和 S3
)以及它們需要的配置。
特定於環境的配置檔案將優先:
在生產中,例如,config/storage/production.yml
檔案(如果存在)
將優先於 config/storage.yml
檔案。
建議在bucket名稱中使用Rails.env
,以進一步降低意外破壞生產資料的風險。
amazon:
service: S3
# ...
bucket: your_own_bucket-<%= Rails.env %>
google:
service: GCS
# ...
bucket: your_own_bucket-<%= Rails.env %>
azure:
service: AzureStorage
# ...
container: your_container_name-<%= Rails.env %>
2.1 磁碟服務
在 config/storage.yml
中宣告一個磁碟服務:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
2.2 S3 服務(Amazon S3 和 S3 相容的 API)
要連線到 Amazon S3,請在 config/storage.yml
中宣告一個 S3 服務:
amazon:
service: S3
access_key_id: ""
secret_access_key: ""
region: ""
bucket: ""
(可選)提供客戶端和上傳選項:
amazon:
service: S3
access_key_id: ""
secret_access_key: ""
region: ""
bucket: ""
http_open_timeout: 0
http_read_timeout: 0
retry_limit: 0
upload:
server_side_encryption: "" # 'aws:kms' or 'AES256'
提示:為您的應用程式設定合理的客戶端 HTTP 超時和重試限制。在某些故障情況下,預設的 AWS 客戶端配置可能會導致連線最多保持幾分鐘並導致請求排隊。
將 aws-sdk-s3
gem 新增到您的 Gemfile
:
gem "aws-sdk-s3", require: false
Active Storage 的核心功能需要以下許可權:s3:ListBucket
、s3:PutObject
、s3:GetObject
和 s3:DeleteObject
。 公共訪問 額外需要s3:PutObjectAcl
。如果您配置了其他上傳選項,例如設定 ACL,則可能需要其他許可權。
如果您想使用環境變數、標準 SDK 配置檔案、配置檔案、
IAM 實例配置檔案或任務角色,可以省略 access_key_id
、secret_access_key
、
和 region
keys 在上面的例子中。 S3 服務支援所有
AWS SDK 文件 中描述的身份驗證選項。
要連線到 S3 相容的物件儲存 API,例如 DigitalOcean Spaces,請提供 endpoint
:
digitalocean:
service: S3
endpoint: https://nyc3.digitaloceanspaces.com
access_key_id: ...
secret_access_key: ...
# ...and other options
還有許多其他選項可用。您可以在 AWS S3 客戶端 文件中檢視它們。
2.3 Microsoft Azure 儲存服務
在 config/storage.yml
中宣告 Azure 儲存服務:
azure:
service: AzureStorage
storage_account_name: ""
storage_access_key: ""
container: ""
將 azure-storage-blob
gem 新增到您的 Gemfile
:
gem "azure-storage-blob", require: false
2.4 谷歌雲端儲存服務
在 config/storage.yml
中宣告 Google Cloud Storage 服務:
google:
service: GCS
credentials: <%= Rails.root.join("path/to/keyfile.json") %>
project: ""
bucket: ""
可以選擇提供憑據雜湊而不是 keyfile 路徑:
google:
service: GCS
credentials:
type: "service_account"
project_id: ""
private_ZHTW_30_WTHZ_id: <%= Rails.application.credentials.dig(:gcs, :private_ZHTW_30_WTHZ_id) %>
private_ZHTW_30_WTHZ: <%= Rails.application.credentials.dig(:gcs, :private_ZHTW_30_WTHZ).dump %>
client_email: ""
client_id: ""
auth_uri: "https://accounts.google.com/o/oauth2/auth"
ZHTW_25_WTHZ_uri: "https://accounts.google.com/o/oauth2/ZHTW_25_WTHZ"
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs"
client_x509_cert_url: ""
project: ""
bucket: ""
Optionally provide a Cache-Control metadata to set on uploaded assets:
google:
service: GCS
...
cache_control: "public, max-age=3600"
在簽署 URL 時,可以選擇使用 IAM 而不是 credentials
。如果您使用 Workload Identity 對 GKE 應用程式進行身份驗證,這將非常有用,請參閱 這篇 Google Cloud 部落格文章 瞭解更多資訊。
google:
service: GCS
...
iam: true
可以選擇在簽署 URL 時使用特定的 GSA。使用 IAM 時,將聯絡 元資料伺服器 以獲取 GSA 電子郵件,但此元資料伺服器並不總是存在(例如本地測試),您可能希望使用非預設 GSA。
google:
service: GCS
...
iam: true
gsa_email: "foobar@baz.iam.gserviceaccount.com"
將 google-cloud-storage
gem 新增到您的 Gemfile
:
gem "google-cloud-storage", "~> 1.11", require: false
2.5 映象服務
您可以透過定義映象服務來保持多個服務同步。一面鏡子 服務跨兩個或多個從屬服務複製上傳和刪除。
映象服務的目標在於在 migration 之間臨時使用 生產中的服務。您可以開始映象到新服務,複製 從舊服務到新服務的預先存在的檔案,然後在新服務上全力以赴 服務。
映象不是原子的。上傳成功是有可能的 主要服務並在任何從屬服務上失敗。去之前 全在新服務上,驗證所有檔案都已複製。
如上所述定義要映象的每個服務。參考 定義映象服務時按名稱命名:
s3_west_coast:
service: S3
access_key_id: ""
secret_access_key: ""
region: ""
bucket: ""
s3_east_coast:
service: S3
access_key_id: ""
secret_access_key: ""
region: ""
bucket: ""
production:
service: Mirror
primary: s3_east_coast
mirrors:
- s3_west_coast
儘管所有輔助服務都接收上傳,但始終會處理下載 透過主要服務。
映象服務與直接上傳相容。新檔案直接 上傳到主要服務。當直接上傳的檔案附加到 記錄,後臺作業排隊以將其複製到輔助服務。
2.6 公共訪問
預設情況下,Active Storage 假定對服務的私有訪問。這意味著為 blob 生成簽名的、一次性使用的 URL。如果您希望 Blob 可公開訪問,請在應用程式的 config/storage.yml
中指定 public: true
:
gcs: &gcs
service: GCS
project: ""
private_gcs:
<<: *gcs
credentials: <%= Rails.root.join("path/to/private_keyfile.json") %>
bucket: ""
public_gcs:
<<: *gcs
credentials: <%= Rails.root.join("path/to/public_keyfile.json") %>
bucket: ""
public: true
確保您的儲存桶已正確設定為公共訪問。請參閱有關如何為 Amazon S3、Google Cloud Storage 和 Microsoft Azure 儲存服務啟用公共讀取許可權的文件。 Amazon S3 還要求您擁有 s3:PutObjectAcl
許可權。
將現有應用程式轉換為使用 public: true
時,請確保在切換之前將儲存桶中的每個檔案都更新為可公開讀取的。
3 將檔案附加到記錄
3.1 has_one_attached
has_one_attached
宏在記錄和
檔案。每條記錄可以附加一個檔案。
例如,假設您的應用程式有一個 User
model。如果您希望每個使用者
有一個頭像,定義User
model如下:
class User < ApplicationRecord
has_one_attached :avatar
end
或者,如果您使用的是 Rails 6.0+,則可以像這樣執行 model 生成器命令:
bin/rails generate model User avatar:attachment
您可以使用頭像建立使用者:
<%= form.file_field :avatar %>
class SignupController < ApplicationController
def create
user = User.create!(user_params)
session[:user_id] = user.id
redirect_to root_path
end
private
def user_params
params.require(:user).permit(:email_address, :password, :avatar)
end
end
呼叫 [avatar.attach
][Attached::One#attach] 將頭像附加到現有使用者:
user.avatar.attach(params[:avatar])
呼叫 avatar.attached?
來確定特定使用者是否有頭像:
user.avatar.attached?
在某些情況下,您可能希望覆蓋特定附件的預設服務。
您可以使用 service
選項為每個附件設定特定服務:
class User < ApplicationRecord
has_one_attached :avatar, service: :s3
end
您可以透過在生成的可附加物件上呼叫 variant
方法來設定每個附件的特定變體:
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end
呼叫 avatar.variant(:thumb)
獲取頭像的拇指變體:
<%= image_tag user.avatar.variant(:thumb) %>
3.2 has_many_attached
has_many_attached
宏在記錄之間建立一對多關係
和檔案。每條記錄可以附加許多檔案。
例如,假設您的應用程式有一個 Message
model。如果你想要每個
訊息有很多影象,定義 Message
model 如下:
class Message < ApplicationRecord
has_many_attached :images
end
或者,如果您使用的是 Rails 6.0+,則可以像這樣執行 model 生成器命令:
bin/rails generate model Message images:attachments
您可以建立帶有影象的訊息:
class MessagesController < ApplicationController
def create
message = Message.create!(message_params)
redirect_to message
end
private
def message_params
params.require(:message).permit(:title, :content, images: [])
end
end
呼叫 [images.attach
][Attached::Many#attach] 將新影象新增到現有訊息中:
@message.images.attach(params[:images])
呼叫 images.attached?
來確定特定訊息是否有任何影象:
@message.images.attached?
覆蓋預設服務的方式與 has_one_attached
相同,使用 service
選項:
class Message < ApplicationRecord
has_many_attached :images, service: :s3
end
設定特定變體的方式與 has_one_attached
相同,透過在生成的可附加物件上呼叫 variant
方法:
class Message < ApplicationRecord
has_many_attached :images do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end
3.3 附加檔案/IO 物件
有時您需要附加一個不是透過 HTTP 請求到達的檔案。 例如,您可能希望附加在磁碟上生成或下載的檔案 來自使用者提交的 URL。您可能還想在一個資料夾中附加一個夾具檔案 model 測試。為此,請提供至少包含一個開放 IO 物件的雜湊 和一個檔名:
@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf')
如果可能,還應提供內容型別。 Active Storage 試圖 根據資料確定檔案的內容型別。它退回到內容 如果它不能這樣做,請輸入您提供的型別。
@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')
您可以透過傳入來繞過資料的內容型別推斷
identify: false
和 content_type
。
@message.images.attach(
io: File.open('/path/to/file'),
filename: 'file.pdf',
content_type: 'application/pdf',
identify: false
)
如果您不提供內容型別並且 Active Storage 無法確定 檔案的內容型別自動,預設為 application/octet-stream。
4 刪除檔案
要從 model 中刪除附件,請呼叫 [purge
][Attached::One#purge]
依戀。如果您的應用程式設定為使用 Active Job,則可以完成刪除
在後臺呼叫 [purge_later
][Attached::One#purge_later]。
清除會從儲存服務中刪除 Blob 和檔案。
# 同步銷燬頭像和實際資原始檔。
user.avatar.purge
# 透過 Active Job 非同步銷燬關聯的 models 和實際資原始檔。
user.avatar.purge_later
5 提供檔案
Active Storage 支援兩種檔案服務方式:重定向和代理。
警告:預設情況下,所有 Active Storage controllers 都是公開訪問的。這 生成的 URL 很難猜測,但在設計上是永久的。如果你的檔案 需要更高級別的保護考慮實施 經過身份驗證的 Controllers。
5.1 重定向模式
要為 blob 生成永久 URL,您可以將 blob 傳遞給
url_for
view helper。這會產生一個
帶有 blob signed_id
的 URL
路由到 blob 的 RedirectController
url_for(user.avatar)
# => /rails/active_storage/blobs/:signed_id/my-avatar.png
RedirectController
重定向到實際的服務端點。這
間接將服務 URL 與實際 URL 分離,並允許
例如,映象不同服務中的附件以獲得高可用性。這
重定向的 HTTP 過期時間為 5 分鐘。
要建立下載連結,請使用 rails_blob_{path|url}
helper。使用這個
helper 允許您設定處置。
rails_blob_path(user.avatar, disposition: "attachment")
警告:為了防止 XSS 攻擊,Active Storage 強制使用 Content-Disposition 標頭 對某種檔案進行“附件”。要更改此行為,請參閱 設定 Rails 應用程式 中的可用設定選項。
如果您需要從 controller/view 上下文(背景
作業、Cronjobs 等),您可以像這樣訪問 rails_blob_path
:
Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true)
5.2 代理模式
或者,檔案可以被代理。這意味著您的應用程式伺服器將從儲存服務下載檔案資料以回應請求。這對於從 CDN 提供檔案很有用。
您可以將 Active Storage 設定為預設使用代理:
# config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy
或者,如果您想明確代理特定附件,則可以以 rails_storage_proxy_path
和 rails_storage_proxy_url
的形式使用 URL helpers。
<%= image_tag rails_storage_proxy_path(@user.avatar) %>
5.2.1 在 Active Storage 前面放一個 CDN
此外,為了對 Active Storage 附件使用 CDN,您需要生成具有代理模式的 URL,以便它們由您的應用程式提供服務,並且 CDN 將快取附件而無需任何額外設定。這是開箱即用的,因為預設的 Active Storage 代理 controller 設定了一個 HTTP 標頭,指示 CDN 快取回應。
您還應該確保生成的 URL 使用 CDN 主機而不是您的應用程式主機。有多種方法可以實現這一點,但總的來說,它涉及調整 config/routes.rb
檔案,以便您可以為附件及其變體生成正確的 URL。例如,您可以新增以下內容:
# 設定/路由.rb
direct :cdn_image do |model, options|
if model.respond_to?(:signed_id)
route_for(
:rails_service_blob_proxy,
model.signed_id,
model.filename,
options.merge(host: ENV['CDN_HOST'])
)
else
signed_blob_id = model.blob.signed_id
variation_key = model.variation.key
filename = model.blob.filename
route_for(
:rails_blob_representation_proxy,
signed_blob_id,
variation_key,
filename,
options.merge(host: ENV['CDN_HOST'])
)
end
end
然後生成這樣的路線:
<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>
5.3 已認證的 Controller
預設情況下,所有 Active Storage controllers 都是公開訪問的。生成的
URL 使用普通的 signed_id
,這使得它們難以
猜測但永久。任何知道 blob URL 的人都可以訪問它,
即使您的 ApplicationController
中的 before_action
否則
需要登入。如果您的檔案需要更高級別的保護,您可以
實現你自己的認證 controllers,根據
ActiveStorage::Blobs::RedirectController
,
ActiveStorage::Blobs::ProxyController
,
ActiveStorage::Representations::RedirectController
和
ActiveStorage::Representations::ProxyController
要僅允許帳戶訪問其自己的徽標,您可以執行以下操作:
# 設定/路由.rb
resource :account do
resource :logo
end
# app/controllers/logos_controller.rb
class LogosController < ApplicationController
# Through ApplicationController:
# include Authenticate, SetCurrentAccount
def show
redirect_to Current.account.logo.url
end
end
<%= image_tag account_logo_path %>
然後你可能想要禁用 Active Storage 預設路由:
config.active_storage.draw_routes = false
以防止使用可公開訪問的 URL 訪問檔案。
6 下載檔案
有時您需要在上傳後處理 Blob — 例如,要轉換
將其轉換為不同的格式。使用附件的 download
方法讀取 blob
二進位制資料進入記憶體:
binary = user.avatar.download
您可能希望將 blob 下載到磁碟上的檔案中,以便外部程式(例如
病毒掃描程式或媒體轉碼器)可以對其進行操作。使用附件的
open
方法將 blob 下載到磁碟上的臨時檔案:
message.video.open do |file|
system '/path/to/virus/scanner', file.path
# ...
end
重要的是要知道該檔案在 after_create
callback 中尚不可用,但僅在 after_create_commit
中可用。
7 分析檔案
Active Storage 透過在 Active Job 中排隊作業來分析上傳的檔案。分析的檔案將在元資料雜湊中儲存附加資訊,包括 analyzed: true
。您可以透過呼叫 analyzed?
來檢查是否已分析 blob。
影象分析提供 width
和 height
屬性。影片分析提供這些以及 duration
、angle
、display_aspect_ratio
和 video
和 audio
布林值來指示這些頻道的存在。音訊分析提供 duration
和 bit_rate
屬性。
8 顯示影象、影片和 PDF
Active Storage 支援表示多種檔案。你可以打電話
representation
在附件上顯示影象變體,或
影片或 PDF 的 preview。在呼叫 representation
之前,檢查是否
附件可以透過呼叫 representable?
來表示。一些檔案格式
開箱即用的 Active Storage 無法預先處理 view(例如 Word 文件);如果
representable?
返回 false 您可能想要連結到
檔案代替。
<ul>
<% @message.files.each do |file| %>
<li>
<% if file.representable? %>
<%= image_tag file.representation(resize_to_limit: [100, 100]) %>
<% else %>
<%= link_to rails_blob_path(file, disposition: "attachment") do %>
<%= image_tag "placeholder.png", alt: "Download file" %>
<% end %>
<% end %>
</li>
<% end %>
</ul>
在內部,representation
為影象呼叫 variant
,為影象呼叫 preview
previewable 檔案。您也可以直接呼叫這些方法。
8.1 延遲載入與立即載入
預設情況下,Active Storage 將延遲處理表示。這段程式碼:
image_tag file.representation(resize_to_limit: [100, 100])
將生成一個 <img>
標籤,其中 src
指向
ActiveStorage::Representations::RedirectController
。瀏覽器會
向該 controller 發出請求,它將返回一個 302
重定向到
遠端服務上的檔案(或在[代理模式](#proxy-mode)下,返回檔案
內容)。延遲載入檔案允許以下功能
單次使用 URL 在不減慢初始頁面載入速度的情況下工作。
這適用於大多數情況。
如果你想立即為圖片生成 URL,你可以呼叫 .processed.url
:
image_tag file.representation(resize_to_limit: [100, 100]).processed.url
Active Storage 變體跟蹤器透過儲存一個
如果之前處理過請求的表示,則在資料庫中記錄。
因此,上面的程式碼只會對遠端服務(例如 S3)進行 API 呼叫
一次,一旦儲存了一個變體,就會使用它。變體跟蹤器執行
自動,但可以透過 config.active_storage.track_variants
禁用。
如果您在頁面上渲染大量影象,則上述示例可能會導致
在 N+1 個查詢中載入所有變體記錄。為了避免這些 N+1 查詢,
在 ActiveStorage::Attachment
上使用命名範圍。
message.images.with_all_variant_records.each do |file|
image_tag file.representation(resize_to_limit: [100, 100]).processed.url
end
8.2 轉換影象
轉換影象允許您以您選擇的尺寸顯示影象。
要建立影象的變體,請在附件上呼叫 variant
。你
可以將變體處理器支援的任何轉換傳遞給方法。
當瀏覽器命中變體 URL 時,Active Storage 會懶惰變換
將原始 blob 轉換為指定格式並重定向到其新服務
地點。
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
如果請求變體,Active Storage 將自動應用 轉換取決於影象的格式:
可變的內容型別(由
config.active_storage.variable_content_types
規定) 並且不考慮網路影象(由config.active_storage.web_image_content_types
規定), 將被轉換為 PNG。如果未指定
quality
,則將使用變體處理器的格式預設質量。
Active Storage 的預設處理器是 MiniMagick,但您也可以使用
貴賓。要切換到 Vips,請將以下內容新增到 config/application.rb
:
config.active_storage.variant_processor = :vips
兩個處理器不完全相容,因此在遷移現有應用程式時 使用 MiniMagick 到 Vips,如果使用格式選項,則必須進行一些更改 具體的:
<!-- MiniMagick -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
<!-- Vips -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
8.3 Previewing 檔案
一些非影象檔案可以是 previewed:也就是說,它們可以作為影象呈現。
例如,影片檔案可以透過提取其第一幀來 previewed。在......之外
盒子,Active Storage 支援 previewing 影片和 PDF 文件。去創造
一個指向延遲生成的 preview 的連結,使用附件的 preview
方法:
<%= image_tag message.video.preview(resize_to_limit: [100, 100]) %>
要新增對其他格式的支援,請新增您自己的 previewer。見
ActiveStorage::Preview
文件瞭解更多資訊。
9 直接上傳
Active Storage,自帶JavaScript庫,支援上傳 直接從客戶端到雲端。
9.1 用法
-
在應用程式的 JavaScript 包中包含
activestorage.js
。使用 asset pipeline:
//= require activestorage
使用 npm 包:
import * as ActiveStorage from "@rails/activestorage" ActiveStorage.start()
-
將
direct_upload: true
新增到您的 檔案欄位:<%= form.file_field :attachments, multiple: true, direct_upload: true %>
或者,如果您沒有使用
FormBuilder
,請直接新增資料屬性:<input type=file data-direct-upload-url="<%= rails_direct_uploads_url %>" />
3、在第三方儲存服務上設定CORS,允許直接上傳請求。
- 就是這樣!上傳在表單提交時開始。
9.2 跨域資源共享(CORS)設定
要直接上傳到第三方服務,您需要設定該服務以允許來自您的應用的跨源請求。查閱 CORS 文件以瞭解您的服務:
注意允許:
- 訪問您的應用程式的所有來源
-
PUT
請求方法 - 以下標題:
Origin
Content-Type
Content-MD5
-
Content-Disposition
(Azure 儲存除外) -
x-ms-blob-content-disposition
(僅適用於 Azure 儲存) -
x-ms-blob-type
(僅適用於 Azure 儲存) -
Cache-Control
(對於GCS,僅當設定了cache_control
)
磁碟服務不需要 CORS 設定,因為它共享您的應用程式的來源。
9.2.1 示例:S3 CORS 設定
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"PUT"
],
"AllowedOrigins": [
"https://www.example.com"
],
"ExposeHeaders": [
"Origin",
"Content-Type",
"Content-MD5",
"Content-Disposition"
],
"MaxAgeSeconds": 3600
}
]
9.2.2 示例:Google Cloud Storage CORS 設定
[
{
"origin": ["https://www.example.com"],
"method": ["PUT"],
"responseHeader": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],
"maxAgeSeconds": 3600
}
]
9.2.3 示例:Azure 儲存 CORS 設定
<Cors>
<CorsRule>
<AllowedOrigins>https://www.example.com</AllowedOrigins>
<AllowedMethods>PUT</AllowedMethods>
<AllowedHeaders>Origin, Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-type</AllowedHeaders>
<MaxAgeInSeconds>3600</MaxAgeInSeconds>
</CorsRule>
</Cors>
9.3 直接上傳 JavaScript 事件
活動名稱 | 活動目標 | 事件資料 (event.detail ) |
說明 |
---|---|---|---|
direct-uploads:start |
<form> |
無 | 已提交包含用於直接上傳欄位的檔案的表單。 |
direct-upload:initialize |
<input> |
{id, file} |
提交表單後為每個檔案分派。 |
direct-upload:start |
<input> |
{id, file} |
開始直接上傳。 |
direct-upload:before-blob-request |
<input> |
{id, file, xhr} |
在向您的應用程式請求直接上傳元資料之前。 |
direct-upload:before-storage-request |
<input> |
{id, file, xhr} |
在請求儲存檔案之前。 |
direct-upload:progress |
<input> |
{id, file, progress} |
隨著儲存檔案的請求的進展。 |
direct-upload:error |
<input> |
{id, file, error} |
發生錯誤。除非取消此事件,否則將顯示 alert 。 |
direct-upload:end |
<input> |
{id, file} |
直接上傳已結束。 |
direct-uploads:end |
<form> |
無 | 所有直接上傳已結束。 |
9.4 示例
您可以使用這些事件來顯示上傳進度。
以表格形式顯示上傳的檔案:
// direct_uploads.js
addEventListener("direct-upload:initialize", event => {
const { target, detail } = event
const { id, file } = detail
target.insertAdjacentHTML("beforebegin", `
<div id="direct-upload-${id}" class="direct-upload direct-upload--pending">
<div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div>
<span class="direct-upload__filename"></span>
</div>
`)
target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name
})
addEventListener("direct-upload:start", event => {
const { id } = event.detail
const element = document.getElementById(`direct-upload-${id}`)
element.classList.remove("direct-upload--pending")
})
addEventListener("direct-upload:progress", event => {
const { id, progress } = event.detail
const progressElement = document.getElementById(`direct-upload-progress-${id}`)
progressElement.style.width = `${progress}%`
})
addEventListener("direct-upload:error", event => {
event.preventDefault()
const { id, error } = event.detail
const element = document.getElementById(`direct-upload-${id}`)
element.classList.add("direct-upload--error")
element.setAttribute("title", error)
})
addEventListener("direct-upload:end", event => {
const { id } = event.detail
const element = document.getElementById(`direct-upload-${id}`)
element.classList.add("direct-upload--complete")
})
新增樣式:
/* direct_uploads.css */
.direct-upload {
display: inline-block;
position: relative;
padding: 2px 4px;
margin: 0 3px 3px 0;
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 3px;
font-size: 11px;
line-height: 13px;
}
.direct-upload--pending {
opacity: 0.6;
}
.direct-upload__progress {
position: absolute;
top: 0;
left: 0;
bottom: 0;
opacity: 0.2;
background: #0076ff;
transition: width 120ms ease-out, opacity 60ms 60ms ease-in;
transform: translate3d(0, 0, 0);
}
.direct-upload--complete .direct-upload__progress {
opacity: 0.4;
}
.direct-upload--error {
border-color: red;
}
input[type=file][data-direct-upload-url][disabled] {
display: none;
}
9.5 與庫或框架整合
如果您想使用 JavaScript 框架中的直接上傳功能,或者
您想整合自定義拖放解決方案,您可以使用
DirectUpload
類用於此目的。從您的圖書館收到檔案後
選擇實例化 DirectUpload 並呼叫其建立方法。建立鏡頭
上傳完成時呼叫的 callback。
import { DirectUpload } from "@rails/activestorage"
const input = document.querySelector('input[type=file]')
// Bind to file drop - use the ondrop on a parent element or use a
// library like Dropzone
const onDrop = (event) => {
event.preventDefault()
const files = event.dataTransfer.files;
Array.from(files).forEach(file => uploadFile(file))
}
// Bind to normal file selection
input.addEventListener('change', (event) => {
Array.from(input.files).forEach(file => uploadFile(file))
// you might clear the selected files from the input
input.value = null
})
const uploadFile = (file) => {
// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)
upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form with a
// value of blob.signed_id so that the blob ids will be
// transmitted in the normal upload flow
const hiddenField = document.createElement('input')
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("value", blob.signed_id);
hiddenField.name = input.name
document.querySelector('form').appendChild(hiddenField)
}
})
}
如果需要跟蹤檔案上傳的進度,可以透過第三個
DirectUpload
建構函式的引數。在上傳過程中,DirectUpload
將呼叫物件的 directUploadWillStoreFileWithXHR
方法。然後你可以
在 XHR 上繫結您自己的進度處理程式。
import { DirectUpload } from "@rails/activestorage"
class Uploader {
constructor(file, url) {
this.upload = new DirectUpload(this.file, this.url, this)
}
upload(file) {
this.upload.create((error, blob) => {
if (error) {
// Handle the error
} else {
// Add an appropriately-named hidden input to the form
// with a value of blob.signed_id
}
})
}
directUploadWillStoreFileWithXHR(request) {
request.upload.addEventListener("progress",
event => this.directUploadDidProgress(event))
}
directUploadDidProgress(event) {
// Use event.loaded and event.total to update the progress bar
}
}
10 測試
使用 fixture_file_upload
在整合或 controller 測試中測試上傳檔案。
Rails 像處理任何其他引數一樣處理檔案。
class SignupController < ActionDispatch::IntegrationTest
test "can sign up" do
post signup_path, params: {
name: "David",
avatar: fixture_file_upload("david.png", "image/png")
}
user = User.order(:created_at).last
assert user.avatar.attached?
end
end
10.1 丟棄測試期間建立的檔案
10.1.1 系統測試
系統測試透過回滾 transaction 來清理測試資料。因為destroy
永遠不會在物件上呼叫,因此永遠不會清除附加的檔案。如果你
要清除檔案,您可以在 after_teardown
callback 中進行。正在做
此處確保測試期間建立的所有連線都完整且
您不會收到來自 Active Storage 的錯誤訊息,說它找不到檔案。
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# ...
def after_teardown
super
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
# ...
end
如果你使用 [parallel tests][] 和 DiskService
,你應該設定每個程序使用它自己的
Active Storage 的資料夾。這樣,teardown
callback 只會從相關程序中刪除檔案'
測試。
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
# ...
parallelize_setup do |i|
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
end
# ...
end
如果您的系統測試驗證了 model 與附件的刪除,並且您 使用 Active Job,將您的測試環境設定為使用內聯佇列介面卡,以便 清除作業會立即執行,而不是在未來的未知時間執行。
# 使用內聯作業處理使事情立即發生
config.active_job.queue_adapter = :inline
10.1.2 整合測試
與系統測試類似,整合測試期間上傳的檔案不會被
自動清理。如果要清除檔案,可以在
teardown
callback。
class ActionDispatch::IntegrationTest
def after_teardown
super
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
end
如果您使用 [parallel tests][] 和 Disk 服務,您應該設定每個程序使用它自己的
Active Storage 的資料夾。這樣,teardown
callback 只會從相關程序中刪除檔案'
測試。
class ActionDispatch::IntegrationTest
parallelize_setup do |i|
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
end
end
10.2 為燈具新增附件
您可以將附件新增到現有的 [裝置][]。首先,您需要建立一個單獨的儲存服務:
# 設定/儲存.yml
test_fixtures:
service: Disk
root: <%= Rails.root.join("tmp/storage_fixtures") %>
這告訴 Active Storage 將夾具檔案“上傳”到哪裡,因此它應該是一個臨時目錄。透過製作
與您的正常 test
服務不同的目錄,您可以將夾具檔案與上傳的檔案分開
測試。
接下來,為 Active Storage 類建立夾具檔案:
# active_storage/attachments.yml
david_avatar:
name: avatar
record: david (User)
blob: david_avatar_blob
# active_storage/blobs.yml
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>
然後在你的fixtures 目錄下放一個檔案(預設路徑是test/fixtures/files
),檔名對應。
有關更多資訊,請參閱 ActiveStorage::FixtureSet
文件。
設定完所有內容後,您將能夠訪問測試中的附件:
class UserTest < ActiveSupport::TestCase
def test_avatar
avatar = users(:david).avatar
assert avatar.attached?
assert_not_nil avatar.download
assert_equal 1000, avatar.byte_size
end
end
10.2.1 清理夾具
雖然在測試中上傳的檔案被清理在每個測試結束時, 你只需要清理一次夾具檔案:當你所有的測試完成時。
如果您使用並行測試,請呼叫 parallelize_teardown
:
class ActiveSupport::TestCase
# ...
parallelize_teardown do |i|
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end
# ...
end
如果您沒有執行並行測試,請使用 Minitest.after_run
或等效的測試
框架(例如 RSpec 的 after(:suite)
):
# test_helper.rb
Minitest.after_run do
FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end
11 實施對其他雲服務的支援
如果您需要支援除這些之外的雲服務,您將需要
實施服務。每項服務延伸
ActiveStorage::Service
透過實施將檔案上傳和下載到雲所需的方法。
12 清除未附加的上傳
在某些情況下,檔案已上傳但從未附加到記錄。使用 直接上傳 時可能會發生這種情況。您可以使用 unattached scope 查詢未附加的記錄。下面是一個使用 自定義 rake 任務 的示例。
namespace :active_storage do
desc "Purges unattached Active Storage blobs. Run regularly."
task purge_unattached: :environment do
ActiveStorage::Blob.unattached.where("active_storage_blobs.created_at <= ?", 2.days.ago).find_each(&:purge_later)
end
end
警告:由 ActiveStorage::Blob.unattached
生成的查詢可能很慢,並且可能會對具有較大資料庫的應用程式造成破壞。
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。