Я использую рельсы 6.1 и скрепку 5.0.
Я создал работника для создания ZIP-файла на основе других файлов, хранящихся в S3, а затем загрузил этот ZIP-файл обратно в S3.
Итак, в основном я загружаю файлы с S3, добавляю их в этот временный ZIP-архив, а затем сохраняю ZIP-архив в S3.
class Download < ApplicationRecord
# ...
has_attached_file :file,
path: "#{ENV['DEVELOPER']}/Institucion-:institution_id/Usuario-:user_id/:id/:filename",
content_type: { content_type: ["application/zip"] }
# ...
end
class GenerateZipWorker < ApplicationWorker
def perform(download_id, options = {})
@download = Download.find(download_id)
# ...
@download.file = generate_full_zip
# Here the ZIP Temp file gets a PDF content-type
# @download.file.content_type
# => "application/pdf"
@download.file_file_name = "#{@postulation_template.name[0..100].parameterize}.zip"
@download.save
end
private
def generate_full_zip
temp_zip_file = Tempfile.new("#{@postulation_template.name[0..100].parameterize}.zip")
Zip::File.open(temp_zip_file.path, Zip::File::CREATE) do |zip|
@postulations.each do |postulation|
generate_postulation_file(zip, postulation)
end
end
temp_zip_file.rewind
temp_zip_file
end
def generate_postulation_file(zip, postulation)
root_path = postulation.file_name
zip.get_output_stream(File.join(root_path, postulation.postulation_pdf_file_name)) { |f| f.write postulation.postulation_pdf.s3_object.get.body.string }
end
end
Я попытался вручную установить тип контента перед сохранением файла.
@download.file_content_type = 'application/zip'
Но когда я загружаю файл с панели управления AWS S3, он загружает его в формате PDF, добавляя .pdf в конце имени файла (foo.zip.pdf). Если я удалю расширение PDF, сохранив ZIP-файл, загруженный файл будет работать нормально.
Если я изменю тип контента на панели управления AWS S3 с application/pdf на application/zip, я смогу загрузить без проблем.
1️Как загрузить файл с типом содержимого application/zip?
2️⃣Это временный файл портит content-type?
3️⃣Есть ли лучший способ создать временный ZIP-файл?
Я реорганизовал его с помощью Zip::OutputStream, но все еще получаю тип контента в формате PDF.
Если я правильно понимаю, вы архивируете несколько файлов .pdf. Если это так, то вы имеете дело с неправильным определением типа MIME. О подобных ошибках сообщалось и раньше, например: github.com/mimemagicrb/mimemagic/issues/49 (файл .pdf внутри файла .zip вызывал неправильное определение типа файла). Я предполагаю, что это ошибка в стороннем коде.





Судя по всему, Paperclip неправильно определяет MIME-тип файла.
Вы правильно попытались вручную установить тип контента перед его сохранением, но у вас это не сработало, поскольку Paperclip фактически считывает тип контента из другого объекта.
Если вам интересно, вы можете проверить исходные файлы Paperclip.
В экземпляре Attachment создана переменная экземпляра @file, которая, в свою очередь, имеет переменную экземпляра @content_type, и именно здесь Paperclip считывает тип контента перед загрузкой его в S3.
Итак, что вам нужно исправить, так это сделать следующее:
@download.file_content_type = 'application/zip'
@download.file.instance_variable_get(:"@file").instance_variable_set(:"@content_type", "application/zip")
@download.save
Вы можете проверить это, прежде чем обновлять переменную экземпляра, как я указал, этот код должен вернуть application/pdf (даже если вы обновили @download.file_content_type):
@download.file.instance_variable_get(:"@file").content_type
После того, как вы установите переменную, как я написал, она вернется application/zip, как и ожидалось.
> Papertrail фактически считывает тип контента из другого объекта. Вы имеете в виду скрепку, верно?
Кажется, что способ сделать это скрепкой: @download.file.instance_write(:content_type, 'application/zip')
Да, Papertrail — опечатка (обновлено), мы тоже используем его в нашем проекте :)
попробуйте установить validates_attachment_content_type для :file
class Download < ApplicationRecord
has_attached_file :file
validates_attachment_content_type :file, content_type: /\A.*\z/
end
P.S. рабочий прототип ниже:
######### CONFIGURATION START #########
require 'active_record'
require 'paperclip'
require 'paperclip/railtie'
require 'paperclip/schema'
require 'zip'
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveSupport.on_load :active_record do
Paperclip::Railtie.insert
end
class FakeRails
def initialize(env, root)
@env = env
@root = root
end
attr_accessor :env, :root
def const_defined?(const)
false
end
end
Rails = FakeRails.new('test', Pathname.new('./').join('tmp'))
######### CONFIGURATION END #########
######## APP START #############
class Download < ActiveRecord::Base
connection.create_table table_name, force: true do |t|
t.string :name
end
connection.add_attachment(:downloads, :postulation)
connection.add_attachment(:downloads, :file)
has_attached_file :postulation
validates_attachment_content_type :postulation, content_type: /\Aimage/.*\z/
has_attached_file :file
validates_attachment_content_type :file, content_type: /\A.*\z/
end
##### SEED DATA START ###
@download = Download.create! name: 'bob'
@download.postulation = File.open('./test.jpg')
@download.save!
##### SEED DATA END ###
##### CONTROLLER START ####
temp_zip_file = Tempfile.new('test.zip')
Zip::File.open(temp_zip_file.path, Zip::File::CREATE) do |zip|
Download.all.each do |postulation|
zip.get_output_stream(File.join('./', 'somename.pdf')) { |f| f.write('Some Content') }
end
end
temp_zip_file.rewind
@download.file = temp_zip_file
@download.file_file_name = 'myzip.zip'
@download.save!
puts @download.file_file_name # => myzip.zip
puts @download.file_content_type # => application/zip
##### CONTROLLER END ####
######## APP END #############
P.S. вы можете запустить прототип с помощью
test.rb файлеmkdir public tmpcp ~/Downloads/test.jpg .ruby test.rb
Здесь вам вообще не нужен временный файл. Вместо этого вы можете просто использовать Zip::OutputStream.