我正在使用Heroku,这意味着我必须直接将多个大文件上传到S3 ..我使用的是Rails 3.2.11和Ruby 1.9.3。我不想使用carrierwave或回形针宝石,或者在这一点上真的改变很多 - 我只需要得到我的工作。
在尝试迁移到S3之前,如果我在本地运行我的应用程序,我可以将多个大文件上传到本地文件系统。当我在Heroku上运行它时,小文件上传但大文件失败。因此切换到S3 ..
我尝试了几个调整,以及下面的这个链接,但这只是对我已经已经使用本地服务器的文件系统(和Heroku,但是Heroku)的更改只是无法处理大文件..)
尝试:https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails
我在Stack Overflow上尝试了其他一些例子,但是对于本地工作的东西来说,它们太多了,而且,我并没有掌握他们正在做的所有事情。
现在,当我尝试上传图片时会发生什么?
好像文件上传工作 - 预览图像已成功创建,但没有任何内容上传到亚马逊s3,我没有收到任何类型的错误消息(如s3身份验证失败或任何事情......没有)< / p>
为了将文件传输到我的s3存储器,我需要更改什么?我可以写出什么来控制台来检测连接到我的s3的问题(如果有的话)?
我的表格:
<%= form_for @status do |f| %>
{A FEW HTML FIELDS USED FOR A DESCRIPTION OF THE FILES - NOT IMPORTANT FOR THE QUESTION}
File:<input id="fileupload" multiple="multiple" name="image"
type="file" data-form-data = <%= @s3_direct_post.fields%>
data-url= <%= @s3_direct_post.url %>
data-host =<%=URI.parse(@s3_direct_post.url).host%> >
<%= link_to 'submit', "#", :id=>'submit' , :remote=>true%>
<% end %>
我的jquery是:
....
$('#fileupload').fileupload({
formData: {
batch: createUUID(),
authenticity_token:$('meta[name="csrf-token"]').attr('content')
},
dataType: 'json',
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
maxFileSize: 5000000, // 5 MB
previewMaxWidth: 400,
previewMaxHeight: 400,
previewCrop: true,
add: function (e, data) {
tmpImg.src = URL.createObjectURL(data.files[0]) ; // create image preview
$('#'+ fn + '_inner' ).append(tmpImg);
...
我的控制器:
def index
#it's in the index just to simplify getting it working
@s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
end
为表单生成的元素是(通过Inspect Element):
<input id="fileupload" multiple="multiple" name="image"
data-form-data="{"key"=>"uploads/34a64607-8d1b-4704-806b-159ecc47745e/${filename}"," "success_action_status"="
>"201"," "acl"=">"public-read"," "policy"=">"[encryped stuff - no need to post]","
"x-amz-credential"=">"
[AWS access key]/[some number]/us-east-1/s3/aws4_request"
," "x-amz-algorithm"=">"AWS4-HMAC-SHA256"
," "x-amz-date"=">"20150924T234656Z"
," "x-amz-signature"=">"[some encrypted stuff]"}"
data-url="https://nunyabizness.s3.amazonaws.com" data-host="nunyabizness.s3.amazonaws.com" type="file">
帮助!
答案 0 :(得分:2)
使用S3实际上没有简单易用的上传文件解决方案,因为亚马逊是一个相当复杂的工具。
我在当天遇到了类似的问题,并花了两周的时间试图弄清楚S3是如何工作的,现在使用一种有效的解决方案将文件上传到S3。我可以告诉你一个适合我的解决方案,我从未尝试过Heroku提出的解决方案。 我使用的插件是Plupload,因为它是我实际设法工作的唯一组件,除了通过XHR简单的直接S3上传,并提供百分比指标的使用和浏览器中的图像大小调整,我发现完全是强制性的对于生产应用程序,一些用户有20mb图像,他们想要上传为他们的头像。
S3中的一些基础知识:
第1步
亚马逊存储桶需要在其CORS文件中进行正确配置,以便首先进行外部上传。 Heroku totorial已经告诉你如何将配置放在正确的位置。 http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
第2步
需要策略数据,否则您的客户端将无法访问相应的存储桶文件。我发现通过Ajax调用可以更好地完成生成策略,因此,例如,admin可以将文件上载到不同用户的文件夹中。 在我的示例中,cancan用于管理给定用户的安全性,figaro用于管理ENV变量。
def aws_policy_image
user = User.find_by_id(params[:user_id])
authorize! :upload_image, current_user
options = {}
bucket = Rails.configuration.bucket
access_key_id = ENV["AWS_ACCESS_KEY_ID"]
secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
options[:key] ||= "users/" + params[:user_id] # folder on AWS to store file in
options[:acl] ||= 'private'
options[:expiration_date] ||= 10.hours.from_now.utc.iso8601
options[:max_filesize] ||= 10.megabytes
options[:content_type] ||= 'image/' # Videos would be binary/octet-stream
options[:filter_title] ||= 'Images'
options[:filter_extentions] ||= 'jpg,jpeg,gif,png,bmp'
policy = Base64.encode64(
"{'expiration': '#{options[:expiration_date]}',
'conditions': [
{'x-amz-server-side-encryption': 'AES256'},
{'bucket': '#{bucket}'},
{'acl': '#{options[:acl]}'},
{'success_action_status': '201'},
['content-length-range', 0, #{options[:max_filesize]}],
['starts-with', '$key', '#{options[:key]}'],
['starts-with', '$Content-Type', ''],
['starts-with', '$name', ''],
['starts-with', '$Filename', '']
]
}").gsub(/\n|\r/, '')
signature = Base64.encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest::Digest.new('sha1'),
secret_access_key, policy)).gsub("\n", "")
render :json => {:access_key_id => access_key_id, :policy => policy, :signature => signature, :bucket => bucket}
end
我把这个方法放到了应用程序控制器中,尽管你可以找到一个更好的地方。 当然,应将此功能的路径放入路径中。
第3步
前端,获取plupload:http://www.plupload.com/建立一些链接作为上传按钮:
<a id="upload_button" href="#">Upload</a>
创建一个配置plupload初始化的脚本。
function Plupload(config_x, access_key_id, policy, signature, bucket) {
var $this = this;
$this.config = $.extend({
key: 'error',
acl: 'private',
content_type: '',
filter_title: 'Images',
filter_extentions: 'jpg,jpeg,gif,png,bmp',
select_button: "upload_button",
multi_selection: true,
callback: function (params) {
},
add_files_callback: function (up, files) {
},
complete_callback: function (params) {
}
}, config_x);
$this.params = {
runtimes: 'html5',
browse_button: $this.config.select_button,
max_file_size: $this.config.max_file_size,
url: 'https://' + bucket + '.s3.amazonaws.com/',
flash_swf_url: '/assets/plupload/js/Moxie.swf',
silverlight_xap_url: '/assets/plupload/js/Moxie.xap',
init: {
FilesRemoved: function (up, files) {
/*if (up.files.length < 1) {
$('#' + config.select_button).fadeIn('slow');
}*/
}
},
multi_selection: $this.config.multi_selection,
multipart: true,
// resize: {width: 1000, height: 1000}, // currently causes "blob" problem
multipart_params: {
'acl': $this.config.acl,
'Content-Type': $this.config.content_type,
'success_action_status': '201',
'AWSAccessKeyId': access_key_id,
'x-amz-server-side-encryption': "AES256",
'policy': policy,
'signature': signature
},
// Resize images on clientside if we can
resize: {
preserve_headers: false, // (!)
width: 1200,
height: 1200,
quality: 70
},
filters: [
{
title: $this.config.filter_title,
extensions: $this.config.filter_extentions
}
],
file_data_name: 'file'
};
$this.uploader = new plupload.Uploader($this.params);
$this.uploader.init();
$this.uploader.bind('UploadProgress', function (up, file) {
$('#' + file.id + ' .percent').text(file.percent + '%');
});
// before upload
$this.uploader.bind('BeforeUpload', function (up, file) {
// optional: regen the filename, otherwise the user will upload image.jpg that will overwrite each other
var extension = file.name.split('.').pop();
var file_name = extension + "_" + (+new Date);
up.settings.multipart_params.key = $this.config.key + '/' + file_name + '.' + extension;
up.settings.multipart_params.Filename = $this.config.key + '/' + file_name + '.' + extension;
file.name = file_name + '.' + extension;
});
// shows error object in the browser console (for now)
$this.uploader.bind('Error', function (up, error) {
console.log('Expand the error object below to see the error. Use WireShark to debug.');
alert_x(".validation-error", error.message);
});
// files added
$this.uploader.bind('FilesAdded', function (up, files) {
$this.config.add_files_callback(up, files, $this.uploader);
// p(uploader);
// uploader.start();
});
// when file gets uploaded
$this.uploader.bind('FileUploaded', function (up, file) {
$this.config.callback(file);
up.refresh();
});
// when all files are uploaded
$this.uploader.bind('UploadComplete', function (up, file) {
$this.config.complete_callback(file);
up.refresh();
});
}
Plupload.prototype.init = function () {
//
}
第4步
通用多用途文件上传器功能的实现:
ImageUploader = {
init: function (user_id, config, callback) {
$.ajax({
type: "get",
url: "/aws_policy_image",
data: {user_id: user_id},
error: function (request, status, error) {
alert(request.responseText);
},
success: function (msg) {
// set aws credentials
callback(config, msg);
}
});
},
},
// local functions
photo_uploader: function (user_id) {
var container = "#photos .unverified_images" // for example;
var can_render = false;
this.init(user_id,
{
select_button: "upload_photos",
callback: function (file) {
file.aws_id = file.id;
file.id = "0";
file.album_title = "userpics"; // I use this param to manage photo directory
file.user_id = user_id;
//console.log(file);
[** your ajax code here that saves the image object in the database via file variable you get here **]
});
},
add_files_callback: function (up, files, uploader) {
$.each(files, function (index, value) {
// do something like adding a progress bar html
});
uploader.start();
},
complete_callback: function (files) {
can_render = true;
}
}, function (config, msg) {
config.key = "users/" + user_id;
// Most important part:
window.photo_uploader = new Plupload(config, msg.access_key_id, msg.policy, msg.signature, msg.bucket);
});
}
can_render
变量非常有用,因此只有在上传器实际完成时才能使应用程序重新呈现页面。
要使按钮在其他地方工作,请致电:
ImageUploader.photo_uploader(user_id);
该按钮将充当Plupload上传器按钮。 重要的是,策略是以某种方式制作的,这样任何人都无法将照片上传到其他人的目录中。 如果有一个版本不是通过ajax回调来做同样的事情会很棒,但是使用web钩子,这是我将来要做的事情。
同样,这不是一个完美的解决方案,但是根据我的经验,将图像和视频上传到亚马逊上的效果非常好。
注意如果有人问为什么我有这个复杂的面向对象的上传器对象结构,原因是我的应用程序有各种不同类型的上传者,他们需要有一个初始化器有共同的行为。我这样做的方式,我可以用最少量的代码为视频编写一个初始化器,它将与现有的图像上传器做类似的事情。