跳至内容 跳至搜索

Active Storage Blob

Blob 是一条记录,它包含有关文件和该文件在服务中存放位置的密钥的元数据。Blob 可以通过两种方式创建

  1. 在文件通过 create_and_upload! 上传到服务端之前。该操作需要在服务器上提供一个可倒带的 io,其中包含文件内容。

  2. 在文件通过 create_before_direct_upload! 直接上传到服务端之前。

第一个选项不需要任何客户端 JavaScript 集成,并且可以被任何其他处理文件的后端服务使用。第二个选项更快,因为你没有使用自己的服务器作为上传的暂存点,并且可以与不提供大量磁盘空间的部署(如 Heroku)配合使用。

Blob 旨在使其对特定文件的引用保持不变。你可以在后续传递中更新 Blob 的元数据,但你不应更新密钥或更改上传的文件。如果你需要创建派生文件或以其他方式更改 Blob,只需创建一个新的 Blob 并清除旧的 Blob。

命名空间
方法
A
C
D
F
G
I
K
O
P
S
T
U
V
包含的模块

常量

MINIMUM_TOKEN_LENGTH = 28
 

类公共方法

compose(blobs, filename:, content_type: nil, metadata: nil)

将多个 blob 连接成一个“组合”blob。

# File activestorage/app/models/active_storage/blob.rb, line 150
def compose(blobs, filename:, content_type: nil, metadata: nil)
  raise ActiveRecord::RecordNotSaved, "All blobs must be persisted." if blobs.any?(&:new_record?)

  content_type ||= blobs.pluck(:content_type).compact.first

  new(filename: filename, content_type: content_type, metadata: metadata, byte_size: blobs.sum(&:byte_size)).tap do |combined_blob|
    combined_blob.compose(blobs.pluck(:key))
    combined_blob.save!
  end
end

create_and_upload!(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil)

创建一个新的 blob 实例,然后将给定 io 的内容上传到服务。在上传开始之前将保存 blob 实例,以防止上传由于键冲突而覆盖另一个上传。在提供内容类型时,传递 identify: false 以绕过自动内容类型推断。

# File activestorage/app/models/active_storage/blob.rb, line 101
def create_and_upload!(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil)
  create_after_unfurling!(key: key, io: io, filename: filename, content_type: content_type, metadata: metadata, service_name: service_name, identify: identify).tap do |blob|
    blob.upload_without_unfurling(io)
  end
end

create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil, service_name: nil, record: nil)

返回一个已保存的 blob,将文件上传到服务。此 blob 将指向尚无文件的键。它旨在与客户端上传一起使用,客户端上传将首先创建 blob 以生成用于上传的签名 URL。此签名 URL 指向 blob 生成的键。一旦提交使用直接上传的表单,就可以使用签名 ID 将 blob 与正确的记录关联起来。

# File activestorage/app/models/active_storage/blob.rb, line 112
def create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil, service_name: nil, record: nil)
  create! key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata, service_name: service_name
end

find_signed(id, record: nil, purpose: :blob_id)

你可以使用 blob 的签名 ID 在客户端引用它,而不用担心被篡改。这对于直接上传特别有用,其中客户端需要在表单提交之前引用已创建的 blob。

签名 ID 也用于通过 BlobsController 为 blob 创建稳定的 URL。

# File activestorage/app/models/active_storage/blob.rb, line 74
def find_signed(id, record: nil, purpose: :blob_id)
  super(id, purpose: purpose)
end

find_signed!(id, record: nil, purpose: :blob_id)

find_signed 类似,但如果 signed_id 已过期、目的不匹配、属于其他记录或已被篡改,则会引发 ActiveSupport::MessageVerifier::InvalidSignature 异常。如果有效的签名 ID 找不到记录,它还会引发 ActiveRecord::RecordNotFound 异常。

# File activestorage/app/models/active_storage/blob.rb, line 82
def find_signed!(id, record: nil, purpose: :blob_id)
  super(id, purpose: purpose)
end

generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)

