如何使用 S3 的 Rails Active Storage 检索附件 url

How to retrieve attachment url with Rails Active Storage with S3

rails version 5.2

我有一个场景,我需要使用 Amazon S3 存储访问 Rails Active Storage 的 public URL,以使用 Sidekiq 后台作业制作一个 zip 文件。

我在获取实际文件时遇到困难 URL。我试过 rails_blob_url 但它给了我以下

http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBZUk9IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9598613be650942d1ee4382a44dad679a80d2d3b/sample.pdf

如何通过 Sidekiq 访问真实文件 URL?

storage.yml

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

development:
  service: S3
  access_key_id: 'xxxxx'
  secret_access_key: 'xxxxx'
  region: 'xxxxx'
  bucket: 'xxxxx'

development.rb

  config.active_storage.service = :development

我可以在 Web 界面上访问这些内容,但不能在 Sidekiq 中访问

使用ActiveStorage::Blob#service_url。例如,假设一个 Post 模型有一个附加的 header_image:

@post.header_image.service_url

更新:Rails6.1

因为 Rails 6.1 ActiveStorage::Blob#service_url 已弃用 ActiveStorage::Blob#url.

那么,现在

@post.header_image.url

是必经之路。

来源:

有点晚了,但是你可以得到 public URL 也像这样(假设一个 Post 模型有一个附加的 header_image 如上例):

@post.header_image.service.send(:object_for, @post.header_image.key).public_url

更新时间 2020-04-06

  1. 您需要确保使用 public ACL 保存文档(例如将默认设置为 public)

  2. rails_blob_url 也可用。请求将由 rails 提供服务,但是,这些请求可能会很慢,因为每个请求都需要生成私有 URL。 (仅供参考:在控制器外部你可以生成 URL 也像这样:Rails.application.routes.url_helpers.rails_blob_url(@post, only_path: true)

我的用例是将图像上传到 S3,S3 可以 public 访问存储桶中的所有图像,因此无论请求来源或 URL 是否过期,作业都可以稍后提取它们。我就是这样做的。 (Rails 5.2.2)

首先,新的 S3 bucked 的默认设置是将所有内容保密,所以要解决这个问题有 2 个步骤。

  1. 添加通配符存储桶策略。在 AWS S3 >> 您的存储桶 >> 权限 >> 存储桶策略
{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "AllowPublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}
  1. 在您的存储桶 >> 权限 >> Public 访问设置中,确保 Block public and cross-account access if bucket has public policies 设置为 false

现在,您只需使用 url 中的 blob.key 即可访问 S3 存储桶中的任何内容。不再需要过期的代币。

其次,要生成 URL,您可以使用@Christian_Butzke 的解决方案:@post.header_image.service.send(:object_for, @post.header_image.key).public_url

但是,要知道 object_for 是 service 上的一个私有方法,如果用 public_send 调用会报错。因此,另一种选择是使用每个@George_Claghorn service_url 并删除任何带有 url&.split("?")&.first 的参数。如前所述,这可能会在本地主机中失败并出现主机丢失错误。

这是我的解决方案或存储在 S3 上的可上传 "logo",默认情况下制作 public:

#/models/company.rb
has_one_attached :logo
def public_logo_url
    if self.logo&.attachment
        if Rails.env.development?
            self.logo_url = Rails.application.routes.url_helpers.rails_blob_url(self.logo, only_path: true)
        else
            self.logo_url = self.logo&.service_url&.split("?")&.first
        end
    end
    #set a default lazily
    self.logo_url ||= ActionController::Base.helpers.asset_path("default_company_icon.png")
end

欣赏^_^

如果您需要所有文件 public,那么您必须 public 上传:

在文件 config/storage.yml

amazon:
  service: S3
  access_key_id: zzz
  secret_access_key: zzz
  region: zzz
  bucket: zzz
  upload:
    acl: "public-read"

代码中

attachment = ActiveStorage::Attachment.find(90)
attachment.blob.service_url # returns large URI
attachment.blob.service_url.sub(/\?.*/, '') # remove query params

它将return类似于: “https://foo.s3.amazonaws.com/bar/buz/2yoQMbt4NvY3gXb5x1YcHpRa

由于上面的配置,它 public 可读。

使用 service_url 方法结合对参数进行条带化以获得 public URL 是个好主意,感谢@genkilabs 和@Aivils_Štoss!

但是,如果您对大量文件使用此方法,则可能会涉及缩放问题,例如。如果您要显示附有文件的记录列表。对于 each 调用 service_url,您将在日志中看到如下内容:

DEBUG -- : [8df9220c-e8c9-45b7-a1ee-b746e623ca1b]   S3 Storage (1.4ms) Generated URL for file at key: ...

您也不能急于加载这些调用,因此您可能需要大量调用 S3 存储来为您显示的每条记录生成这些 URLs。

我通过创建一个像这样的 Presenter 解决了这个问题:

class FilePresenter < SimpleDelegator
  def initialize(obj)
    super
  end

  def public_url
    return dev_url if Rails.env.development? || Rails.env.test? || assest_host.nil?

    "#{assest_host}/#{key}"
  end

  private

  def dev_url
    Rails.application.routes.url_helpers.rails_blob_url(self, only_path: true)
  end

  def assest_host
    @assest_host ||= ENV['ASSET_HOST']
  end
end

然后我设置了一个 ENV 变量 ASSET_HOST

https://<your_app_bucket>.s3.<your_region>.amazonaws.com

然后当我显示图像或只显示文件时 link,我这样做:

<%= link_to(image_tag(company.display_logo),
    FilePresenter.new(company.logo).public_url, target: "_blank", rel:"noopener") %>

<a href=<%= FilePresenter.new(my_record.file).public_url %> 
   target="_blank" rel="noopener"><%= my_record.file.filename %></a>

请注意,您仍然需要对图像使用 display_logo,以便在您使用它们时它会访问变体。

此外,这一切都是基于按照上面的@genkilabs 步骤 #2 设置我的 AWS 存储桶 public,并按照 @Aivils_Štoss 将 upload: acl: "public-read" 设置添加到我的 'config/storage.yml' !的建议。

如果有人发现此方法有任何问题或陷阱,请告诉我!这似乎对我很有用,因为它允许我显示 public URL 但不需要为每条记录访问 S3 存储来生成 URL.

我在运行时遇到了一些问题。以为我会为后代记录它们。

  • 在 rails 6.0 中使用 @post.header_image.service_url
  • 在 rails >= 6.1 中,按照 的建议使用 @post.header_image.url

我收到 this 错误:

error: uninitialized constant Analyzable

这是 rails 6.0 中的一个奇怪错误,它是 fixed 将其放置在 config/application.rb

config.autoloader = :classic

然后我看到 this 错误:

URI::InvalidURIError (bad URI(is not URI?): nil) Active Storage service_url

只需将 this 添加到您的 application_controller.rb

即可修复它
include ActiveStorage::SetCurrent

现在@post.image.blob.service_url 之类的东西将如您所愿地工作=)

另请参阅 rails 活动存储中的 public access。这是在 Rails 6.1.

中引入的

在应用的 config/storage.yml 中指定 public: true。 Public 服务将永远 return 永久 URL。