简体   繁体   English

使用Rails 3.2和AJAX(非闪存上传解决方案)将多个文件直接上传到Amazon S3

[英]Uploading multiple files directly to Amazon S3 using Rails 3.2 and AJAX (non-flash upload solutions)

This issue has been bothering me for many hours and I can't seem to find a solution to it. 这个问题困扰了我好几个小时,我似乎找不到解决方案。

I have a rails 3.2 app that allows users to upload files to an Amazon S3 account using carrierwave_direct, fog, and carrierwave (dependency for carrierwave_direct). 我有一个Rails 3.2应用程序,该应用程序允许用户使用carrierwave_direct,fog和carrierwave(对carrierwave_direct的依赖性)将文件上传到Amazon S3帐户。 Using carrierwave_direct allows the user to skip uploading the file to the server by POSTing it directly to Amazon S3 (saves server processing and timeouts like Heroku for large files). 通过使用carrierwave_direct,用户可以通过直接将文件直接发布到Amazon S3来跳过将文件上传到服务器(对于大型文件,可以节省服务器处理和超时(如Heroku))。

It works fine if all you do is select 1 file, upload it to Amazon, and want a redirect_to a URL you provide Amazon. 如果您要做的只是选择1个文件,然后将其上传到Amazon,并需要redirect_to您提供给Amazon的URL,它会很好地工作。 It does this by POSTing the form to Amazon S3, and Amazon responds to a provided URL (you specify this URL in your form) with some params in the URL, which are then stored as a pointer to the file on Amazon in your model. 它通过将表单发布到Amazon S3来执行此操作,然后Amazon响应提供的URL(您在表单中指定此URL),并在URL中添加一些参数,然后将这些参数作为指向文件的指针存储在模型中的Amazon上。

So the lifecycle is: select 1 file, POST to Amazon, Amazon responds with a URL that sends you to another page, and you can then save a record with a pointer to the Amazon file. 因此,生命周期为:选择1个文件,发布到Amazon,Amazon通过URL进行响应,该URL会将您发送到另一个页面,然后您可以保存带有指向Amazon文件的指针的记录。

What I've been trying to figure out is how do I allow multiple files to be selected and uploaded and update the upload progress? 我一直试图找出的是如何允许选择并上传多个文件并更新上传进度? I'm trying to do this with pure javascript (using the file API provided by modern browsers) so I don't want any 3rd party tools. 我正在尝试使用纯JavaScript(使用现代浏览器提供的文件API)来执行此操作,因此我不需要任何第三者工具。 Also, in the pursuit of learning this in-depth, I'm avoiding any plugins and am trying to write the code myself. 另外,为了深入学习,我避免使用任何插件,而是尝试自己编写代码。

The functionality I'm trying to obtain is: 我想获得的功能是:

  1. User sees form with file field (or drag/drop) 用户看到带有文件字段的表单(或拖放)
  2. User selects multiple files (either click file field or drag/drop) 用户选择多个文件(单击文件字段或拖放)
  3. Using Javascript (no servers yet), build a queue of selected files to upload (just filename and size, using browser File API) 使用Javascript(尚无服务器),构建要上传的选定文件的队列(仅文件名和大小,使用浏览器File API)
  4. User then clicks a "begin Upload" button 用户然后单击“开始上传”按钮
  5. Iterate over each file in queue and POST the file to Amazon S3; 遍历队列中的每个文件,然后将文件发布到Amazon S3; Amazon will respond to each individual POST with a URL and that URL needs to be handled via Javascript, not as a standard request; 亚马逊将使用URL响应每个单独的POST,并且该URL需要通过Javascript处理,而不是作为标准请求; the URL provided by Amazon will create a record that stores the pointer to the Amazon file; Amazon提供的URL将创建一条记录,该记录存储指向Amazon文件的指针; once the record has been created, the code goes to the next file in the queue until finished. 创建记录后,代码将转到队列中的下一个文件,直到完成。

At this point, I could even do without an individual progress bar; 在这一点上,我什至可以没有单独的进度条。 I'd be happy just to get multiple files POSTed to Amazon S3 without page refreshes. 我很乐意将多个文件发布到Amazon S3,而无需刷新页面。

I am not partial to any of the gems. 我不喜欢任何宝石。 I'm actually afraid I'm going to have to write what I want to do from scratch if I really want it done in a specific way. 实际上,如果我真的想以特定的方式完成工作,我恐怕将不得不从头开始编写我想做的事情。 The goal is multiple file uploads to an Amazon S3 account via AJAX. 目标是通过AJAX将多个文件上传到Amazon S3帐户。 I would be ecstatic with even general concepts of how to approach the problem. 我将对如何解决该问题的一般概念感到欣喜若狂。 I've spent many hours googling this and I just haven't found any solutions that do what I want. 我已经花了很多时间来进行搜索,但还没有找到能满足我需要的解决方案。 Any help at all would be greatly appreciated. 任何帮助将不胜感激。

EDIT 2014-03-02 编辑2014-03-02

Raj asked how I implemented my multiple upload. Raj问我如何实施多重上传。 It's been so long I don't recall all the "why" behind what I did (probably bad code anyway as it was my first time), but here is what I had going on. 一直以来,我都不记得我所做的所有“原因”(无论如何这是我第一次来的错误代码),但这就是我一直在做的事情。

