Fetch提交multipart/form-data表单上传文件为空问题修复
问题描述
我有一个如下所示的简单表单:
<form id="my-form-1" class="ajax-form my-form p-4 p-md-5 bg-light text-dark rounded shadow-sm fs-6" action="https://www.gmondini.local/contact-form/send/1" method="post" enctype="multipart/form-data" _lpchecked="1"> ... <button type="submit" class="mt-4 p-3 btn btn-primary text-uppercase text-white">Send Message</button> <div class="mt-4 d-none alert alert-success" role="alert"> Thank you for your message. It has been sent. </div> <div class="mt-4 d-none alert alert-danger" role="alert"> There was an error trying to send your message. Please try again later. </div> </form>
我编写了如下JavaScript函数,将原生表单提交转换为Ajax异步调用:
document.querySelector('.ajax-form').addEventListener('submit', function(e) { e.preventDefault(); let form = this; let formData = new FormData(form); let parsedData = {}; for (let name of formData) { if (typeof(parsedData[name[0]]) == "undefined") { let tempdata = formData.getAll(name[0]); if (tempdata.length > 1) { parsedData[name[0]] = tempdata; } else { parsedData[name[0]] = tempdata[0]; } } } let options = {}; switch (this.method.toLowerCase()) { case 'post': options.body = JSON.stringify(parsedData); case 'get': options.method = this.method; options.headers = { 'Content-Type': 'application/json' }; break; } var error = form.querySelector(".alert-danger"); var success = form.querySelector(".alert-success"); var submits = form.querySelectorAll('[type="submit"]'); fetch(this.action, options).then(r => r.json()).then(data => { if (typeof data.success == 'undefined' || !data.success) { success.classList.add("d-none"); error.classList.remove("d-none"); } else { success.classList.remove("d-none"); error.classList.add("d-none"); submits.forEach(toremove => { toremove.classList.add("d-none"); }); } }); });
点击提交后查看请求payload,上传的文件字段始终为空:
后端基于Laravel 8.0框架,编写如下代码获取上传文件:
$files = $request->file('filebig'); var_dump($files);die;
执行后返回NULL。我已经在表单上正确设置了multipart/form-data属性,该如何修复这个问题?
问题原因
代码存在3个核心问题直接/间接导致文件上传失败:
- 错误地把FormData转成了JSON对象:
JSON.stringify无法序列化File/Blob类型的文件对象,序列化后文件字段会变成空对象或者null,后端自然拿不到文件内容。 - 手动设置了错误的Content-Type请求头:用
multipart/form-data上传文件时,请求头里的Content-Type必须携带浏览器自动生成的boundary分隔符,手动设置为application/json或者不带boundary的multipart/form-data,都会导致后端无法正确解析上传的文件。 - switch语句漏写
break:POST请求逻辑会穿透到GET分支,不管什么请求方法都会强制设置JSON请求头,属于明显的逻辑bug。
修复方案
直接将原生FormData对象作为fetch的body传入,不要手动转JSON,也不要手动设置Content-Type请求头,浏览器会自动生成符合规范的请求头(包含正确的boundary值)。
修复后的完整JS代码如下:
document.querySelector('.ajax-form').addEventListener('submit', function(e) { e.preventDefault(); const form = this; // 直接使用FormData实例即可,不需要手动转成普通对象/JSON const formData = new FormData(form); const options = { method: form.method }; // 仅POST/PUT/PATCH等需要传请求体的方法设置body if (['post', 'put', 'patch'].includes(form.method.toLowerCase())) { options.body = formData; // 禁止手动设置Content-Type,浏览器会自动配置multipart/form-data和对应boundary } const errorEl = form.querySelector(".alert-danger"); const successEl = form.querySelector(".alert-success"); const submitBtns = form.querySelectorAll('[type="submit"]'); fetch(form.action, options) .then(r => r.json()) .then(data => { if (typeof data.success == 'undefined' || !data.success) { successEl.classList.add("d-none"); errorEl.classList.remove("d-none"); } else { successEl.classList.remove("d-none"); errorEl.classList.add("d-none"); submitBtns.forEach(btn => btn.classList.add("d-none")); // 可选:提交成功后重置表单 form.reset(); } }) .catch(() => { // 补充网络异常场景的提示 successEl.classList.add("d-none"); errorEl.classList.remove("d-none"); }); });
额外说明
如果需要在提交前修改/追加表单字段,直接操作FormData实例即可,不要转成普通对象,示例:
// 追加额外字段 formData.append('extra_field', 'extra_value'); // 格式化已有字段 formData.set('username', formData.get('username').trim());
Laravel端不需要做特殊修改,保持原有的$request->file('filebig')写法即可正常获取上传的文件。
内容的提问来源于stack exchange,提问作者Kormi Web Solutions
相关产品推荐
相关产品推荐

