使用jQuery.ajax发送multipart/formdata

我有一个问题发送文件到服务器端php脚本使用jQuery的ajax函数。 这是有可能得到文件列表与$('#fileinput').attr('files'),但如何可能将此数据发送到服务器?当使用file-input.

时,服务器端php-script上的结果数组($_POST)是0 (NULL)

我知道这是可能的(尽管到目前为止我还没有找到任何jQuery解决方案,只有prototyye代码(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);
}
});
1028441 次浏览

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

(只有一个文件file-0,除非您在文件输入上指定了multiple属性,在这种情况下,数字将随着每个文件的增加而增加。)

使用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']将是一个包含每个上传文件的文件上传字段的数组。实际上,我建议在初始解决方案中使用这种方法,因为迭代更简单。

只是想给拉斐尔的回答补充一点。下面是如何让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>

PHP在没有JavaScript的情况下提交,会生成$_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);
}
});

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

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,但我发现使用相同的$_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()的构造函数中删除了form元素——在Safari中修复了这段代码。)

该代码做两件事。

  1. 自动检索input name属性,使HTML更易于维护。现在,只要form拥有putImages类,其他一切都将自动处理。也就是说,input不需要有任何特殊的名称。
  2. 普通HTML提交的数组格式由JavaScript在数据中重新创建。附加线。注意括号。

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

我只是根据我读到的一些信息构建了这个函数。

使用它就像使用.serialize(),而不是只放.serializefiles();
在这里做测试
< / p >
//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);

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"));

并继续执行拉斐尔的代码

  1. jquery-> $("#id")[0]表单对象
  2. data = new FormData($(#id)[0]);
  3. 好的,数据是你想要的

如果表单是在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){
...

看看我的代码,它为我做了这项工作

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

我今天遇到的一个问题,我认为值得指出与这个问题有关:如果ajax调用的url被重定向,那么content-type: 'multipart/form-data'的报头可能会丢失。

例如,我在http://server.com/context?param=x上发帖

在Chrome的网络选项卡中,我看到了这个请求的正确的多部分头,但随后302重定向到http://server.com/context/?param=x(注意上下文后的斜杠)

在重定向期间,多部分报头丢失。如果这些解决方案不适合您,请确保请求没有被重定向。

Devin Venable的回答接近于我想要的,但是我想要一个可以在多个表单上工作,并使用表单中已经指定的操作,以便每个文件都能到正确的位置。

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

这让我想到了这个: (用jQuery选择器替换formSelector)

$(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.
});


});

旧版本的IE不支持FormData (FormData的完整浏览器支持列表在这里:https://developer.mozilla.org/en-US/docs/Web/API/FormData)。

你可以使用jquery插件(对于ex, http://malsup.com/jquery/form/#code-samples),或者,你可以使用基于IFrame的解决方案通过ajax发布多部分表单数据:https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript

上面所有的解决方案看起来都很好,很优雅,但是FormData()对象不需要任何参数,而是在实例化它后使用append(),就像上面写的那样:

formData.append (val.name val.value);

现在你甚至不需要jQuery:) 获取API支持表

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

如果文件输入name表示一个数组并标记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来获得此功能。

由此产生的$_FILES条目在Chrome开发工具中是这样的:

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。在我的测试集中,几十张图片中似乎只有一张出现这种情况。