为了防止大小写不敏感的文件系统出现问题,特别是与将索引视为区分大小写的数据库结合使用时,生成的所有 blob 密钥都只包含 36 进制字符,因此将是小写。为了保持与 has_secure_token 使用的 58 进制编码相同或更高的熵,使用的字节数从标准的 24 增加到 28

# File activestorage/app/models/active_storage/blob.rb, line 121
def generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
  SecureRandom.base36(length)
end

unattached

返回未附加到任何记录的 blob。

# File activestorage/app/models/active_storage/blob.rb, line 43
scope :unattached, -> { where.missing(:attachments) }

实例公共方法

attachments

返回关联的 ActiveStorage::Attachment 实例。

# File activestorage/app/models/active_storage/blob.rb, line 37
has_many :attachments

audio?()

如果此 blob 的 content_type 处于音频范围内(如 audio/mpeg),则返回 true。

# File activestorage/app/models/active_storage/blob.rb, line 197
def audio?
  content_type.start_with?("audio")
end

custom_metadata()

# File activestorage/app/models/active_storage/blob.rb, line 183
def custom_metadata
  self[:metadata][:custom] || {}
end

custom_metadata=(metadata)

# File activestorage/app/models/active_storage/blob.rb, line 187
def custom_metadata=(metadata)
  self[:metadata] = self[:metadata].merge(custom: metadata)
end

delete()

删除与此 blob 关联的服务上的文件。仅当 blob 也将被删除时才应执行此操作,否则你基本上将拥有一个死引用。建议在大多数情况下使用 purgepurge_later 方法。

# File activestorage/app/models/active_storage/blob.rb, line 307
def delete
  service.delete(key)
  service.delete_prefixed("variants/#{key}/") if image?
end

download(&block)

下载与此 blob 关联的文件。如果没有给出块,则将整个文件读入内存并返回。对于非常大的文件,这将使用大量的 RAM。如果给出了块,则下载将被流式传输并分块生成。

# File activestorage/app/models/active_storage/blob.rb, line 267
def download(&block)
  service.download key, &block
end

download_chunk(range)

下载与此 blob 关联的文件的一部分。

# File activestorage/app/models/active_storage/blob.rb, line 272
def download_chunk(range)
  service.download_chunk key, range
end

filename()

返回 ActiveStorage::Filename 实例,其中包含可查询基本名称、扩展名和 URL 中可安全使用的已清理文件名的文件名。

# File activestorage/app/models/active_storage/blob.rb, line 179
def filename
  ActiveStorage::Filename.new(self[:filename])
end

image?()

如果此 blob 的 content_type 处于图像范围内(例如 image/png),则返回 true。

# File activestorage/app/models/active_storage/blob.rb, line 192
def image?
  content_type.start_with?("image")
end

key()

返回指向与此 blob 关联的服务上的文件的密钥。该密钥是 Rails 中小写的安全令牌格式。因此,它看起来像:xtapjjcjiudrlk3tmwyjgpuobabd。此密钥不打算直接向用户透露。始终使用 signed_id 或经过验证的密钥形式来引用 blob。

# File activestorage/app/models/active_storage/blob.rb, line 171
def key
  # We can't wait until the record is first saved to have a key for it
  self[:key] ||= self.class.generate_unique_secure_token(length: MINIMUM_TOKEN_LENGTH)
end

open(tmpdir: nil, &block)

将 blob 下载到磁盘上的临时文件中。生成临时文件。

临时文件名的前缀为 ActiveStorage- 和 blob 的 ID。其扩展名与 blob 的扩展名匹配。

默认情况下,临时文件在 Dir.tmpdir 中创建。传递 tmpdir: 以在其他目录中创建

blob.open(tmpdir: "/path/to/tmp") do |file|
  # ...
end

在执行给定块后,临时文件自动关闭并取消链接。

如果下载的数据与 blob 的校验和不匹配,则引发 ActiveStorage::IntegrityError

# File activestorage/app/models/active_storage/blob.rb, line 289
def open(tmpdir: nil, &block)
  service.open(
    key,
    checksum: checksum,
    verify: !composed,
    name: [ "ActiveStorage-#{id}-", filename.extension_with_delimiter ],
    tmpdir: tmpdir,
    &block
  )
