使用jQuery.ajax发送multipart/formdata

628
我在使用jQuery的ajax函数将文件发送到服务器端PHP脚本时遇到了问题。可以使用$('#fileinput').attr('files')获取文件列表,但如何将此数据发送到服务器呢?使用文件输入时,在服务器端PHP脚本中得到的数组($_POST)为0(null)。
我知道这是可能的(虽然我还没有找到任何jQuery解决方案,只有Prototye代码(http://webreflection.blogspot.com/2009/03/safari-4-multiple-upload-with-progress.html))。
这似乎是比较新的功能,请不要提到通过XHR / Ajax上传文件是不可能的,因为它绝对可行。
我需要在Safari 5中实现此功能,FF和Chrome也不错,但并非必需品。
目前我的代码是:
$.ajax({
    url: 'php/upload.php',
    data: $('#file').attr('files'),
    cache: false,
    contentType: 'multipart/form-data',
    processData: false,
    type: 'POST',
    success: function(data){
        alert(data);
    }
});

1
很遗憾,在IE<10上无法使用FormData对象。 - Alejandro García Iglesias
@GarciaWebDev 据说你可以使用Flash的polyfill来支持相同的API。请查看https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills获取更多信息。 - yuxhuang
可能 重复 - Raphael Schweikert
3
你可以使用$(':file')来选择所有的文件输入框,这样会更简单一些。 - Shahar
@RameshwarVyevhare 那个答案是在这个问题被回答五年后发布的。请不要挑拨类似的问题,只为了推销自己的答案。 - kitti
@RameshwarVyevhare,您在链接上的答案是在原问题发布一年后发布的。 - Xzhibit
13个回答

963

从Safari 5/Firefox 4开始,最简单的方法是使用FormData类:

var data = new FormData();
jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file-'+i, file);
});

现在你有一个FormData对象,可以与XMLHttpRequest一起发送了。

jQuery.ajax({
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
});

必须将contentType选项设置为false,强制jQuery不为您添加Content-Type头,否则,其中的边界字符串将会丢失。 此外,您必须保留processData标志设置为false,否则,jQuery将尝试将您的FormData转换为字符串,这将失败。

您现在可以使用以下代码在PHP中检索文件:

$_FILES['file-0']

除非您在文件输入上指定了multiple属性,否则只有一个文件file-0,否则数字将随每个文件递增。

对于旧版浏览器,可以使用FormData模拟

var opts = {
    url: 'php/upload.php',
    data: data,
    cache: false,
    contentType: false,
    processData: false,
    method: 'POST',
    type: 'POST', // For jQuery < 1.9
    success: function(data){
        alert(data);
    }
};
if(data.fake) {
    // Make sure no text encoding stuff is done by xhr
    opts.xhr = function() { var xhr = jQuery.ajaxSettings.xhr(); xhr.send = xhr.sendAsBinary; return xhr; }
    opts.contentType = "multipart/form-data; boundary="+data.boundary;
    opts.data = data.toString();
}
jQuery.ajax(opts);

从现有表单创建FormData

可以使用现有表单对象的内容来创建FormData对象,而不必手动迭代文件:

var data = new FormData(jQuery('form')[0]);

使用PHP本地数组而不是计数器

只需将您的文件元素命名相同,并以方括号结尾:

jQuery.each(jQuery('#file')[0].files, function(i, file) {
    data.append('file[]', file);
});

$_FILES['file']将是一个包含每个已上传文件的文件上传字段的数组。实际上,我推荐这种方式而不是我的初始解决方案,因为它更容易迭代。


当发送非文件(例如作为JSON的对象)时,我的后端由于缺少部分的Content-Type而出现问题。因此,请考虑将其包装在Blob中,例如 data.append('my-object', new Blob([JSON.stringify(myObject)], {type: 'application/json'})) - D. Walz

68

看看我的代码,它为我完成了工作

$( '#formId' )
  .submit( function( e ) {
    $.ajax( {
      url: 'FormSubmitUrl',
      type: 'POST',
      data: new FormData( this ),
      processData: false,
      contentType: false
    } );
    e.preventDefault();
  } );

