2

所以我正在使用gem 'angular-file-upload-rails'它安装我这个Angular插件:Angular File Ipload

现在,我当前用来上传文件的代码如下所示:

HTML:

<form ng-controller="MediumNewCtrl">
    <input type="file" ng-file-select="upload2($files)" multiple>
</form>

咖啡稿:

$scope.upload2 = ($file) ->
        console.log($file[0])
        fileReader = new FileReader()
        fileReader.readAsArrayBuffer($file[0])
        fileReader.onload = (e) ->
            $upload.http(
                url: "/media.json"
                headers: 'Content-Type': $file[0].type
                data: medium: {text: 'text', image_video: e.target.result}
            ).progress((evt) ->
                console.log "percent: " + parseInt(100.0 * evt.loaded / evt.total)
                return
            ).success((data, status, headers, config) ->
                # file is uploaded successfully
                console.log data

            ).error((data) ->
                console.log 'Error'
            )

现在,当我查看服务器的响应时,我看到了:

Started POST "/media.json" for 127.0.0.1 at 2014-12-12 20:19:10 +0200
Processing by Content::MediaController#create as JSON
  User Load (0.8ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1

{"action"=>"create", "controller"=>"content/media", "format"=>"json"}
Completed 400 Bad Request in 3ms

ActionController::ParameterMissing - param is missing or the value is empty: medium:

问题在于我将其格式化为 json 吗?但是至少不应该将文本参数传递给控制器​​吗?

我也不能使用 html Post,因为我的应用程序的性质是它会在您登录时拦截所有 HTML 请求。

也可能一文不值,我用它paperclip来管理我的上传。所以我可能也必须将文件发送成正确的格式?

4

1 回答 1

3

看起来您正在使用“立即上传”模式。这是未来寻求者的完整示例:

应用程序/视图/静态页面/index.html:

<div ng-app='myApp'>

  <h1>StaticPages#index</h1>
  <p>Find me in: app/views/static_pages/index.html.erb</p>
  <hr>

  <div ng-controller="FileUploadCtrl">
    <input type="file" 
      ng-file-select=""
      ng-model='selectedFiles' 
      ng-file-change="myUpload(selectedFiles)" 
      ng-multiple="true">
  </div>

</div>

应用程序/资产/javascripts/main.js.coffee:

@app = angular.module 'myApp', ['angularFileUpload']

应用程序/资产/javascripts/FileUploadCtrl.js.coffee:

@app.controller 'FileUploadCtrl', [
  '$scope', 
  '$upload', 
  '$timeout', 
  ($scope, $upload, $timeout) ->

    $scope.myUpload = (files) ->
      len = files.length
      i = 0
      fileReader = undefined
      csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

      for file in files
        fileReader = new FileReader()

        #-------
        fileReader.onload = (e) ->

          #Define function for timeout, e.g. $timeout(timeout_func, 5000) 
          timeout_func = ->
            file.upload = $upload.http {
              url: "/static_pages/upload",
              method: 'POST',
              headers: {
                'Content-Type': file.type,
                'X-CSRF-TOKEN': csrf_token
              },
              data: e.target.result #the file's contents as an ArrayBuffer
            }

            file.upload.then(
              (success_resp) -> file.result = success_resp.data,  #response from server (=upload.html)
              (failure_resp) -> 
                if failure_resp.status > 0
                  $scope.errorMsg = "#{failure_resp.status}: #{response.data}"
            )

            file.upload.progress( (evt) ->
              file.progress = Math.min 100, parseInt(100.0 * evt.loaded / evt.total)
            )
          #end of timeout_func

          $timeout timeout_func, 5000 

        #end of FileReader.onload

        fileReader.readAsArrayBuffer file
]

注意:在上面的代码中,我必须添加 csrf 行,因为在 app/views/layouts/application.rb 中,我有这个:

<%= csrf_meta_tags %>

这会导致 rails 为每个网页添加一个 csrf 令牌。angular-file-upload 是导致的rails CSRF Errors,所以我必须检索 csrf 令牌并将其添加到请求标头中。

应用程序/资产/javascripts/application.js:

//I removed: 
//     require turbolinks 
//for angular app
//
//= require jquery
//= require jquery_ujs
//
//The 'require_tree .' adds all the files in some random
//order, but for AngularJS the order is important:
//
//= require angular
//= require angular-file-upload-all
//
//And for some inexplicable reason, this is needed:
//= require main
//I would think 'require_tree .' would be enough for that file.
//
//= require_tree .

我没有将 gems 用于 angular 或 angular-file-upload。我只是将 AngularJS 代码复制到一个名为 angular.js 的文件中,我将它放在 app/assets/javascripts 中。同样,我将 angular-file-upload-all 中的代码复制到 app/assets/javascripts/angular-file-upload-all.js

应用程序/控制器/static_pages_controller.rb:

class StaticPagesController < ApplicationController

  def index
  end

  def upload
    puts "****PARAMS:"
    p params 

    puts "****body of request: #{request.body.read.inspect}"  #inspect => outputs "" for empty body rather than nothing
    puts "****Content-Type: #{request.headers['Content-Type']}"

    render nothing: true
  end  

end

配置/路由.rb:

Test1::Application.routes.draw do
  root "static_pages#index" 
  post "static_pages/upload"

据我所知,data:关键需要是文件的内容(作为 ArrayBuffer)。要让 rails 在 params 哈希中插入其他数据,您可以使用 url,例如

url: "/media.json" + '?firstName=Kaspar 

在服务器端,我可以访问该文件的唯一方法是使用request.body.read带有request.headers['Content-Type']. 你最后做了什么?

另外,我在这里发现了 file.type 的两个问题:

headers: {
  'Content-Type': file.type,

1)由于某种原因,FireFox 和 Chrome 都无法确定 file 的文件类型.json因此file.type最终成为一个空白字符串:"". 然后,Rails 将文件的内容作为参数哈希中的键输入。嗯?

如果您添加.json到网址的末尾:

url: "/static_pages/upload.json",

...然后 Rails 会将请求的主体解析为 JSON 并在 params 哈希中输入键/值对。但是添加.json到 url 不会使代码非常通用,因为它会阻止其他文件类型被正确处理。

这是上传.json文件的更通用解决方案:

  for file in files
    file_type = file.type

    if file_type is ''  #is => ===
      [..., file_ext] = file.name.split '.'

      if file_ext is 'json' 
        file_type = 'application/json'

...然后在代码后面:

 headers: {
    'Content-Type': file_type, #instead of file.type

2)但是,原代码中仍然存在一个闭包问题,需要进行更正才能使多个文件选择正常工作。如果选择多个文件,则所有文件的 file_type 最终将是最后一个文件的 file_type。例如,如果您选择一个.txt文件和一个.json文件,那么这两个文件都将具有第二个文件的类型,即application/json. 这是有问题的,因为 rails 会尝试将文本文件的主体解析为 JSON,这将产生错误ActionDispatch::ParamsParser::ParseError

为了纠正闭包问题,一个众所周知的解决方案是围绕 fileReader.onload() 定义一个包装函数。Coffeescript 的语法使得添加包装函数特别轻松:

    do (file_type) ->  #wrapper function, which `do` immediately executes sending it the argument file_type
      fileReader.onload = (e) ->
        ...
        ...

通过添加一行,您可以解决共享变量问题。有关其作用的详细信息,请转到coffeescript 主页并在页面上搜索:do keyword.

应用程序/资产/javascripts/FileUploadCtrl.js.coffee:

@app.controller 'FileUploadCtrl', [
  '$scope', 
  '$upload', 
  '$timeout', 
  ($scope, $upload, $timeout) ->

    $scope.myUpload = (files) ->
      len = files.length
      i = 0
      fileReader = undefined
      csrf_token = document.querySelector('meta[name="csrf-token"]').getAttribute('content')

      for file in files
        #console.log file
        file_type = file.type
        #console.log file_type

        if file_type is '' 
          [..., file_ext] = file.name.split '.'
          #console.log file_ext
          if file_ext is 'json'
            file_type = 'application/json'
            #console.log "Corrected file_type: " + file_type


        fileReader = new FileReader()

        #-------
        do (file_type) ->
          fileReader.onload = (e) ->

            #Define function for timeout, e.g. $timeout(timeout_func, 5000) 
            timeout_func = ->

              file.upload = $upload.http( 
                url: "/static_pages/upload"
                method: 'POST'
                headers: 
                  'Content-Type': file_type 
                  'X-CSRF-TOKEN': csrf_token
                data: e.target.result, #file contents as ArrayBuffer
              )

              file.upload.then(
                (success_resp) -> file.result = success_resp.data,  #response from server 
                (failure_resp) -> 
                  if failure_resp.status > 0
                    $scope.errorMsg = "#{failure_resp.status}: #{response.data}"
              )

              file.upload.progress (evt) ->
                file.progress = Math.min 100, parseInt(100.0 * evt.loaded / evt.total)
            #end of timeout_func

            $timeout timeout_func, 5000 

          #end of FileReader.onload

        fileReader.readAsArrayBuffer file
]

最后,在这段代码中,

data: e.target.result

...返回的实体e.target.resultArrayBuffer,我无法弄清楚如何修改它以添加其他数据。

于 2014-12-12T19:56:36.433 回答