end

purge()

销毁 blob 记录,然后删除服务上的文件。这是处理不需要的 blob 的推荐方式。但是,请注意,从服务中删除文件将启动与服务的 HTTP 连接,这可能会很慢或被阻止,因此你不应在事务或回调中使用此方法。请改用 purge_later

# File activestorage/app/models/active_storage/blob.rb, line 315
def purge
  destroy
  delete if previously_persisted?
rescue ActiveRecord::InvalidForeignKey
end

purge_later()

ActiveStorage::PurgeJob 加入队列以调用 purge。这是从事务、Active Record 回调或任何其他实时场景中清除 blob 的推荐方式。

# File activestorage/app/models/active_storage/blob.rb, line 323
def purge_later
  ActiveStorage::PurgeJob.perform_later(self)
end

service()

返回服务实例,该实例可以全局配置或针对每个附件配置

# File activestorage/app/models/active_storage/blob.rb, line 328
def service
  services.fetch(service_name)
end

service_headers_for_direct_upload()

返回一个 Hash,其中包含用于 service_url_for_direct_upload 请求的标头。

# File activestorage/app/models/active_storage/blob.rb, line 227
def service_headers_for_direct_upload
  service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum, custom_metadata: custom_metadata
end

service_url_for_direct_upload(expires_in: ActiveStorage.service_urls_expire_in)

返回一个 URL,可用于在服务上直接上传此 blob 的文件。此 URL 旨在为安全起见而短期存在,并且仅由负责执行上传的客户端 JavaScript 按需生成。

# File activestorage/app/models/active_storage/blob.rb, line 222
def service_url_for_direct_upload(expires_in: ActiveStorage.service_urls_expire_in)
  service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum, custom_metadata: custom_metadata
end

signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)

返回此 blob 的签名 ID,该 ID 适用于在客户端进行引用,而无需担心篡改。

# File activestorage/app/models/active_storage/blob.rb, line 163
def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
  super
end

text?()

如果此 blob 的 content_type 属于文本范围(如 text/plain),则返回 true。

# File activestorage/app/models/active_storage/blob.rb, line 207
def text?
  content_type.start_with?("text")
end

upload(io, identify: true)

io 上传到服务,作为此 blob 的 key。Blob 旨在不可变,因此在已将文件上传到与 blob 匹配后,你不应使用此方法。如果你想创建派生 blob,则应仅基于旧 blob 创建一个新 blob。

在上传之前,我们计算校验和,该校验和将发送到服务以进行传输完整性验证。如果校验和与服务接收到的内容不匹配,则会引发异常。我们还测量 io 的大小,并将其存储在 blob 记录上的 byte_size 中。除非你指定 content_type 并将 identify 传递为 false,否则内容类型将自动从 io 中提取。

通常,你根本不必直接调用此方法。请改用 create_and_upload! 类方法。如果你确实直接使用此方法,请确保在持久化 Blob 上使用它,否则服务上可能会覆盖另一个 blob 的数据。

# File activestorage/app/models/active_storage/blob.rb, line 244
def upload(io, identify: true)
  unfurl io, identify: identify
  upload_without_unfurling io
end

url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)

返回服务中 blob 的 URL。这会为公开文件返回一个永久 URL,并为私有文件返回一个短时 URL。私有文件已签名,不供公开使用。相反,URL 应仅作为从一个稳定的、可能经过身份验证的 URL 中重定向而公开。将 URL 隐藏在重定向之后还允许您在不更新所有 URL 的情况下更改服务。

# File activestorage/app/models/active_storage/blob.rb, line 215
def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)
  service.url key, expires_in: expires_in, filename: ActiveStorage::Filename.wrap(filename || self.filename),
    content_type: content_type_for_serving, disposition: forced_disposition_for_serving || disposition, **options
end

video?()

如果此 blob 的 content_type 处于视频范围内(例如 video/mp4),则返回 true。

# File activestorage/app/models/active_storage/blob.rb, line 202
def video?
  content_type.start_with?("video")
end