当前位置: 首页 > 知识库问答 >
问题:

在heroku上使用aws sdk gem和jQuery文件上传将Rails直接上传到S3

蒋典
2023-03-14

我正在尝试使用jQuery文件上传和aws sdk gem在Rails中实现直接到AmazonS3上传,并遵循heroku的直接到S3上传说明。这是在html中生成的上载表单:

<form id="pic-upload"
class="directUpload" 
data-form-data="{
"key":"uploads/59c99e44-6bf2-4937-9680-02c839244b33/${filename}",
"success_action_status":"201",
"acl":"public-read",
"policy":"eyJle...In1dfQ==",
"x-amz-credential":"AKIAJCOB5HQVW5IUPYGQ/20160101/us-east-1/s3/aws4_request",
"x-amz-algorithm":"AWS4-HMAC-SHA256",
"x-amz-date":"20160101T010335Z",
"x-amz-signature":"0f32ae...238e"}" 
data-url="https://websmash.s3.amazonaws.com" 
data-host="websmash.s3.amazonaws.com"
enctype="multipart/form-data"
action="/users/bazley/update_pictures" 
accept-charset="UTF-8" 
method="post">

这是相应的jQuery:

$(function() {
  $('.directUpload').find("input:file").each(function(i, elem) {
    var fileInput    = $(elem);
    var form         = $(fileInput.parents('form:first'));
    var submitButton = form.find('input[type="submit"]');
    var progressBar  = $("<div class='bar'></div>");
    var barContainer = $("<div class='progress'></div>").append(progressBar);
    fileInput.after(barContainer);
    fileInput.fileupload({
      fileInput:       fileInput,
      url:             form.data('url'),
      type:            'POST',
      autoUpload:       true,
      formData:         form.data('form-data'),
      paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
      dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
      replaceFileInput: false,
      progressall: function (e, data) {
        var progress = parseInt(data.loaded / data.total * 100, 10);
        progressBar.css('width', progress + '%')
      },
      start: function (e) {
        submitButton.prop('disabled', true);
        progressBar.
          css('background', 'green').
          css('display', 'block').
          css('width', '0%').
          text("Loading...");
      },
      done: function(e, data) {
        submitButton.prop('disabled', false);
        progressBar.text("Uploading done");
        // extract key and generate URL from response
        var key   = $(data.jqXHR.responseXML).find("Key").text();
        var url   = '//' + form.data('host') + '/' + key;
        // create hidden field
        var input = $("<input />", { type:'hidden', name: fileInput.attr('name'), value: url })
        form.append(input);
      },
      fail: function(e, data) {
        submitButton.prop('disabled', false);
        progressBar.
          css("background", "red").
          text("Failed");
      }
    });
  });
});

尝试上传文件会产生以下日志:

Started POST "/users/bazley/update_pictures" for ::1 at 2016-01-01 21:26:59 +0000 Processing by CharactersController#update_pictures as HTML
Parameters: {
    "utf8"=>"✓", 
    "authenticity_token"=>"rvhu...fhdg==",
    "standardpicture"=>{
        "picture"=>#<ActionDispatch::Http::UploadedFile:0x0000010b32f530 
            @tempfile=#<Tempfile:/var/folders/19/_vdcl1r913g6fzvk1l56x4km0000gn/T/RackMultipart20160101-49946-7t94p.jpg>, 
            @original_filename="europe.jpg", 
            @content_type="image/jpeg", 
            @headers="Content-Disposition: form-data; name=\"standardpicture[picture]\"; filename=\"europe.jpg\"\r\nContent-Type: image/jpeg\r\n">
    }, 
    "commit"=>"Upload pictures", 
    "callsign"=>"bazley"
}

表单提交成功,但由于Rails没有在S3上保存正确的位置(“picture”,一个字符串),表单无法工作;相反,它认为地点是

"picture"=>#<ActionDispatch::Http::UploadedFile:0x0000010b32f530

您可以在提交的参数中看到这一点。应该是这样的:

"picture"=>"//websmash.s3.amazonaws.com/uploads/220f5378-1e0f-4823-9527-3d1170089a49/europe.jpg"}, "commit"=>"Upload pictures"}

