我希望为我的Rails应用程序添加功能,以直接上传文件到Amazon S3。根据我的研究,普遍的共识似乎是使用 s3-swf-upload-plugin 插件。我已经使用这个gem设置了一个示例应用程序,但我无法仅允许选择单个文件。我还希望在上传后创建记录并使用paperclip创建缩略图,但关于此方面的指导很少。
因此,我的问题是:
(1) 我是否正确使用该gem,或者应该采取其他方法?
(2) 是否有任何可以用作参考的样本代码?
非常感谢您的任何帮助。
Chris
我希望为我的Rails应用程序添加功能,以直接上传文件到Amazon S3。根据我的研究,普遍的共识似乎是使用 s3-swf-upload-plugin 插件。我已经使用这个gem设置了一个示例应用程序,但我无法仅允许选择单个文件。我还希望在上传后创建记录并使用paperclip创建缩略图,但关于此方面的指导很少。
因此,我的问题是:
(1) 我是否正确使用该gem,或者应该采取其他方法?
(2) 是否有任何可以用作参考的样本代码?
非常感谢您的任何帮助。
Chris
尝试使用名为CarrierWaveDirect的新宝石,它允许您使用HTML表单直接将文件上传到S3,并轻松将图像处理移入后台进程。
我不确定你是否能轻松地修改它,以便一次只上传一个文件,但这个宝石对我来说非常有效。它基于Ryan Bates的 Railscast之一:
建议使用carrierwave https://github.com/jnicklas/carrierwave(支持s3)进行研究。 使用carrierwave和uploadify进行多文件上传 http://blog.assimov.net/post/4306595758/multi-file-upload-with-uploadify-and-carrierwave-on
如果您正在使用Rails 3,请查看我的示例项目:
使用Rails 3、Flash和基于MooTools的FancyUploader的示例项目,直接上传到S3:https://github.com/iwasrobbed/Rails3-S3-Uploader-FancyUploader
使用Rails 3、Flash/Silverlight/GoogleGears/BrowserPlus和基于jQuery的Plupload的示例项目,直接上传到S3:https://github.com/iwasrobbed/Rails3-S3-Uploader-Plupload
顺便说一下,您可以使用类似于这篇博客文章描述的方法使用Paperclip进行后处理:
http://www.railstoolkit.com/posts/fancyupload-amazon-s3-uploader-with-paperclip
我在Rails中使用了Heroku的直接上传到S3解决方案(它使用jQuery-File-Upload和aws-sdk gem),以便可以通过ajax远程上传到S3。希望这对你有用:
posts_controller.rb
before_action :set_s3_direct_post, only: [:index, :create]
before_action :delete_picture_from_s3, only: [:destroy]
class PostsController < ApplicationController
def index
.
.
end
def create
@post = @user.posts.build(post_params)
if @post.save
format.html
format.js
end
end
def destroy
Post.find(params[:id]).destroy
end
private
def set_s3_direct_post
return S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
end
def delete_picture_from_s3
key = params[:picture_url].split('amazonaws.com/')[1]
S3_BUCKET.object(key).delete
return true
rescue => e
# If anyone knows a good way to deal with a defunct file sitting in the bucket, please speak up.
return true
end
def post_params
params.require(:post).permit(:content, :picture_url)
end
end
posts.html.erb
<div class="info" data-url="<%= @s3_direct_post.url %>"
data-formdata="<%= (@s3_direct_post.fields.to_json) %>"
data-host="<%= URI.parse(@s3_direct_post.url).host %>">
</div>
表单
<%= form_for(:post, url: :posts, method: :post,
html: { class: "post_form", id: "post_form-#{post.id}" }
) do |f| %>
<%= f.text_area :content, id: "postfield-#{post.id}", class: "postText" %>
<%= f.button( :submit, name: "Post", title: "Post" ) do %>
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
<% end %>
<span class="postuploadbutton" id="postUp-<%= post.id %>" title="Add file" >
<span class="glyphicon glyphicon-upload" aria-hidden="true"></span>
</span>
<span title="Cancel file" class="noticecancelupload" id="postCancel-<%= post.id %>" >
<span class="glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</span>
<%= f.file_field :picture_url, accept: 'image/jpeg,image/gif,image/png',
class: "notice_file_field", id: "postFile-#{post.id}" %>
<% end %>
_post.html.erb
<%= button_to post_path(
params: {
id: post.id,
picture_url: post.picture_url
}
),
class: 'btn btn-default btn-xs blurme',
data: { confirm: "Delete post: are you sure?" },
method: :delete do %>
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
<% end %>
_post.html.erb中的Javascript
$(document).off('click',"#postUp-<%= post.id %>");
$(document).on('click', '#postUp-<%= post.id %>', function(e) {
prepareUpload("#post_form-<%= post.id %>");
$('#postFile-<%= post.id %>').trigger("click");
});
$(document).off('click',"#postCancel-<%= post.id %>");
$(document).on('click', '#postCancel-<%= post.id %>', function(e) {
$(".appendedInput").remove(); // $('#postFile-<% post.id %>').val(""); doesn't work for me
$('.progBar').css('background','white').text("");
});
$(document).off('submit',"#post_form-<%= post.id %>"); // without this the form submitted multiple times in production
$(document).on('submit', '#post_form-<%= post.id %>', function(e) { // don't use $('#post_form-<%= post.id %>').submit(function() { so it doesn't bind to the #post_form (so it still works after ajax loading)
e.preventDefault(); // prevent normal form submission
if ( validatePostForm('<%= post.id %>') ) {
$.ajax({
type: 'POST',
url: $(this).attr('action'),
data: $(this).serialize(),
dataType: 'script'
});
$('#postCancel-<%= post.id %>').trigger("click");
}
});
function validatePostForm(postid) {
if ( jQuery.isBlank($('#postfield-' + postid).val()) && jQuery.isBlank($('#postFile-' + postid).val()) ) {
alert("Write something fascinating or add a picture.");
return false;
} else {
return true;
}
}
application.js 中的 Javascript
function prepareUpload(feckid) {
$(feckid).find("input:file").each(function(i, elem) {
var fileInput = $(elem);
var progressBar = $("<div class='progBar'></div>");
var barContainer = $("<div class='progress'></div>").append(progressBar);
fileInput.after(barContainer);
var maxFS = 10 * 1024 * 1024;
var info = $(".info");
var urlnumbnuts = info.attr("data-url");
var formdatanumbnuts = jQuery.parseJSON(info.attr("data-formdata"));
var hostnumbnuts = info.attr("data-host");
var form = $(fileInput.parents('form:first'));
fileInput.fileupload({
fileInput: fileInput,
maxFileSize: maxFS,
url: urlnumbnuts,
type: 'POST',
autoUpload: true,
formData: formdatanumbnuts,
paramName: 'file',
dataType: 'XML',
replaceFileInput: false,
add: function (e, data) {
$.each(data.files, function (index, file) {
if (file.size > maxFS) {
alert('Alas, the file exceeds the maximum file size of 10MB.');
form[0].reset();
return false;
} else {
data.submit();
return true;
}
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
progressBar.css('width', progress + '%')
},
start: function (e) {
progressBar.
css('background', 'orange').
css('display', 'block').
css('width', '0%').
text("Preparing...");
},
done: function(e, data) {
var key = $(data.jqXHR.responseXML).find("Key").text();
var url = '//' + hostnumbnuts + '/' + key;
var input = $('<input />', { type:'hidden', class:'appendedInput',
name: fileInput.attr('name'), value: url });
form.append(input);
progressBar.
css('background', 'green').
text("Ready");
},
fail: function(e, data) {
progressBar.
css("background", "red").
css("color", "black").
text("Failed");
}
});
});
} // function prepareUpload()
create.js.erb
$(".info").attr("data-formdata", '<%=raw @s3_direct_post.fields.to_json %>'); // don't use .data() to set attributes
$(".info").attr("data-url", "<%= @s3_direct_post.url %>");
$(".info").attr("data-host", "<%= URI.parse(@s3_direct_post.url).host %>");
$('.post_form')[0].reset();
$('.postText').val('');
application.js
//= require jquery-fileupload/basic
config/initializers/aws.rb
Aws.config.update({
region: 'us-east-1',
credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
})
S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['S3_BUCKET'])
注意:
此解决方案适用于 index.html.erb 页面上的多个帖子表单。这就是为什么将 @s3_direct_post
信息放置在 index.html.erb 中 class 为 info
的 div 中,而不是每个帖子表单中的原因。这意味着页面上只有一个 @s3_direct_post
在任何时候都会显示,而与页面上的表单数量无关。只有在单击文件上传按钮时才会抓取 @s3_direct_post
中的数据(使用 prepareUpload()
调用)。提交后,在 posts 控制器中生成一个新的 @s3_direct_post
,并通过 create.js.erb 更新 .info
中的信息。将 @s3_direct_post
数据存储在表单中意味着可以同时存在许多不同的 @s3_direct_post
实例,从而导致文件名生成错误。
您需要在帖子控制器索引操作(准备第一次上传)和创建操作(准备第二次及随后的上传)中都设置 :set_s3_direct_post
。
通过e.preventDefault();
阻止了普通表单提交,因此可以使用$.ajax({
手动进行。为什么不在表单中使用remote: true
呢?因为在Rails中,文件上传是通过HTML请求和页面刷新来完成的,即使您尝试远程上传也是如此。
使用info.attr()
而不是info.data()
来设置和检索@s3_direct_post
属性,因为info.data不会得到更新(例如,请参见this问题)。这意味着您还必须手动使用jQuery.parseJSON()
将属性解析为对象(.data()实际上会自动执行此操作)。
在application.js中不要使用//= require jquery-fileupload
。这个bug真的很难识别(请参见这里)。直到我改变了这个,原始Heroku解决方案才起作用。