JS fetch的FormData中基于Blob创建zip文件上传报400错误
问题说明
在JavaScript中使用fetch搭配FormData上传zip文件时,通过<input type="file">控件读取的本地zip文件可正常上传,但自行通过Blob生成的shapefile格式zip文件上传时始终返回400 Bad Request错误。
可正常运行的文件选择上传代码
// 本地文件选择上传逻辑 var fileInput = document.getElementById('avatar'); var filename = fileInput.files[0].name; var headers = new Headers(); headers.set('X-CSRFToken', csrftoken); var formData = new FormData() formData.set("time", false) formData.set("base_file", fileInput.files[0] ) fetch("/upload/", { "credentials": "include", "body": formData, "headers": headers, "method": "POST", "mode": "cors" })
异常的Blob生成文件上传代码
以下代码中,生成的Blob通过saveAs方法可正常保存为本地可打开的zip文件,构造出的对象经校验为File实例,但上传始终报错:
var shpBuffer = await ShapeFileFromGeoJSON(geojson, fileName); var base64String = Uint8Array.from(window.atob(shpBuffer), (v) => v.charCodeAt(0)); var shapeFileBlob = new Blob([ base64String ], {type:"application/zip"}); saveAs(shapeFileBlob, fileName); // 本地保存正常,zip文件可用 var file = new File([shapeFileBlob], 'application.zip', { type: shapeFileBlob.type,}); console.log(file); // 控制台输出: File {name: 'application.zip', lastModified: 1656002582746, // lastModifiedDate: Fri Jun 24 2022 04:43:02 GMT+1200 (New Zealand Standard Time), // webkitRelativePath: '', size: 6091726, …} console.log(file instanceof File); // 输出true var headers = new Headers(); headers.set('X-CSRFToken', csrftoken); var formData = new FormData() formData.set("time", false) formData.set("file", file) fetch("/upload/", { "credentials": "include", "body": formData, "headers": headers, "method": "POST", "mode": "cors" })
报错信息
POST https://WEBSITE/upload/ 400 (Bad Request) {success: false, errors: Array(1)}
问题根因
- 核心错误:表单字段名不匹配
正常上传逻辑中后端接收文件的字段名为base_file,但Blob生成文件的上传代码错误将字段名写为file,后端无法读取到预期的文件参数,直接返回400错误。 - 存在冗余代码逻辑:先将Uint8Array转为Blob再包装为File的操作没有必要,可直接通过二进制数据构造File实例,不会导致报错但属于多余写法。
- 潜在踩坑点:若手动给FormData请求设置
Content-Type: multipart/form-data请求头,会覆盖浏览器自动生成的boundary分隔符参数,导致后端无法解析表单内容,也会触发400错误。
注:shapefile格式的zip本身不会影响上传逻辑,本地saveAs可正常打开说明文件二进制内容完全符合要求,不需要额外处理文件内容。
修复后可运行代码
var shpBuffer = await ShapeFileFromGeoJSON(geojson, fileName); var uint8Arr = Uint8Array.from(window.atob(shpBuffer), (v) => v.charCodeAt(0)); // 直接通过二进制数据构造File实例,文件名使用实际业务名称,MIME类型设为application/zip var uploadFile = new File([uint8Arr], `${fileName}.zip`, { type: "application/zip" }); var headers = new Headers(); headers.set('X-CSRFToken', csrftoken); // 禁止手动设置Content-Type请求头,浏览器会自动填充multipart/form-data及对应的boundary参数 var formData = new FormData(); formData.set("time", false); // 字段名严格和后端要求保持一致,使用base_file formData.set("base_file", uploadFile); fetch("/upload/", { "credentials": "include", "body": formData, "headers": headers, "method": "POST", "mode": "cors" })
排查注意事项
- 上传时表单的字段名必须和后端接口要求完全一致,大小写、拼写均不能出错
- 所有通过FormData上传文件的请求,都不要手动设置Content-Type请求头
- 只要本地保存的zip文件可正常打开,说明文件内容没有问题,不需要额外做二进制转码处理
- 构造File时的文件名不需要固定写死,使用实际业务文件名即可,不会影响接口解析
内容的提问来源于stack exchange,提问作者Paul Butler
相关产品推荐
相关产品推荐

