最新更新:添加最终演示的 sinatra 项目源码 https://github.com/vincenting/sinatra-image-server-example
由于各种原因,需要给快速客户端提供一个图片上传、生成缩略图、以及获取的接口。使用到的技术关键字:
最终实现的目标:
前方高能,代码块成堆!
post '/' do
result = []
begin
params[:images].each do |f|
filename = SecureRandom.hex(32) + File.extname(f[:filename])
filepath = "#{ENV['STATIC_ROOT']}#{filename}"
FileUtils.cp(f[:tempfile].path, filepath)
result << "#{ENV['STATIC_URL']}#{filename}"
end
rescue Exception => e
halt 400
end
json msg: 'upload success.', urls: result
end
然后使用 minitest 来进行测试,或者写一个简单的页面来测试。
<html>
<head><title>Multi file upload</title></head>
<body>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="images[]" multiple />
<input type="submit" />
</form>
</body>
</html>
首先需要实现一个简单的缩略图 Worker
class ImageResizeWorker
include Sidekiq::Worker
def perform(image_path, sizes)
image_info = {}
[:dirname, :extname].each do |f|
image_info[f] = File.send f, image_path
end
image_info[:basename] = File.basename image_path, image_info[:extname]
sizes.each do |size|
output_path = File.join(image_info[:dirname],
"#{image_info[:basename]}@#{size}#{image_info[:extname]}")
resize_image image_path, size, output_path
end
end
private
def resize_image(image_path, output_size, output_path)
image = MiniMagick::Image.open image_path
image.resize output_size
image.write output_path
end
end
使用测试用例测试 worker,测试用例基本如下:
require_relative 'spec_helper'
describe ImageResizeWorker do
before do
@test_image_path = File.join(File.dirname(__FILE__), 'test_image_files', 'wensen.jpg')
@worker = ImageResizeWorker.new
end
it 'should resize images without any error' do
@worker.perform(@test_image_path, %w(100x100 200x200))
end
it 'should has the same extname' do
@worker.perform(@test_image_path, %w(100x100 200x200))
assert File.exist?(File.join(File.dirname(@test_image_path), "[email protected]"))
assert File.exist?(File.join(File.dirname(@test_image_path), "[email protected]"))
end
it 'should assert_equal name and size' do
@worker.perform(@test_image_path, %w(40x50 100x80 120x120))
Dir[File.join(File.dirname(__FILE__), 'test_image_files', '*@*.*')].each do |f|
image = MiniMagick::Image.open(f)
size = File.basename(f, '.*').split('@').last.split 'x'
assert_equal image.width, size.first.to_i
assert_equal image.height, size.last.to_i
end
end
after do
Dir[File.join(File.dirname(__FILE__), 'test_image_files', '*@*.*')].each { |f| File.delete f }
end
end
当测试到大小比例和原图不等的时候突然发现报错了,发现 resize 没有自动切割(或许是有参数我不知道)。于是就要继续拓展 resize_image 方法来支持切割。此处略去一大块代码。
修改 webserver 的代码,在图片拷贝到上传文件夹后,
unless ENV['RACK_ENV'] == 'test'
ImageResizeWorker.perform_async filepath, (params[:resize] || '').split(',')
end
然后就是测试测试,这个时候基本上一个支持多图片上传,并且支持后台图片缩略图的 server 就完成了。
在下面的讨论内容之前,先贴一个地址 IF IS EVAL。
需求:访问 /static/url.jpg@100x100
可以访问到图片 /opt/static/[email protected] 并返回,如果这个时候缩略图还没生成,就先返回原图。
先不管第二个需求,写出来的基本上就是如下的配置:
server {
root /opt;
location /static/ {
rewrite ^/static/(\w+)\.(\w+)@(\w+) /static/$1@$3.$2 break;
}
}
默认完成图片的访问地址与硬盘地址对应,如果满足 rewrite 正则则执行 rewrite,即需求中的第一点。现在需要完成第二条需求,即如果默认的访问失败的话,就去访问原图,于是这里需要使用到 try_files - 按照特定顺序来尝试 file 直到第一个成功。
server {
root /opt;
location /static/ {
try_files $uri $uri.origin;
rewrite ^/static/(\w+)\.(\w+)@(\w+) /static/$1@$3.$2 break;
}
location ~ \.origin$ {
rewrite ^/static/(\w+)\@(\w+).(\w+).origin$ /static/$1.$3 break;
}
}
于是最终就变成了如上的配置文件(虽然一路并不顺利)。在同时使用 try_files 和 rewrite 的时候,发现执行顺序是先 尝试第一个,即 $uri,然后执行 /static/ 中的 rewrite,此时的 $uri 其实已经变成了 rewrite 后的内容,所以当 /static/ 没有找到文件换为 $uri.origin 这里的 $uri 已经不是原来的请求路径,而是 rewrite 后的。所以对于特别是新人,在写 nginx 配置的时候要记得看 error 输出,如果没有的话记得开启,里面会给出很多有价值的内容。
吐槽完毕,最终在 sinatra 中通过 before 的方法加上基本的鉴权项目就上了测试环境。