我不明白的是,当所有正确的信息似乎都存在于表单中时,为什么它会把参数弄错。上面清楚地写着

data-url="https://websmash.s3.amazonaws.com" 

在表单中,jQuery包括

url:  form.data('url'),

那怎么了?

完整性:在控制器中:

before_action :set_s3_direct_post
.
.
def set_s3_direct_post
  @s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: '201', acl: 'public-read')
end

形式:

<%= form_for :standardpicture, url: update_pictures_user_path,
             html: {  id: "pic-upload", class: "directUpload",
                      data: { 'form-data' => (@s3_direct_post.fields),
                              'url' => @s3_direct_post.url,
                              'host' => URI.parse(@s3_direct_post.url).host } 
                   } do |f| %>
  <div class="field">
    <%= f.label :picture %>
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </div>
  <%= f.submit "Upload pictures", class: "btn btn-primary" %>
<% end %>

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'])

编辑

控制台显示此错误:

Uncaught TypeError: Cannot read property 'innerHTML' of null

在这个文件(tmpl.self-c210…9488.js?body=1)中:

(function ($) {
    "use strict";
    var tmpl = function (str, data) {
        var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] ||
                tmpl(tmpl.load(str)) :
                    new Function(
                        tmpl.arg + ',tmpl',
                        "var _e=tmpl.encode" + tmpl.helper + ",_s='" +
                            str.replace(tmpl.regexp, tmpl.func) +
                            "';return _s;"
                    );
        return data ? f(data, tmpl) : function (data) {
            return f(data, tmpl);
        };
    };
    tmpl.cache = {};
    tmpl.load = function (id) {
        return document.getElementById(id).innerHTML;
    };
    tmpl.regexp = /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g;
    tmpl.func = function (s, p1, p2, p3, p4, p5) {
        if (p1) { // whitespace, quote and backspace in HTML context
            return {
                "\n": "\\n",
                "\r": "\\r",
                "\t": "\\t",
                " " : " "
            }[p1] || "\\" + p1;
        }
        if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%}
            if (p2 === "=") {
                return "'+_e(" + p3 + ")+'";
            }
            return "'+(" + p3 + "==null?'':" + p3 + ")+'";
        }
        if (p4) { // evaluation start tag: {%
            return "';";
        }
        if (p5) { // evaluation end tag: %}
            return "_s+='";
        }
    };
    tmpl.encReg = /[<>&"'\x00]/g;
    tmpl.encMap = {
        "<"   : "&lt;",
        ">"   : "&gt;",
        "&"   : "&amp;",
        "\""  : "&quot;",
        "'"   : "&#39;"
    };
    tmpl.encode = function (s) {
        /*jshint eqnull:true */
        return (s == null ? "" : "" + s).replace(
            tmpl.encReg,
            function (c) {
                return tmpl.encMap[c] || "";
            }
        );
    };
    tmpl.arg = "o";
    tmpl.helper = ",print=function(s,e){_s+=e?(s==null?'':s):_e(s);}" +
        ",include=function(s,d){_s+=tmpl(s,d);}";
    if (typeof define === "function" && define.amd) {
        define(function () {
            return tmpl;
        });
    } else {
        $.tmpl = tmpl;
    }
}(this));

共有2个答案

包子航
2023-03-14

