我使用paperclip和AWS S3进行文件存储。
我有一个Car模型和一个Image模型。
一辆汽车has_many :images
。
图片has_attachment :file
。
汽车可以拥有我想要的任意数量的图像。
我想要的是一种同时下载所有这些汽车图像的方法。
我有工作代码:
def download
@images = @car.images
compressed_filestream = Zip::OutputStream.write_buffer do |zos|
@images.each do |img|
zos.put_next_entry img.file_file_name
zos.print open(img.file.url).read
end
end
compressed_filestream.rewind
send_data compressed_filestream.read, filename: "#{@car.name}.zip"
end
请求/cars/1/download
时,上述控制器操作将运行。
它有效,但我发现它真的很慢。
我现在想要的是一个更快的大规模下载解决方案。
我觉得下载时间每兆字节需要6秒。
我想要更快的方式。 我知道您可以访问任何网页,右键单击,然后"另存为..."为了保存该特定页面。 当页面有图像时,它们会在下载完成后显示在新文件夹中。 下载也非常快。 我想这是因为浏览器已经下载了这些图像,所以它只是将它们提供给我的计算机而不是再次获取图像。 如果浏览器可以下载HTML文件和资产文件夹,我们应该能够让浏览器只下载一个图像文件夹吗?
我有一些想法,我会继续努力,但我想知道是否有人有更快的解决方案或至少对当前的想法输入。
思路:
每次有人想下载时,都不要起草新的.zip文件,而是每次更新汽车的图片时编辑.zip文件。这样,当用户请求所有图像时,该文件已经存在,并且他们只是下载它。但是这些.zip文件应该放在哪里?我们在哪里以及如何保存它们?
在JavaScript中,您可以使用某个图片网址创建blob文件。我们可以在页面加载后加载所有图像吗?这样页面加载速度很快,但是在后台,当用户正在查看页面时,浏览器正在后台下载图像。如果用户决定下载它们,则下载时间很快。
也许我的控制器操作可以更快地创建一个临时的.zip文件。
想点什么?
答案 0 :(得分:0)
这里最好的选择是将原始缓存系统与aws s3 gem结合使用。
首先,您在S3中创建一个名为car_image_zips
的存储桶。当有人点击下载时,你会联系到这个桶,看看那里是否存在汽车图片zip。如果是,请下载它。如果没有,请下载所有文件并创建zip并上传。这里要注意的一件事是,如果您的实现使用Sidekiq之类的东西作为后台作业,您可以通过使后续上传成为后台作业来进行优化。
所以说:
我认为@car
有一个id
。这也假设您已正确配置AWS S3 gem。所以下载看起来像这样:
def download
car_id = @car.id
s3 = AWS::S3.new #should be added as constant somewhere
bucket = s3.buckets['car_image_zips']
if buckets.object["#{car_id}_zip"].exists? #Sample naming scheme
send_data s3.get_object(bucket:'car_image_zips', key:"#{car_id}_zip").body.read
else
# Zip up files like you have
@images = @car.images
compressed_filestream = Zip::OutputStream.write_buffer do |zos|
@images.each do |img|
zos.put_next_entry img.file_file_name
zos.print open(img.file.url).read
end
end
compressed_filestream.rewindsend_data
s3_obj = s3.bucket('car_image_zips').object("#{@car.name}.zip")
s3_obj.upload_file("#{@car.name}.zip")
send_data compressed_filestream.read, filename: "#{@car.name}.zip"
end
当然,我还没有对此进行过测试,但是这应该可以让您大致了解如何使用基本缓存进行此操作。它并不完美,因为您需要下载并处理一次,但对于相对简单的解决方案来说,这是一个巨大的收获。
如果您真的想要优化,那么每次将文件上传到S3并使其可供下载时,您都可以使用类似AWS Lambda函数的内容来创建zip。
答案 1 :(得分:0)
以下是选项3的答案。在原始代码中,.each
方法等待每个循环完成,然后继续下一个循环。如果将图片从互联网下载到服务器平均需要一秒钟,那么下载40张图像需要大约40秒。而是,同时下载所有文件。为此,请使用Threads。
class CarsController < ApplicationController
def download
images = load_images
filestream = write_file images
send_data filestream.read, filename: "#{@car.name}.zip"
end
def load_images
threads = []
images = []
@car.images.each do |f|
threads << Thread.new do
images << { name: f.file_file_name, file: open(f.file.url).read }
end
end
threads.each(&:join)
images
end
def write_file(images)
require 'zip'
Zip.default_compression = Zlib::NO_COMPRESSION
stream = Zip::OutputStream.write_buffer do |zos|
images.each do |img|
zos.put_next_entry img[:name]
zos.print img[:file]
end
end
stream.rewind
stream
end
end
使用线程将图像及其名称铲成数组。将该信息数组传递给zipfile编写器方法。编写zipfile后,将其发送给用户。
这会将控制器动作运行时间从40秒减少到1..2秒。