使用 RubyZip 生成文件并以 zip 格式下载

Posted

技术标签:

【中文标题】使用 RubyZip 生成文件并以 zip 格式下载【英文标题】:Generate files and download as zip using RubyZip 【发布时间】:2019-02-05 17:43:15 【问题描述】:

对于我的 Ruby on Rails 项目(Rails 版本 5.1.2),我正在生成图像文件 (png) 并使用 RubyZip gem 将它们下载为 zip 文件。

图像文件不存储在任何目录中。我有一个名为 Attachment 的模型。每个附件都有一个属性 image_string,它是图像的 base64 字符串。您可以使用 image_tag(src = "data:image/jpeg;base64, #attachment.image_string", style: "border-radius: 0;") 之类的标签显示图像

对于多个图像,我想为每个图像创建临时文件而不将它们存储在任何地方并将这些图像下载为 zip 文件。

我现在的代码:

def bulk_download
  require('zip')
  ::Zip::File.open("/tmp/mms.zip", Zip::File::CREATE) do |zipfile|
    Attachment.all.each do |attachment|
      image_file = Tempfile.new("#attachment.created_at.in_time_zone.png")
      image_file.write(attachment.image_string)
      zipfile.add("#attachment.created_at.in_time_zone.png", image_file.path)
    end
  end
  send_file "/tmp/mms.zip", type: 'application/zip', disposition: 'attachment', filename: "my_archive.zip"
  respond_to do |format |
    format.all  head :ok, content_type: "text/html" 
  end
end

但是下载的zipfile里面没有文件,大小为0字节。 提前致谢。

【问题讨论】:

嘿@jl118,我的回答对你有用吗? 【参考方案1】:

您应该像这样关闭并取消链接 zip 文件:

require('zip')

class SomeController < ApplicationController
  # ...

  def bulk_download
    filename = 'my_archive.zip'
    temp_file = Tempfile.new(filename)

    begin
      Zip::OutputStream.open(temp_file)  |zos| 

      Zip::File.open(temp_file.path, Zip::File::CREATE) do |zip|
        Attachment.all.each do |attachment|
          image_file = Tempfile.new("#attachment.created_at.in_time_zone.png")
          image_file.write(attachment.image_string)
          zipfile.add("#attachment.created_at.in_time_zone.png", image_file.path)
        end
      end

      zip_data = File.read(temp_file.path)
      send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)
    ensure # important steps below
      temp_file.close
      temp_file.unlink
    end
  end
end

这是我用作此代码来源的一篇很好的博客文章:https://thinkingeek.com/2013/11/15/create-temporary-zip-file-send-response-rails/

此外,最好将所有库要求放在文件顶部(即require('zip'))。

【讨论】:

【参考方案2】:

公认的解决方案确实是正确的。但是,我将扩展已经提供的解决方案以使其与ActiveStorage 附件一起使用。 在使用公认的解决方案时,我发现image_string 方法不适用于ActiveStorage 附件,并引发类似这样的错误

NoMethodError - undefined method `image_string' for #<ActiveStorage::Attached::One:0x00007f78cc686298>

假设我们有一个名为 Product 的 rails 模型,其 ActiveStorage 属性名为 attachment

class Product < ApplicationRecord
  has_one_attached :attachment
end

为了让 ActiveStorage 附件能够正常工作,我们需要如下更新代码

begin
  Zip::OutputStream.open(temp_file)  |zos| 

  Zip::File.open(temp_file.path, Zip::File::CREATE) do |zip|
    Product.all.each do |product|
      image_file = Tempfile.new("#product.attachment.created_at.in_time_zone.png")
      
    # image_file.write(product.attachment.image_string) #this does not work for ActiveStorage attachments
      
      # use this instead
      File.open(image_file.path, 'w', encoding: 'ASCII-8BIT') do |file|
        product.attachment.download do |chunk|
          file.write(chunk)
        end
      end

      zipfile.add("#product.attachment.created_at.in_time_zone.png", image_file.path)
    end
  end

  zip_data = File.read(temp_file.path)
  send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)

ensure # important steps below
  temp_file.close
  temp_file.unlink
end

【讨论】:

【参考方案3】:

它适用于我(我需要加载基于 Carrierwave 的 MyModel 文档):

require 'zip'
require 'open-uri'

class TestsController < ApplicationController
  def index
    filename = 'test.zip'
    temp_file = ::Tempfile.new(filename)

    my_model_document = ::MyModel.last
    my_model_document_name = ::File.basename(my_model_document.document.path)

    begin
      ::Zip::OutputStream.open(temp_file)  |zos| 
      ::Zip::File.open(temp_file.path, ::Zip::File::CREATE) do |zipfile|
        dr_temp_file = Tempfile.new(my_model_document_name)
        dr_temp_file.write(open(my_model_document.document.url).read.force_encoding("UTF-8"))
        zipfile.add(my_model_document_name, dr_temp_file.path)
      end

      zip_data = File.read(temp_file.path)
      send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)
    ensure
      temp_file.close
      temp_file.unlink
    end
  end
end

【讨论】:

以上是关于使用 RubyZip 生成文件并以 zip 格式下载的主要内容,如果未能解决你的问题,请参考以下文章

RubyZip:找不到存储在活动存储中的文件的路径

从 Azure blob 存储下载所有文件,对其进行压缩并以 JAVA 格式上传 zip 文件

如何直接从 ZipEntry(RubyZip、Paperclip、Rails 3)获取临时文件对象(内容类型正确,无需写入磁盘)?

rubyzip 压缩文件夹时遇到Errno::EACCES (Permission denied @ rb_sysopen -

使用 Ruby 归档文件的正确方法是啥?

是否有可用于列出非 zip 档案内容的 gem?