后端返回文件流,前端如何接收处理
今天来和大家分享一下我是如何遇到这个问题的
我在做我公司项目的一个excel导出的功能,项目的后端我使用的是nodejs的egg框架
简单介绍一下我这个项目的一些插件吧,首先这个项目集成了egg的swagger,使用了egg-sequelize,数据库的orm框架,以及跨域中间件等等。
我在做用户管理的时候,想给它加一个导出的功能,我在网上搜索找到了一个处理excel的库,node-xlsx,这是一个专门用来处理excel文件的工具库,也是chatgpt推荐使用的一个工具库。
话不多说,直接看代码!!!
router路由
router.post('/auth/admin/export', controller.auth.admin.export); //导出用户
对应的controller
async export() {
const { ctx,service } = this;
const buffer = await service.auth.admin.exportAdmin(ctx.request.body);
// 设置响应头,指定文件名和文件类型
ctx.set('Content-Type', 'application/octet-stream');
ctx.set('Content-Disposition', 'attachment; filename=admin.xlsx');
// 发送导出的文件给前端
ctx.body = buffer;
}
对应的service
async exportAdmin(options) {
const exportData = await this.getExportData('Auth.Admin', options);
const exportTheadArr = ['邮箱', '姓名', '电话', '创建时间', '更新时间'];
const exportTheadKeyArr = ['email', 'name', 'phone', 'create_time', 'update_time'];
const uExportData = this.getUExportData({
exportTheadArr,
exportTheadKeyArr,
exportData
});
const buffer = await this.service.export.exportData(uExportData);
return buffer;
}
我这边有一个service专门负责处理导出的
const xlsx = require('node-xlsx');
//...
async exportData(data) {
// 通过 node-xlsx 生成 Excel 数据
const excelData = [
{ name: 'Sheet1', data: data }, // data 是一个二维数组,表示 Excel 中的数据
];
const buffer = xlsx.build(excelData);
return buffer;
}
目前的导出很简单,只有这些代码,然后在我的apifox里面测试也是正常的,导出来的效果是这样的
然后在前端去接收这个blob,去下载的时候,我的拦截器是这么写的
axios.interceptors.response(response=>{
if(response.data.type === 'application/octet-stream'){
const blob = new Blob([response.data], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf-8'
})
// 获取文件名,根据自己需要的分割
const fileName = response.headers['content-disposition'].split(';')[1].split("=")[1]
const a = document.createElement('a')
const url = window.URL.createObjectURL(blob)
a.href = url
a.download = fileName
document.body.appendChild(a)
a.style.display = 'none'
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
}
//...
})
我感觉我的写法没有问题,也能正常导出但是发现导出的文件一直损坏。我去百度搜了几篇博客,它们都说是需要在axios里面添加responseType为blob才行
后面我调整了一下,发现还是不行,文件还是损坏,我也试过改成arraybuffer,甚至换成get请求试了下,很多方式我都尝试了一遍
export async function exportAdmin(exportInfo) {
const resp = await request({
url: '/auth/admin/export',
method: 'post',
responseType: 'blob',
data: {
//...
}
});
return resp.data;
}
最后我怀疑是不是axios的版本存在问题,我还专门去下了其他的版本,发现还是存在问题,我又开始想会不会是后端返回的数据流有问题,因为文件导出这一块之前我没试过,我测试了下apifox的导出和swagger的导出,内容都能正常显示,我直接给整不会了。
我也一直尝试着定位问题,我发现拦截器里面打印的response.data一直是一个给转码的string字符串,我就觉得纳闷了,是axios拦截器的问题还是axios没有正确的接收到后端返回的二进制流,我甚至一度怀疑是我后端的问题。
直到后面找了一个大佬帮忙,他给我提供了一个导出文件的接口,但是我测试了发现还是存在问题,我已经明确的知道就是我前端的问题,但是我分析不出来是什么问题,这个项目是基于antd vue admin的。
我索性直接用vue脚手架重新创建了一个空项目来测试项目,然后使用了大佬的接口再进行了一下测试,结果竟然可以了,看到了response.data打印结果出来,真的很激动,搞了快两天的问题
找到大致的方向我就开始看我项目的一个全局引用,我在想是不是引入了哪些插件或者工具把请求拦截做处理才会让response.data的值是一个字符串。
果然让我找到了,这万恶的根源
我发现这里引入了一个mockjs,mockjs是前端用来模拟请求的,它会对请求做一个拦截,而且这里我测试了一下,单纯的引入mockjs不会出问题,但是只要引入了mockjs,并且写了Mock.mock去模拟一个请求拦截,那么所有二进制数据都会被转成字符串类型,所以在平时开发的过程中,mockjs还是慎重使用。如果是单纯前端所有接口都是mock可以使用mock数据,但是如果有真实接口还是要把mock注释掉,防止mock影响我们的axios的一个响应结果。
这个问题还是花了我两天才解决,虽然不是什么很难的事情,但是这一个过程中还是学到了很多东西,也有了启发,一个问题的出现就会带动我全身的一个细胞,不停为了解决问题而分析,搞技术就是这样,遇到问题一定要学会去分析问题,定位到具体的问题,然后再解决它,最后总结一下,为什么会遇到这种问题,这种问题遇到之后的一个解决思路是什么,后面如何规避在出现这种问题。
哈哈哈,希望我这篇博客能帮助到有缘人
记录时间:2023.9.25 11:27