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

时间:2012-07-22 04:30:05

标签: jquery ruby-on-rails ruby-on-rails-3 file-upload amazon-s3

这个问题困扰了我好几个小时,我似乎无法找到解决方案。

我有一个rails 3.2应用程序,允许用户使用carrierwave_direct,fog和carrierwave(对carrierwave_direct的依赖关系)将文件上传到Amazon S3帐户。使用carrierwave_direct允许用户通过将文件直接发送到Amazon S3来跳过将文件上传到服务器(保存服务器处理和Heroku等大文件的超时)。

如果您只选择1个文件,将其上传到亚马逊,并希望重定向到您提供亚马逊的网址,那么它的工作正常。它通过将表单发布到Amazon S3来执行此操作,并且Amazon使用URL中的一些参数响应提供的URL(您在表单中指定此URL),然后将其存储为模型中Amazon上的文件的指针。

所以生命周期是:选择1个文件,POST到亚马逊,亚马逊用一个URL将你发送到另一个页面,然后你可以用指向亚马逊文件的指针保存记录。

我一直想弄清楚的是如何选择和上传多个文件并更新上传进度?我正在尝试使用纯javascript(使用现代浏览器提供的文件API)这样做,所以我不想要任何第三方工具。另外,为了深入学习,我正在避免使用任何插件,并试图自己编写代码。

我想要获得的功能是:

  1. 用户看到带有文件字段的表单(或拖放)
  2. 用户选择多个文件(单击文件字段或拖放)
  3. 使用Javascript(尚无服务器),构建一个要上传的选定文件队列(只使用浏览器文件API,只需文件名和大小)
  4. 用户然后点击“开始上传”按钮
  5. 迭代队列中的每个文件并将文件POST到Amazon S3;亚马逊将使用URL响应每个POST,并且该URL需要通过Javascript处理,而不是作为标准请求;亚马逊提供的URL将创建一个记录,用于存储指向Amazon文件的指针;创建记录后,代码将转到队列中的下一个文件,直到完成。
  6. 此时,我甚至可以没有单独的进度条;我很乐意在没有页面刷新的情况下将多个文件发布到Amazon S3。

    我不偏袒任何宝石。我实际上害怕如果我真的希望以特定方式完成它,我将不得不从头开始写我想做的事情。目标是通过AJAX将多个文件上传到Amazon S3帐户。即使是如何处理问题的一般概念,我也会欣喜若狂。我花了很多时间在谷歌搜索这个,我还没有找到任何可以做我想要的解决方案。任何帮助都将非常感激。

    编辑2014-03-02

    Raj问我如何实现我的多重上传。已经很久没有回忆起我所做的所有“为什么”(可能是糟糕的代码,因为这是我第一次),但这就是我的目标。

    我上传的模型是推荐书,其中相关图片存储在Amazon S3中。它允许用户选择多个图像(我认为它们实际上是我转换为图像的PDF文件)并将它们拖放到屏幕上。上传时,我显示了一个模式,让用户反馈需要多长时间。

    我不会假装记住我在很多方面所做的事情,但如果它有助于随意使用它。

    # Gemfile
    # For client-side multiple uploads
    gem "jquery-fileupload-rails"
    
    # For file uploads and Amazon S3 storage
    gem "rmagick"
    gem "carrierwave"
    gem "fog"
    

    以下是观点:

    # 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>
    

    和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)
    

    推荐模型:

    class Testimonial < ActiveRecord::Base
      mount_uploader :image, ImageUploader
    
      def display_name
        if name.blank?
          return "Testimonial #{self.id}"
        else
          return name
        end
      end
    end
    

2 个答案:

答案 0 :(得分:6)

正如评论中所建议的那样,使用jQuery Upload:http://blueimp.github.com/jQuery-File-Upload/

答案 1 :(得分:0)

我已经开始为此功能编写基本库。我在github上有一个工作版本,我正在撰写一系列博客文章,详细说明如何实现这一目标。

&#39;工作&#39;代码可以在https://github.com/joeandrews/s3multipartupload找到。