S3 保存旧 url,更改回形针配置,将新 url 设置为旧

S3 save old url, change paperclip config, set new url as old

事情是这样的:目前我们的文件,当用户下载它们时,名称类似于 897123uiojdkashdu182uiej.pdf。我需要将其更改为 file-name.pdf. 从逻辑上讲,我会从这里更改 paperclip.rb 配置:

Paperclip::Attachment.default_options.update({
                                               path: '/:hash.:extension',
                                               hash_secret: Rails.application.secrets.secret_key_base
                                             })

对此:

Paperclip::Attachment.default_options.update({
                                               path: "/attachment/#{SecureRandom.urlsafe_base64(64)}/:filename",
                                               hash_secret: Rails.application.secrets.secret_key_base
                                             })

效果很好,文件名很棒。但是,由于路径更改,旧文件现在无法访问。所以我想出了以下决定 首先,我做了一个 rake 任务,它将旧路径存储在数据库中:

namespace :paperclip do
  desc "Set old urls for attachments"
  task :update_old_urls => :environment do

    Asset.find_each do |asset|
      if asset.attachment
        attachment_url = asset.attachment.try!(:url)
        file_url = "https:#{attachment_url}"

        puts "Set old url asset attachment #{asset.id} - #{file_url}"
        asset.update(old_url: file_url)
      else
        puts "No attachment found in asset #{asset.id}"
      end
    end
  end
end

现在 asset.old_url 存储文件的当前 url。然后我去更改配置,使文件无法访问。 现在是时候执行新的 rake 任务了:

require 'uri'
require 'open-uri'

namespace :paperclip do
  desc "Recreate attachments and save them to new destination"
  task :move_attachments => :environment do

    Asset.find_each do |asset|
      unless asset.old_url.blank?
        url = asset.old_url
        filename = File.basename(asset.attachment.path)
        file = File.new("#{Rails.root}/tmp/#{filename}", "wb")
        file.write(open(url).read)

        if File.exists? file
          puts "Re-saving asset attachment #{asset.id} - #{filename}"
          asset.attachment = file
          asset.save
          # if there are multiple styles, you want to recreate them :
          asset.attachment.reprocess!
          file.close
        else
          puts "Missing file attachment #{asset.id} - #{filename}"
        end
        File.delete(file)
      end
    end
  end
end

但我的计划根本行不通,我无法访问这些文件,而且 asset.url 仍然不等于 asset.old_url

非常感谢帮助!

使用 S3,您可以将“保存时的文件名”设置为 header。具体来说,用户将进入 url https://foo.bar.com/mangled/path/some/weird/hash/whatever?options 并且当浏览器提供保存时,您可以控制文件名(而不是 url)。

这个技巧依赖于浏览器从响应中读取 Content-Disposition header,如果它读取 Content-Disposition: attachment; filename="filename.jpg" 它将保存(或要求用户另存为)filename.jpg,独立于原来的URL.

您可以通过向 URL 添加一个参数或在文件上设置元数据来强制 S3 添加此 header。

前者可以通过将其传递给url方法来完成:

has_attached_file :attachment, 
                  s3_url_options: ->(instance) { 
                    {response_content_disposition: "attachment; filename=\"#{instance.filename}\""}
                  }

检查 https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/storage/s3.rb#L221-L225 以获取相关源代码。

后者可以通过回形针批量完成(您还应该将其配置为在新上传时执行)。也需要很长时间!!

Asset.find_each do |asset|
  next unless asset.attachment
  
  s3_object = asset.attachment.s3_object
  s3_object.copy_to(
    s3_object,
    metadata_directive: 'REPLACE',
    content_disposition: "attachment; filename=\"#{asset.filename}\")"
  )
end
# for new uploads
has_attached_file :attachment, 
                  s3_headers: ->(att) {
                    {content_disposition: "attachment; filename=\"#{att.model.filename}\""}
                  }