后端返回文件流,前端导出文件损坏怎么办(踩坑记录)

后端返回文件流,前端如何接收处理

今天来和大家分享一下我是如何遇到这个问题的

我在做我公司项目的一个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

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
您好!对于后端返回文件并由前端导出的需求,您可以按照以下步骤进行操作: 1. 后端传输文件:在后端,您可以使用合适的编程语言和框架,将文件的形式返回前端。根据您使用的具体技术栈,可能会有不同的方法来实现这一点。 2. 前端接收文件前端需要通过合适的方式接收后端返回文件。通常情况下,可以使用浏览器的内置 API(如 Fetch API 或 XMLHttpRequest)来发起请求并接收文件。 3. 将文件导出:一旦前端成功接收到文件,您可以使用合适的 JavaScript 库或框架来处理并导出文件。常见的方法是创建一个 `<a>` 标签,并为其设置 `href` 属性为文件的 URL,再调用 `click()` 方法以触发下载。 以下是一个简单的示例,演示了如何在前端导出后端返回文件(以 CSV 文件为例): ```javascript // 后端返回文件 fetch('/api/getFile', { method: 'GET', }) .then(response => response.blob()) // 将响应转换为 Blob 对象 .then(blob => { // 创建下载链接 const downloadLink = document.createElement('a'); downloadLink.href = window.URL.createObjectURL(blob); downloadLink.download = 'file.csv'; // 设置下载文件的名称 // 触发下载 downloadLink.click(); }) .catch(error => { console.error('导出文件失败:', error); }); ``` 请注意,这只是一个示例,并不能适用于所有情况。根据您的实际需求和技术栈,可能需要进行适当的调整和修改。希望对您有所帮助!如果您有任何进一步的问题,请随时提问。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值