52

我只是想为 Raphael 的好答案添加一些内容。以下是如何让 PHP 生成相同的 $_FILES,不管您是否使用 JavaScript 提交表单。

HTML 表单:

<form enctype="multipart/form-data" action="/test.php" 
method="post" class="putImages">
   <input name="media[]" type="file" multiple/>
   <input class="button" type="submit" alt="Upload" value="Upload" />
</form>

当没有启用JavaScript提交表单时,PHP 会生成这个$_FILES

Array
(
    [media] => Array
        (
            [name] => Array
                (
                    [0] => Galata_Tower.jpg
                    [1] => 518f.jpg
                )

            [type] => Array
                (
                    [0] => image/jpeg
                    [1] => image/jpeg
                )

            [tmp_name] => Array
                (
                    [0] => /tmp/phpIQaOYo
                    [1] => /tmp/phpJQaOYo
                )

            [error] => Array
                (
                    [0] => 0
                    [1] => 0
                )

            [size] => Array
                (
                    [0] => 258004
                    [1] => 127884
                )

        )

)

如果您使用渐进增强,可以使用 Raphael JS 提交文件...

var data = new FormData($('input[name^="media"]'));     
jQuery.each($('input[name^="media"]')[0].files, function(i, file) {
    data.append(i, file);
});

$.ajax({
    type: ppiFormMethod,
    data: data,
    url: ppiFormActionURL,
    cache: false,
    contentType: false,
    processData: false,
    success: function(data){
        alert(data);
    }
});

使用该 JavaScript 提交后,这是 PHP 的 $_FILES 数组的样子:

Array
(
    [0] => Array
        (
            [name] => Galata_Tower.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpAQaOYo
            [error] => 0
            [size] => 258004
        )

    [1] => Array
        (
            [name] => 518f.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/phpBQaOYo
            [error] => 0
            [size] => 127884
        )

)

这是一个很好的数组,实际上有些人会将$_FILES 转换成这样,但我发现使用相同的 $_FILES无论是否使用JavaScript提交都很有用。 这里有一些JS的微小改变:

// match anything not a [ or ]
regexp = /^[^[\]]+/;
var fileInput = $('.putImages input[type="file"]');
var fileInputName = regexp.exec( fileInput.attr('name') );

// make files available
var data = new FormData();
jQuery.each($(fileInput)[0].files, function(i, file) {
    data.append(fileInputName+'['+i+']', file);
});

(2017年4月14日编辑:我从FormData()的构造函数中删除了表单元素——这修复了Safari中的此代码。)

这段代码完成了两件事。

  1. 自动检索输入名称属性,使HTML更易维护。现在只要表单具有putImages类,其他所有东西都会自动处理。也就是说,input不需要有任何特殊的名称。
  2. JavaScript在data.append行中通过数组格式重新创建了普通HTML提交的格式。请注意方括号。

通过这些更改,使用JavaScript提交将产生与使用简单HTML提交完全相同的$_FILES数组。


48

我基于一些信息编写了此函数。

使用方式类似于使用.serialize(),只需使用.serializefiles();即可。
在我的测试中可以正常工作。

//USAGE: $("#form").serializefiles();
(function($) {
$.fn.serializefiles = function() {
    var obj = $(this);
    /* ADD FILE TO PARAM AJAX */
    var formData = new FormData();
    $.each($(obj).find("input[type='file']"), function(i, tag) {
        $.each($(tag)[0].files, function(i, file) {
            formData.append(tag.name, file);
        });
    });
    var params = $(obj).serializeArray();
    $.each(params, function (i, val) {
        formData.append(val.name, val.value);
    });
    return formData;
};
})(jQuery);