这段代码使用aws-sdk-v2 for ruby,它已提交给Heroku,以更新其s3直接上传的文档(https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails#pre-(署名邮递)

有些方面是不必要的,例如在上传到s3后将自己的照片对象存储在DB中。代码显然没有它可能的那么好,这只是为了让它工作。我很高兴听到任何建议,以改善它或知道如果这里的东西不适合你。

Sidekiq作业有一个方法do_work这是我在BaseJobs中定义的一个类,它给我一些额外的指标和信息,没有SidekiqPro你就得不到,所以如果你没有对Sidekiq执行类做任何特别的事情,你需要把它改为“执行”...

#ROUTE CODE
post 'create_photo' => 'users#create_photo'

#CONTROLLER CODE

#intial view, might be update or another method in your case
def new
  @user = current_user.find params[:user_id]            

  @s3_direct_post = Aws::S3::Bucket.new(name: ENV['aws_bucket']).presigned_post(key: "musea_upload/harrisjb/${filename}", success_action_status: "201", acl: "public-read") 
end

#called from ajax method in code below
# I store a photograph object with an id and s3 url in my DB
# this code calls a sidekiq job in the user model to create 
# the photo object in the DB after the photo is uploaded directly to s3
# not essential, but helpful to reference uploaded photos

def create_photo                                                              
  @user = current_user.find params[:user_id]            
  options = {}                                                                
  options['user_id'] = params[:user_id]                                     
  options['key'] = params[:key]                                               
  options['url'] = params[:url]                                               
  @user.create_photograph_from_s3(options)                                   

  respond_to do |format|                                                      
    format.js { }                                                             
   end                                                                         
end  

#MODEL CODE
def create_photograph_from_s3(options)
  CreatePhotographJob.perform_async(options)
end

#JOB CODE
  def do_work(options)                                                          
    user_id = options['user_id']                                              
    user = User.find_by(id: user_id)                                                
    Rails.logger.info "CREATE PHOTOGRAPH JOB FOR USER #{user_id}"             
    url = options['url'].gsub("//h", "h")                                       
    user.create_photograph_from_file(url)                                
    Rails.logger.info "PHOTOGRAPH CREATED"                                      
  end 


#VIEW CODE
<%= form_for(:user_avatar, :remote => true, :url => p_user_avatar_upload_to_s3_path(user_id: @user.id), html: { id: "uploader", class: "uploader white-bg" }) do |f| %>                                                                                
  <input name="authenticity_token" type="hidden" value="<%= form_authenticity_token %>">
  <%= f.file_field :photo, multiple: true, style: 'margin-top: 20px; height: 5em; width: 100%; padding-top: 5%; padding-left: 20%;', class: 'form-group form-control light-gray-bg' %>                                       
<% end %>                         

# JS -- if you want this in your view use the content_for
# or you can store the functions in application.js or wherever you want

<% content_for :javascript do %>
  <script>  
      $(function() {
        $('.uploader').find("input:file").each(function(i, elem) {
          var fileInput    = $(elem);
          var form         = $(fileInput.parents('form:first'));
          var photos       = $('#photo-list');
          var photo_errors = $('#photo-list-errors');
          var photo_name   = 'photo_' + i;
          var submitButton = form.find('input[type="submit"]');

          //taken from heroku example, some bootstrap specific CSS here as well
          var progressBar  = $("<div class='progress-bar progress-bar-success progress-bar-striped active style='width: 0%;'></div>");
          var barContainer = $("<div class='progress col-md-12 col-sm-12 col-xs-12' style='margin-top: 20px; padding-left: 0px; padding-right: 0px;'></div>").append(progressBar);
          fileInput.after(barContainer);

          //jquery direct upload do its thing
          fileInput.fileupload({
            url:             '<%= @s3_direct_post.url %>',
            type:            'POST',
            autoUpload:       true,
            paramName:        'file', // S3 does not like nested name fields i.e. name="user[avatar_url]"
            dataType:         'XML',  // S3 returns XML if success_action_status is set to 201
            replaceFileInput: false,
            formData:         <%= @s3_direct_post.fields.to_json.html_safe %>,
            fileInput:       fileInput,
            progressall: function (e, data) {
              var progress = parseInt(data.loaded / data.total * 100, 10);
              progressBar
                .text(progress + '%')
                .css('role', 'progressbar')
                .css('width', progress + '%');

            },

            start: function (e) {
              $('.spinner').removeClass('hidden');
              $('.progress-bar').addClass('progress-bar-striped');
              submitButton.prop('disabled', true);
            },

            // Called when all files are uploaded
            stop: function (e) {
              $('.progress-bar').removeClass('progress-bar-striped');
              $('.progress-bar').html('Done!');
            },

            //
            done: function(e, data) {
              $('.spinner').addClass('hidden');
              submitButton.prop('disabled', false);

              // extract key and generate URL from response
              var key   = $(data.jqXHR.responseXML).find("Key").text();
              var url   = '//<%= @s3_direct_post.url %>/' + key;

              // when uploading entire folders dropped onto file_field
              // ignore .DS_Store (common with iPhone photos, etc)    
              if (key.indexOf(".DS_Store") >= 0 ) {
                console.log("nope. not doing it.");

              } else {

                // this is showing a preview photo on the left side of the screen
                // as photos are being uploaded

                  photos.prepend("<div class='photo col-md-2 col-sm-3 col-xs-3' style='margin-bottom: 10px; margin-top: 10px; background-image: url(<%= @s3_direct_post.url %>/" + key + ");  background-position: 50% 50%; background-size: cover;'><img class='photo_image' height='1' width='1' src='<%= @s3_direct_post.url %>/" + key + "'> </div>");

                // actual post to your controller

                  $.ajax({ type: "POST",
                  url: "/users/<%=@user.id%>/create_user_avatar",
                  data: { authenticity_token: '<%= form_authenticity_token %>', user_id: '<%= @user.id %>', key: key, url: url },
                  success: function(data) { },
                  error: function(data) {
                    var jqxHR = data.jqXHR;
                    var status = data.textStatus;
                    var error_message = data.errorThrown;
                    var key = $(data.jqXHR.responseXML).find("Key").text();
                    photo_errors.append("<div class='col-md-11 col-sm-11 col-xs-11 headline_three' style='margin-top:10px; margin-right: 10px;'> There was an error uploading the photo " + key + " Error message: " + error_message + " Please attempt to upload this photo again.</div>");
                  }
                }); //ajax call

               } // if-else

            } // done function

          });

        });
      });

   </script>  
<% end %>
东郭海阳
2023-03-14

终于在这里找到了答案。只是去application.js换衣服

//= require jquery-fileupload

//= require jquery-fileupload/basic

基督乘双人马车。仅仅因为多看了两次就丢掉了50个代表点。

 类似资料:
  • 当我试图从客户端上传时,我一直得到403。这是因为桶上没有条件吗?如果我只是指定密钥-没有访问密钥,签名或策略-它将上传罚款。 桶策略: CORS(因当地开发而开放) 签名生成 客户:

  • 问题内容: 关于此问题,有什么方法可以将[文件从ASP.NET应用程序直接上传到Amazon S3并具有进度条? -—编辑---- 两天后,仍然没有直接的运气。发现了一件看起来很有前途但又不是免费的东西:http : //www.flajaxian.com/ 使用Flash通过进度条直接上传到S3。 问题答案: 我也在寻找解决方案。也许这会有所帮助, 来自AWS Dev Commnity, 但在许

  • 问题内容: 看来我还没有清楚地传达出我的问题。我需要发送一个文件(使用AJAX),并且需要使用Nginx HttpUploadProgressModule 获取文件的上传进度。我需要一个很好的解决方案。我已经尝试过使用jquery.uploadprogress插件,但是我发现自己不得不重写其中的大部分内容,以使其在所有浏览器中都能正常工作并使用AJAX发送文件。 我所需要的只是执行此操作的代码,它

  • 问题内容: 我已经进行了很多搜索,以了解如何使用Ajax从表单上载文件,并发现xhr2应该可以做到。但是,我已经尝试过使用FormData对象,但它不起作用。 这是一个简单的html表单 这是“ post.php”文件,当以“老式”方式运行时,它可以正常工作: 这是“ upload.js” 您知道为什么它不起作用吗?控制台返回“未发送文件”。 非常感谢 ! 问题答案: 尝试替换代码: 有了这个:

  • 问题内容: 诚然,Stack Overflow周围也存在类似的问题,但似乎没有一个完全符合我的要求。 这是我想要做的: 上载整个数据格式,其中一个是 单个 文件 使用Codeigniter的文件上传库 到这里为止,一切都很好。数据根据需要进入我的数据库。但我也想通过AJAX帖子提交表单: 使用原生HTML5 File API,而不是Flash或iframe解决方案 最好与低级jQuery方法接口

  • 问题内容: 我希望在用户使用$ .ajax在输入文件中选择文件时异步上传文件。但是接收调用返回索引的PHP未定义。jQuery代码是下一个: 以及调用该调用的php: 谢谢 问题答案: 您无法使用AJAX上传文件,但可以使用,因此不必刷新当前页面。 很多人都对插件束手无策,但您可以轻松完成此操作,并具有AJAX请求的所有功能。 不必使用AJAX函数,而是将表单提交到具有事件处理程序的隐藏文件中,以