在工作中遇到一个关于文件下载的问题。
场景:从后端请求到一段二进制数据,点击按钮的时候下载。
一开始下载的文件呐,不是打不开,就是打开了乱码,通过查阅资料并学习最终解决了问题。以下是解决方法和学习笔记。
获取数据
用axios
发送请求。我用到的是post请求,
{
url: 'your url',
method: 'post',
/* 一定要加这个 否则就会乱码 */
responseType: 'arraybuffer',
}
XMLHttpRequest.responseType
是一个枚举字符串值,用于指定响应中的数据类型。(还有别的值,可根据实际需要选择)- 当值
arraybuffer
时表示一个包含二进制数据的JavaScript ArrayBuffer。 - ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。 ps.就是因为这个responseType,卡了我两三个小时,我在别人写的博客里其实也看到过这个参数,但是没往这个上想,平时发请求也没用过这个参数,这次新的知识又增加了。
将二进制数据变成blob
res是获取到的原始二进制数据
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
- Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成
ReadableStream
来用于数据操作。 - 构造函数
Blob(blobParts[, options])
blobParts
是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob。DOMStrings会被编码为UTF-8。options
是一个可选的BlobPropertyBag字典,它可能会指定如下两个属性:type
,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型endings
,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个:native
,代表行结束符会被更改为适合宿主操作系统文件系统的换行符transparent
,代表会保持blob中保存的结束符不变
- 直接打印blob对象,会得到
这只能看到大小和MIME类型,想看文本内容的话酱紫:
const READER = new FileReader();
READER.addEventListener('loadend', (e) => {
console.log(e.target.result);// 此处输出结果
});
READER.readAsText(blob);
ok啦
- MIME类型,媒体类型(通常称为 Multipurpose Internet Mail Extensions 或 MIME 类型 )是一种标准,用来表示文档、文件或字节流的性质和格式。
- 这次只用到了excel,需要用别的的时候去网上查一下就行,下面这个是
.csv
文件的type
{ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8" }
- 这次只用到了excel,需要用别的的时候去网上查一下就行,下面这个是
下载文件
用filer-saver
下载,blob是上一步处理好的Blob对象。我这次要下载的是一个excel文件
import FileSaver from 'file-saver';
FileSaver.saveAs(blob, 'filename.xlsx');
这一步就是纯粹的API调用。
合起来酱紫
const getListAll = () => {
setExportLoading(true);
getExcel({
params: {
...queryParams,
},
}, { responseType: 'arraybuffer' }).then((res) => {
if (res) {
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
FileSaver.saveAs(blob, 'filename.xlsx');
}
}).finally(() => {
setExportLoading(false);
});
};
补充-为什么增加了responseType后,返回结果就能正常处理了。
打印看看结果
先打印出,加和不加responseType: 'arraybuffer'
时原始数据和blob缓冲区的数据
控制台打印加了responseType: 'arraybuffer'
的结果
控制台打印不加
responseType: 'arraybuffer'
的结果
打印加了
responseType: 'arraybuffer'
的blob缓冲区
打印不加
responseType: 'arraybuffer'
的blob缓冲区
用其他的不需要responseType就可以正常返回对象或者数组的接口试一下,给他加上responseType: 'arraybuffer'
,
分别在network和控制台打印一下结果,发现请求的资源就是很正常的我们期待返回的结果,
但是控制台里,就是个这
这时候推出:加或者不加responseType: 'arraybuffer'
获取到的资源都是固定的(在network里看到的东西没有变化),加上这个会限制res(响应结果)的类型(加上responseType: 'arraybuffer'
后打印出的res就是个ArrayBuffer对象)。
再查一下打印出来的东西都代表啥意思
ArrayBufferByteLength
表示ArrayBuffer的大小,单位是字节。
- Int8Array 类型数组表示二进制补码8位有符号整数的数组。
- Uint8Array 数组类型表示一个8位无符号整型数组
这一堆数组都是二进制数组,应该就是这些数组里存放了文件相关的信息。
结论
一般情况下,我们发请求都不加这个responseType: 'arraybuffer'
,不加的时候默认值是text,会指定response是DOMString对象中的文本,所以平时不加responseType
也没事。导出文件这个需求这里,后端返回的是二进制文件,这时候要是不加responseType,二进制流会被当成DOMString解析成一段乱码的字符串,这时候用乱码的字符串构建去的blob对象,显然不是预期的结果,所以会造成导出文件乱码或者文件打不开(被破坏)。我们预期的是用返回的二进制数据构建blob,这时候必须指定响应数据的类型,浏览器才能正确的处理数据。
这时候又有新的问题,浏览器是怎么解析响应正文的呢?