The model I was uploading was a Testimonial, which has an associated image being stored in Amazon S3. 我上载的模型是个人鉴定,其中相关图像存储在Amazon S3中。 It allowed a user to select multiple images (I think they were actually PDF files I converted to images) and drag/drop them onto the screen. 它允许用户选择多个图像(我认为它们实际上是我转换为图像的PDF文件)并将它们拖放到屏幕上。 While uploading, I displayed a modal that gave the user feedback about how long it would take. 上载时,我显示了一个模态,向用户反馈了需要多长时间。

I don't pretend to remember what I was doing on a lot of this, but if it helps feel free to use it. 我不假装记得我在很多事情上所做的事情,但是如果有帮助的话,可以随时使用它。

# Gemfile
# For client-side multiple uploads
gem "jquery-fileupload-rails"

# For file uploads and Amazon S3 storage
gem "rmagick"
gem "carrierwave"
gem "fog"

Here's the view: 这是视图:

# app/views/testimonials/new.html.erb
<div id="main" class="padded">
  <div class="center">
    <div id="dropzone">
      Click or Drop Files here to Upload
    </div>

    <%= form_for @testimonial do |f| %>
      <div class="field">
        <%= file_field_tag :image, multiple: true, name: "testimonial[image]", id: "testimonial_image" %>
      </div>
    <% end %>
  </div>
</div>

<div id="mask"></div>
<div id="modal">
  <h1>
    Uploading <span id="global-upload-count">0</span> Files...
  </h1>
  <div id="global-progress">
    <div id="global-progress-bar" style="width: 0%">
      <div id="global-progress-percentage">0%</div>
    </div>
  </div>
  <div id="global-processing">
    <span class="spinner"></span> Processing...<span id="global-processing-count">0</span> sec
  </div>
</div>

<script id="template-upload" type="text/x-tmpl">
  <div class="upload">
    {%=o.name%} ({%=o.readable_size%})
    <div class="float-right percentage"></div>
    <div class="progress"><div class="bar" style="width: 0%"></div></div>
  </div>
</script>

And the JS: 和JS:

number_to_human_size = (bytes) ->
  sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
  i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)))
  return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]

dropzone_hover = (e) ->
  e.preventDefault()
  $(this).addClass("dropzone-hover")

dropzone_leave = (e) ->
  e.preventDefault()
  $(this).removeClass("dropzone-hover")

jQuery ->
  global_count = 0
  seconds_to_process = 0
  processing_factor = 5 # seconds to convert/process each uploaded file

  $("#testimonial_image").hide()

  dropzone = $("#dropzone")

  dropzone.bind "click", (e) ->
    $("#testimonial_image").click()

  dropzone.bind("dragover", dropzone_hover)
  dropzone.bind("dragleave", dropzone_leave)
  dropzone.bind("drop", dropzone_leave)

  $("#new_testimonial").data("global-count", "0")

  $("#new_testimonial").fileupload
    dropZone: $("#dropzone")
    maxFileSize: 5000000 # 5 MB
    dataType: "script"

    add: (e, data) ->
      file = data.files[0]
      file.readable_size = number_to_human_size(file.size)
      data.context = $(tmpl("template-upload", file).trim())
      $("#new_testimonial").append(data.context)
      data.submit()
      global_count += 1

    progress: (e, data) ->
      if data.context
        progress = parseInt(data.loaded / data.total * 100, 10)
        data.context.find(".bar").css("width", progress + "%")
        data.context.find(".percentage").text(progress + "%")

    submit: (e, data) ->
      $("#mask").show()
      $("#modal").center().show()

    progressall: (e, data) ->
      $("#global-upload-count").text(global_count)
      global_progress = parseInt(data.loaded / data.total * 100, 10)
      $("#global-progress-bar").css("width", global_progress + "%")
      $("#global-progress-percentage").text(global_progress + "%")

      if global_progress >= 100
        seconds_to_process = global_count * processing_factor
        $("#global-processing-count").text(seconds_to_process)

        $("#global-processing").show()

        timer = setInterval(->
          seconds_to_process = seconds_to_process - 1
          $("#global-processing-count").text(seconds_to_process)

          if seconds_to_process == 0
            clearInterval(timer)
            global_count = 0
            seconds_to_process = 0
            $("#modal, #mask").hide(0)
        , 1000)

The Testimonial model: 推荐模型:

class Testimonial < ActiveRecord::Base
  mount_uploader :image, ImageUploader

  def display_name
    if name.blank?
      return "Testimonial #{self.id}"
    else
      return name
    end
  end
end

根据评论中的建议,使用jQuery Upload: http : //blueimp.github.com/jQuery-File-Upload/

I have started writing a basic library for this functionality. 我已经开始为此功能编写一个基本库。 I have a working version on github, and am writing a series of blog posts to detail how to achieve this. 我在github上有一个工作版本,并且正在撰写一系列博客文章以详细说明如何实现此目的。

'Working' code can be found at : https://github.com/joeandrews/s3multipartupload . 可以在以下位置找到“工作”代码: https : //github.com/joeandrews/s3multipartupload

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM