使用activestorage的直接上传上传到S3时如何指定前缀?

Posted

技术标签:

【中文标题】使用activestorage的直接上传上传到S3时如何指定前缀?【英文标题】:How to specify a prefix when uploading to S3 using activestorage's direct upload? 【发布时间】:2018-07-01 13:38:16 【问题描述】:

使用标准 S3 配置:

AWS_ACCESS_KEY_ID:        [AWS ID]
AWS_BUCKET:               [bucket name]
AWS_REGION:               [region]
AWS_SECRET_ACCESS_KEY:    [secret]

我可以使用此 Rails 5.2 代码(仅显示相关代码)将文件上传到 S3(使用直接上传):

form.file_field :my_asset, direct_upload: true

这将在提交表单后有效地将我的资产放入我的 S3 存储桶的根目录中。

如何指定前缀(例如“development/”,以便模仿 S3 上的文件夹)?

【问题讨论】:

不确定目前是否可行 - 请查看此处的源代码:github.com/rails/rails/blob/master/activestorage/lib/… 【参考方案1】:

我目前在 S3 上的解决方法(至少在 ActiveStorage 引入为has_one_attachedhas_many_attached 宏传递路径的选项之前)是实现move_to method。

所以我让 ActiveStorage 像现在通常那样(在存储桶顶部)将图像保存到 S3,然后将文件移动到文件夹结构中。

move_to 方法基本上将文件复制到您传递的文件夹结构中,然后删除放在存储桶根目录的文件。这样,您的文件就会出现在您想要的位置。

例如,如果我们要存储驱动程序详细信息:namedrivers_license,请将它们保存为您已经在执行的操作,以便它位于存储桶的顶部。

然后实现以下(我把我的放在一个助手中):

        module DriversHelper

          def restructure_attachment(driver_object, new_structure)

          old_key = driver_object.image.key

          begin
            # Passing S3 Configs
            config = YAML.load_file(Rails.root.join('config', 'storage.yml'))

            s3 = Aws::S3::Resource.new(region: config['amazon']['region'],
                                       credentials: Aws::Credentials.new(config['amazon']['access_key_id'], config['amazon']['secret_access_key']))

            # Fetching the licence's Aws::S3::Object
            old_obj = s3.bucket(config['amazon']['bucket']).object(old_key)

            # Moving the license into the new folder structure
            old_obj.move_to(bucket: config['amazon']['bucket'], key: "#new_structure")


            update_blob_key(driver_object, new_structure)
          rescue => ex
            driver_helper_logger.error("Error restructuring license belonging to driver with id #driver_object.id: #ex.full_message")
          end
          end

          private

          # The new structure becomes the new ActiveStorage Blob key
          def update_blob_key(driver_object, new_key)
            blob = driver_object.image_attachment.blob
            begin
              blob.key = new_key
              blob.save!
            rescue => ex
              driver_helper_logger.error("Error reassigning the new key to the blob object of the driver with id #driver_object.id: #ex.full_message")
            end
          end

          def driver_helper_logger
            @driver_helper_logger ||= Logger.new("#Rails.root/log/driver_helper.log")
          end
        end

更新 blob 键很重要,这样对键的引用就不会返回错误。

如果密钥没有更新,任何试图引用图像的函数都会在它之前的位置(在存储桶的顶部)而不是在它的新位置中查找它。

文件一保存(即在创建操作中),我就从我的控制器调用这个函数,这样即使不是,它看起来也是无缝的。

虽然这可能不是最好的方法,但它现在有效。

仅供参考:根据您提供的示例,new_structure 变量将是 new_structure = "development/#driver_object.image.key"

我希望这会有所帮助! :)

【讨论】:

对于这样的操作需要像这样的变通方法,这是一种耻辱。这也是更改 acl 权限和存储类型所必需的。【参考方案2】:

谢谢你,索尼娅,你的回答。

我尝试了您的解决方案,效果很好,但我遇到了覆盖附件的问题。我在执行此操作时经常遇到 IntegrityError。我认为,这和 checksum 处理可能是 Rails 核心团队不想添加传递路径名功能的原因。这将需要更改 upload 方法的整个逻辑。

ActiveStorage::Attached#create_from_blob 方法,也可以接受 ActiveStorage::Blob 对象。所以我尝试了一种不同的方法:

    使用代表所需文件结构和上传附件的键手动创建 Blob。 使用 ActiveStorage 方法附加创建的 Blob

在我的使用中,解决方案是这样的:

def attach file # method for attaching in the model
  blob_key = destination_pathname(file)
  blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)

  unless blob
    blob = ActiveStorage::Blob.new.tap do |blob|
      blob.filename = blob_key.basename.to_s
      blob.key = blob_key
      blob.upload file
      blob.save!
    end
  end

  # Attach method from ActiveStorage
  self.file.attach blob
end

感谢将完整路径名传递给 Blob 的密钥,我在服务器上收到了所需的文件结构。

【讨论】:

为什么这对我不起作用。我会将 self.file.attach blob 放在哪里?在模型中?我应该如何调用它?【参考方案3】:

抱歉,目前无法实现。我建议创建一个供 Active Storage 独占使用的存储桶。

【讨论】:

我相信有no plans to add this,如果我们需要这个功能,我们需要实现类似索尼娅的回答。 哦……又是你。这不是一个可行的选择,@George Claghorn。所以,这么多人想要这个,我不明白你为什么要取消一个几乎每个竞争库都有能力做的选项。 与@mpowered 相同...我只是不明白您为什么不想添加该功能。至少给我们一个理由,而不是只说“不”。甚至 Carrierwave 也能轻松做到这一点。【参考方案4】:

上面的解决方法还是会报IntegrityError,需要使用File.open(file)。谢谢你的想法。

class History < ApplicationRecord
  has_one_attached :gs_history_file

  def attach(file) # method for attaching in the model
    blob_key = destination_pathname(file)
    blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)
    unless blob
      blob = ActiveStorage::Blob.new.tap do |blob|
        blob.filename = blob_key.to_s
        blob.key = blob_key
        #blob.byte_size = 123123
        #blob.checksum = Time.new.strftime("%Y%m%d-") + Faker::Alphanumeric.alpha(6)
        blob.upload File.open(file)
        blob.save!
      end
    end

    # Attach method from ActiveStorage
    self.gs_history_file.attach blob
  end

  def destination_pathname(file)
    "testing/filename-#Time.now.xlsx"
  end
end

【讨论】:

以上是关于使用activestorage的直接上传上传到S3时如何指定前缀?的主要内容,如果未能解决你的问题,请参考以下文章

从 ActiveStorage 迁移到 Shrine 的 Rake 任务

如何使用 Rails 和 Active Storage 实现 AWS S3 分段上传?

Rails 5.2 + Trix + ActiveStorage

Blueimp 文件上传 - 直接多次上传到 S3

ActiveStorage 上传大型 base64 编码字符串?

s3直接上传限制文件大小和类型