2
我正在尝试使这个工作起来,但它似乎无法识别serializefiles()作为一个函数,尽管这个定义放在页面顶部。 - Fallenreaper
1
对我来说完全没问题。使用以下代码获取数据: var data = $("#avatar-form").serializefiles(); 将其通过ajax数据参数发送,并使用express formidable进行分析: form.parse(req, function(err, fields, files){ 感谢您提供的代码片段 :) - SchurigH

24

如果您的表单在HTML中定义,将表单传递到构造函数中比迭代并添加图像更容易。

$('#my-form').submit( function(e) {
    e.preventDefault();

    var data = new FormData(this); // <-- 'this' is your form element

    $.ajax({
            url: '/my_URL/',
            data: data,
            cache: false,
            contentType: false,
            processData: false,
            type: 'POST',     
            success: function(data){
            ...

15

Devin Venable的答案接近我想要的内容,但我想要一个可以在多个表单中使用并且使用表单中已指定的操作,以便每个文件都会被发送到正确位置的解决方案。

我还想使用jQuery的on()方法,这样我就可以避免使用.ready()了。

这让我得到了以下内容:(将formSelector替换为您的jQuery选择器)

$(document).on('submit', formSelecter, function( e ) {
        e.preventDefault();
    $.ajax( {
        url: $(this).attr('action'),
        type: 'POST',
        data: new FormData( this ),
        processData: false,
        contentType: false
    }).done(function( data ) {
        //do stuff with the data you got back.
    });

});

9
如果文件输入的名称(name)表示一个数组并且标志(flags)中有multiple,并且您使用FormData解析整个form,则不需要迭代地使用append()添加输入文件。 FormData将自动处理多个文件。

$('#submit_1').on('click', function() {
  let data = new FormData($("#my_form")[0]);

  $.ajax({
    url: '/path/to/php_file',
    type: 'POST',
    data: data,
    processData: false,
    contentType: false,
    success: function(r) {
      console.log('success', r);
    },
    error: function(r) {
      console.log('error', r);
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="my_form">
  <input type="file" name="multi_img_file[]" id="multi_img_file" accept=".gif,.jpg,.jpeg,.png,.svg" multiple="multiple" />
  <button type="button" name="submit_1" id="submit_1">Not type='submit'</button>
</form>

请注意,使用常规的 button type="button",而不是 type="submit"。 这表明在获取此功能时不依赖于使用 submit
在 Chrome 开发工具中,生成的 $_FILES 条目如下所示:
multi_img_file:
  error: (2) [0, 0]
  name: (2) ["pic1.jpg", "pic2.jpg"]
  size: (2) [1978036, 2446180]
  tmp_name: (2) ["/tmp/phphnrdPz", "/tmp/phpBrGSZN"]
  type: (2) ["image/jpeg", "image/jpeg"]

注意:有时候单独上传一些图片是没问题的,但是当以多个文件的方式上传时,就会出现上传失败的情况。表现为 PHP 报告空的 $_POST$_FILES,而 AJAX 没有抛出任何错误。这个问题发生在 Chrome 75.0.3770.100 和 PHP 7.0 上。在我的测试数据中,只有几十张图片中的1张会出现这种情况。

8

现在甚至不需要jQuery:)fetch API 支持表格

let result = fetch('url', {method: 'POST', body: new FormData(document.querySelector("#form"))})

优美的解决方案 - Alexander Zabyshny

1
今天我遇到了一个问题,值得指出的是:如果ajax调用的url被重定向,则content-type: 'multipart/form-data'的标头可能会丢失。
例如,我正在发布到http://server.com/context?param=x 在Chrome的网络选项卡中,我看到了这个请求的正确的多部分标头,但然后是一个302重定向到http://server.com/context/?param=x(注意context后面的斜杠)
在重定向期间,多部分标头丢失了。如果这些解决方案不起作用,请确保请求没有被重定向。

1
FormData类确实可用,但在iOS Safari上(至少是iPhone),我无法直接使用Raphael Schweikert的解决方案。
Mozilla Dev有一个不错的页面关于操作FormData对象
因此,在页面中添加一个空表单,并指定enctype。
<form enctype="multipart/form-data" method="post" name="fileinfo" id="fileinfo"></form>

然后,创建FormData对象如下:
var data = new FormData($("#fileinfo"));

并按照 Raphael的代码 进行操作。

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