使用FormData和multer上传文件

6
我成功地使用multer模块将文件通过选择文件并提交表单的方式上传到Node服务器,但现在我需要创建一个FormData对象,并使用XMLHttpRequest发送文件,而不是提交表单。但是它没有起作用,在服务器端(路由器)文件始终是未定义的。
执行AJAX请求的函数如下:
function uploadFile(fileToUpload, url) {

  var form_data = new FormData();

  form_data.append('track', fileToUpload, fileToUpload.name);

  // This function simply creates an XMLHttpRequest object
  // Opens the connection and sends form_data
  doJSONRequest("POST", "/tracks/upload", null, form_data, function(d) {
    console.log(d);
  })

}

请注意,fileToUpload已定义且url正确,因为调用了正确的路由方法。 fileToUpload是通过将文件从文件系统拖放到dropzone中,然后访问drop事件的dataTransfer属性获得的File对象。 doJSONRequest是一个函数,它创建一个XMLHttpRequest对象并发送文件等(如注释中所述)。
function doJSONRequest(method, url, headers, data, callback){

  //all the arguments are mandatory
  if(arguments.length != 5) {
    throw new Error('Illegal argument count');
  }

  doRequestChecks(method, true, data);

  //create an ajax request
  var r = new XMLHttpRequest();

  //open a connection to the server using method on the url API
  r.open(method, url, true);

  //set the headers
  doRequestSetHeaders(r, method, headers);

  //wait for the response from the server
  r.onreadystatechange = function () {
    //correctly handle the errors based on the HTTP status returned by the called API
    if (r.readyState != 4 || (r.status != 200 && r.status != 201 && r.status != 204)){
      return;
    } else {
      if(isJSON(r.responseText))
        callback(JSON.parse(r.responseText));
      else if (callback !== null)
        callback();
    }
  };

  //set the data
  var dataToSend = null;
  if (!("undefined" == typeof data) 
    && !(data === null))
    dataToSend = JSON.stringify(data);

  //console.log(dataToSend)

  //send the request to the server
  r.send(dataToSend);
}

这里是 doRequestSetHeaders

function doRequestSetHeaders(r, method, headers){

  //set the default JSON header according to the method parameter
  r.setRequestHeader("Accept", "application/json");

  if(method === "POST" || method === "PUT"){
    r.setRequestHeader("Content-Type", "application/json");
  }

  //set the additional headers
  if (!("undefined" == typeof headers) 
    && !(headers === null)){

    for(header in headers){
      //console.log("Set: " + header + ': '+ headers[header]);
      r.setRequestHeader(header, headers[header]);
    }

  }
}

我的路由器上传文件的操作如下:
// Code to manage upload of tracks
var multer = require('multer');
var uploadFolder = path.resolve(__dirname, "../../public/tracks_folder");

function validTrackFormat(trackMimeType) {
  // we could possibly accept other mimetypes...
  var mimetypes = ["audio/mp3"];
  return mimetypes.indexOf(trackMimeType) > -1;
}

function trackFileFilter(req, file, cb) {
  cb(null, validTrackFormat(file.mimetype));
}

var trackStorage = multer.diskStorage({
  // used to determine within which folder the uploaded files should be stored.
  destination: function(req, file, callback) {

    callback(null, uploadFolder);
  },

  filename: function(req, file, callback) {
    // req.body.name should contain the name of track
    callback(null, file.originalname);
  }
});

var upload = multer({
  storage: trackStorage,
  fileFilter: trackFileFilter
});


router.post('/upload', upload.single("track"), function(req, res) {
  console.log("Uploaded file: ", req.file); // Now it gives me undefined using Ajax!
  res.redirect("/"); // or /#trackuploader
});

我猜测 multer 没有正确识别 fileToUpload 这个文件名为 track 的文件(是这样的吧?),也就是说中间件 upload.single("track") 没有正常工作/解析,或者干脆不支持使用 FormData,如果是这样的话,那就很麻烦了。那么在继续使用 multer 的情况下有哪些替代方法呢?
如何使用 AJAX 和 multer 上传文件?
如果需要更多细节,请随时询问。

嗨,试试这个。http://jasonshultz.com/blog/file-upload-with-angularjs-node-and-express - Amaranadh Meda
2个回答

7

multer 使用 multipart/form-data 内容类型请求上传文件。从您的 doRequestSetHeaders 函数中删除这一部分应该可以解决您的问题:

if(method === "POST" || method === "PUT"){
   r.setRequestHeader("Content-Type", "application/json");
}

您不需要指定content-type,因为FormData对象已经使用正确的编码类型。根据文档

传输的数据格式与表单的submit()方法发送数据时使用的格式相同,如果表单的编码类型设置为multipart/form-data。

这是一个工作示例。它假设有一个带有id drop-zone的拖放区域和一个具有id upload-button的上传按钮:

var dropArea  = document.getElementById("drop-zone");
var uploadBtn = document.getElementById("upload-button");
var files     = [];

uploadBtn.disabled = true;
uploadBtn.addEventListener("click", onUploadClick, false);

dropArea.addEventListener("dragenter", prevent, false);
dropArea.addEventListener("dragover",  prevent, false);
dropArea.addEventListener("drop", onFilesDropped, false);   

//----------------------------------------------------
function prevent(e){

    e.stopPropagation();
    e.preventDefault();
}

//----------------------------------------------------
function onFilesDropped(e){

    prevent(e);

    files = e.dataTransfer.files;

    if (files.length){
        uploadBtn.disabled = false;
    }
}

//----------------------------------------------------
function onUploadClick(e){

    if (files.length){
        sendFile(files[0]);
    }
}

//----------------------------------------------------
function sendFile(file){

    var formData = new FormData();
    var xhr      = new XMLHttpRequest();

    formData.append("track", file, file.name);

    xhr.open("POST", "http://localhost:3000/tracks/upload", true);

    xhr.onreadystatechange = function () {  
        if (xhr.readyState === 4) {  
            if (xhr.status === 200) {  
                console.log(xhr.responseText);
            } else {  
                console.error(xhr.statusText);  
            }  
        }  
    };

    xhr.send(formData);
}

服务器端的代码是一个简单的express应用程序,其中包括您提供的精确路由器代码。

我说过我的表单是多部分的吗,也就是说我的表单中有其他类型的字段..? - nbro
回答这个问题,你应该要么发布你完整的客户端代码(不仅仅是uploadFile),要么尝试我的代码。我的代码再次证明可以工作。已经使用你的路由器代码进行了测试。 - cviejo
我将发布我的doJSONRequest方法实现...现在请查看编辑。 - nbro
你在 doRequestSetHeaders 中手动设置 content-type 了吗?这可能是我代码和你的不同之处,也是为什么我的代码能够正常工作而你的不能的原因。 - cviejo
好的,这就是为什么你的代码不起作用而我的代码可以的原因。不过我们最好把这个问题移到聊天中讨论。 - cviejo
显示剩余4条评论

2

要发布一个被multer接受的FormData对象,上传函数应该像这样:

function uploadFile(fileToUpload, url) { 
    var formData = new FormData();
    //append file here 
    formData.append('file', fileToUpload, fileToUpload.name);
    //and append the other fields as an object here
    /* var user = {name: 'name from the form',
        email: 'email from the form' 
        etc...       
    }*/
    formData.append('user', user);

    // This function simply creates an XMLHttpRequest object
    // Opens the connection and sends form_data
    doJSONRequest("POST", "/tracks/upload", null, formData, function(d) {
        console.log(d);
    })
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接