Ruby on rails 3 Rails-使用Jquery文件上传功能将大文件直接上传到S3(托管在Heroku上)

Ruby on rails 3 Rails-使用Jquery文件上传功能将大文件直接上传到S3(托管在Heroku上),ruby-on-rails-3,heroku,amazon-s3,jquery-file-upload,Ruby On Rails 3,Heroku,Amazon S3,Jquery File Upload,我使用的是Heroku,这意味着我必须直接将多个大文件上传到S3。。我使用的是Rails 3.2.11和Ruby 1.9.3。我不希望使用carrierwave或回形针宝石,或者在这一点上真的改变太多-我只需要让这一切我有工作 在尝试转到S3之前,如果我在本地运行我的应用程序,我可以将多个大文件上传到本地文件系统。当我在Heroku上运行它时,小文件上传,但大文件上传失败。因此切换到S3 我尝试了一些调整,也尝试了下面的链接,但这对我已经使用的本地服务器的文件系统(还有Heroku,但是Hero

我使用的是Heroku,这意味着我必须直接将多个大文件上传到S3。。我使用的是Rails 3.2.11和Ruby 1.9.3。我不希望使用carrierwave或回形针宝石,或者在这一点上真的改变太多-我只需要让这一切我有工作

在尝试转到S3之前,如果我在本地运行我的应用程序,我可以将多个大文件上传到本地文件系统。当我在Heroku上运行它时,小文件上传,但大文件上传失败。因此切换到S3

我尝试了一些调整,也尝试了下面的链接,但这对我已经使用的本地服务器的文件系统(还有Heroku,但是Heroku无法处理大文件)来说是一个太大的改变

尝试:

我在这里尝试了一些关于堆栈溢出的其他示例,但是它们对本地工作的更改太多了,而且,我没有掌握它们所做的一切

现在,当我尝试上传图像时会发生什么

这就好像文件上传可以正常工作一样——预览图像已经成功创建,但是没有任何东西被上传到Amazon s3,我也没有收到任何类型的错误消息(比如s3身份验证失败或者其他什么..什么都没有)

为了将文件转移到我的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 %>
我的控制器:

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元素):



救命啊

对于S3,实际上没有现成的上传文件的解决方案,因为Amazon是一个相当复杂的工具

当时我也遇到了类似的问题,花了两周时间试图弄清楚S3是如何工作的,现在我使用一个可行的解决方案将文件上传到S3上。我可以告诉你一个对我有效的解决方案,我从未尝试过Heroku提出的方案。 我选择使用的插件是Plupload,因为它是我实际使用的唯一组件,除了通过XHR直接上传S3外,它还提供百分比指示器和浏览器内图像大小调整,我发现这对于生产应用程序是完全必需的,其中一些用户有20mb的图像,他们想上传作为他们的头像

S3中的一些基础知识:

步骤1

Amazon bucket需要在其CORS文件中进行正确配置,以便首先允许外部上传。Heroku totorial已经告诉您如何将配置放在正确的位置。

步骤2

需要策略数据,否则客户端将无法访问相应的bucket文件。我发现通过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:制作一些链接作为上传按钮:

<a id="upload_button" href="#">Upload</a>
can\u render
变量非常有用,这样您就可以使应用程序在实际完成上载后重新呈现页面

要使按钮在其他地方工作,请拨打:

ImageUploader.photo_uploader(user_id);
该按钮将充当多功能上传器按钮。 重要的是,制定政策的方式确保没有人可以将照片上传到其他人的目录中。 如果有一个版本不是通过ajax回调实现同样的功能,而是通过web钩子实现同样的功能,那就太好了,这是我将来想要做的事情

同样,这不是一个完美的解决方案,但从我的经验来看,它足以将图像和视频上传到亚马逊


注意如果有人问我为什么有这种复杂的面向对象的上传器对象结构,原因是我的应用程序有各种不同的上传器,它们的行为不同,它们需要有一个具有共同行为的初始值设定项。按照我的方式,我可以用最少的代码为视频编写一个初始值设定项,这将与现有的图像上传程序做类似的事情。

cool。。我不确定这对我有什么帮助,但我会调查的。。非常感谢,我喜欢你的面向对象解决方案。。
<a id="upload_button" href="#">Upload</a>
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 () {
  //
}
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);
  });
}
ImageUploader.photo_uploader(user_id);