当导入导出为同一个接口时,会产生什么样的“化学反应”?

28 篇文章 1 订阅
22 篇文章 0 订阅

业务需求中出现一个接口既有导入功能,又需要有导出功能,接口具体情况如下:

导入excel表格接口中,前端传递上传的excel文档,后端判断文档是否格式正常,如果格式正常,则返回json格式的成功数据,如果格式不成功,则返回错误的excel文档,前端负责把错误的excel文档导出,第三种情况,如果5s内再次导入文档,则后端返回不能频繁导入的错误json数据。

总结一下:

情况1:前端导入excel表格,触发请求,后端响应文件,blob格式,导入数据失败,前端导出文件;

情况2:前端导入excel表格,触发请求,后端响应json格式,导入数据正常,存储到数据库中;

情况3:前端5s内再次导入excel表格,触发请求,后端响应json格式,导入数据失败,不能频繁导入;

情况1:导出excel由于格式问题打不开

在这里插入图片描述

导出接口处理如下:

const apiImportExcel = (params) => {
  return axiosBizcenterMarketingAct.request({
    url: 'https//www.xxx.com/importExcelOptLessNum',
    method: 'post',
    data: params
  })
}


// 上传excel,失败下载excel,成功返回msg
const uploading = async (data) => {
  uploadLoading.value = true
  let file = data.file
  let params = new FormData()
  params.append('file', file)
  let res = await apiImportExcel(params)
  console.log(res, 1234)
  blobData.value = res
  isShowDownload.value = true
  uploadMessage.value = '处理失败,请整理表格后重新上传文档'
  uploadLoading.value = false
  dialogVisible.value = true
}

// 导出
const downErrorExcel = () => {
  let fileName = '错误提示.xlsx'
  if (!blobData.value) {
    return
  }
  const blob = new Blob([blobData.value], { type: 'application/vnd.ms-excel' })
  const url = URL.createObjectURL(blob)
  downloadCommon(url, fileName)
}

// 下载处理
const downloadCommon = (url: string, fileName: string) => {
  const link = document.createElement('a')
  link.style.display = 'none'
  link.href = url
  link.setAttribute('download', fileName)
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link) // 下载完成移除元素
}

控制台输出为
在这里插入图片描述

axios拦截器输出为

在这里插入图片描述

解决问题1

由于后端返回文件时,前端必须设置responseType为blob,否则导出的excel会由于格式问题,打不开;

// 上传excel
const apiImportExcel = (params) => {
  return axiosBizcenterMarketingAct.request({
    url: 'https"//www.test.com/importExcelOptLessNum',
    method: 'post',
    responseType: 'blob',
    data: params
  })
}

在这里插入图片描述

情况2:导入成功,返回的内容为blob类型,json字符读取不到

如下图:控制台上方为拦截器输出内容,下方为拿到的data数据

在这里插入图片描述

但是当设置了响应格式为blob,则当后端返回json格式时,前端只能读到blob数据,blob数据看不到里面的内容。

相信大家看到了虽然返回都是blob格式,但是情况1和情况2返回的blob格式的type不一样。所以可以如下处理:

解决问题2:

在这里插入图片描述

处理代码如下:

// 上传excel,失败下载excel,成功返回msg
const uploading = async (data) => {
  uploadLoading.value = true
  let file = data.file
  let params = new FormData()
  params.append('file', file)
  let res = await apiImportExcel(params)
  console.log(res, 1234)
  if(res.type === 'text/xml') {
      isShowDownload.value = true
      uploadMessage.value = '处理成功,成功导入'
  } else {
      blobData.value = res
      isShowDownload.value = true
      uploadMessage.value = '处理失败,请整理表格后重新上传文档'
  }
  uploadLoading.value = false
  dialogVisible.value = true
}

情况3:第三种情况和第二种情况怎么区分?

由于情况2和情况3返回都是json格式的问题,提示用户接口处理是否成功,所以正常的流程是后端接口返回的什么样的字符串,前端页面直接提示用户接口处理结果,而且目前是三种情况,将来扩展可能接口出现第四种第五种情况,所以使用上面的type判断,并不合适。

解决问题3:

不设置responseType为blob,会导致excel因为格式问题打不开,所以只能让接口返回blob格式,那就只能当接口返回类型为text/xml的时候,将blob解析成json格式,然后再读取json字符串了。

如何解析blob格式呢?下面有三种方法:

// 方法1
    const getJSONObjectFromBlob = (blob: Blob) => {
      const reader = new FileReader()
      reader.readAsArrayBuffer(blob)
      reader.onload = function () {
        console.info('reader.result>>>', reader.result) //ArrayBuffer {}
        //将 ArrayBuffer  转换成Blob
        const buf = new Uint8Array(reader.result as ArrayBuffer)
        let enc = new TextDecoder('utf-8')
        let text = enc.decode(buf)
        try {
          const obj = JSON.parse(text)
          return obj
        } catch (error) {
          console.error('解析错误', error)
          return error
        }
      }
    }

// 方法2
    const getJSONObjectFromBlob = (blob: Blob) => {
      const text = await (new Response(blob)).text();
      try {
          const obj = JSON.parse(text)
          return obj
        } catch (error) {
          console.error('解析错误', error)
        	return error
        }
    }

// 方法3
    const getJSONObjectFromBlob = (blob: Blob) => {
      const text = await blob.text();
      try {
          const obj = JSON.parse(text)
          return obj
        } catch (error) {
          console.error('解析错误', error)
          return error
        }
    }

看到这里,大家可能看到方法2和方法3都是读取Blob对象原型链上的text()方法进行解析,其实text()方法就是根据方法1这样设计的,不过目前这个方法1有点小瑕疵,大家能够发现吗?

方法1中使用FileReader对象去读取blob,然后通过onload去解析,不知道大家是否了解onload事件,onload是一个异步任务,所以js引擎走到这里不会去执行内部逻辑,而是等函数执行完才去宏任务队列中取出它,并执行。那函数还有未执行呢?走到这里,函数还没有返回值,所以大家应该猜到了吧,这里会返回undefined。

那怎么解决呢?大家有没有想到await和async关键字,但是使用这两个关键字,必须要是一个promise,所以把方法1改造成返回promise,然后去读取json对象,使用await关键字等待,promise响应再执行后续操作,就可以啦~~~

代码如下:


const getTextFromBlob = (blob: Blob): Promise<string> => {
  //将Blob 对象转换成 ArrayBuffer
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.readAsArrayBuffer(blob)
    reader.onload = function () {
      console.info('reader.result>>>', reader.result) //ArrayBuffer {}
      // 经常会遇到的异常 Uncaught RangeError: byte length of Int16Array should be a multiple of 2
      // const buf = new int16array(reader.result);
      // console.info(buf);

      //将 ArrayBuffer  转换成Blob
      const buf = new Uint8Array(reader.result as ArrayBuffer)
      let enc = new TextDecoder('utf-8')
      resolve(enc.decode(buf))
    }
    reader.onerror = function (e) {
      console.error('转换成ArrayBeffer失败')
      reject(e)
    }
  })
}
// 解析 text/xml类型的blob, 成为JSON对象(失败则是json字符串)
export async function getJSONObjectFromBlob(blob: Blob): Promise<Record<string, unknown> | unknown> {
  let text = ''
  try {
    text = await getTextFromBlob(blob)
    const obj: Record<string, unknown> = JSON.parse(text)
    return obj
  } catch (error) {
    return error
  }
}

好了,为了能够通熟易懂,我还是使用了最简单的方法,代码如下:

const apiImportExcel = (params) => {
  return axiosBizcenterMarketingAct.request({
    url: 'https"//www.test.com/importExcelOptLessNum',
    method: 'post',
    data: params
  })
}
// 解析application/json类型的blob, 成为JSON对象(失败则是json字符串)
   const getJSONObjectFromBlob = (blob: Blob) => {
      const text = await blob.text();
      try {
          const obj = JSON.parse(text)
          return obj
        } catch (error) {
          console.error('解析错误', error)
          return error
        }
    }
    // 上传excel,失败下载excel,成功返回msg
    const uploading = async (data) => {
      uploadLoading.value = true
      let file = data.file
      let params = new FormData()
      params.append('file', file)
      let res = await apiImportExcel(params)
      if (res.type === 'application/json') {
        // 读取Blob
        getJSONObjectFromBlob(res).then((res) => {
          uploadMessage.value = res.msg
        })
      } else {
        blobData.value = res
        isShowDownload.value = true
        uploadMessage.value = '处理失败,请整理表格后重新上传文档'
      }
      uploadLoading.value = false
      dialogVisible.value = true
    }
    
    const downloadCommon = (url: string, fileName: string) => {
      const link = document.createElement('a')
      link.style.display = 'none'
      link.href = url
      link.setAttribute('download', fileName)
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link) // 下载完成移除元素
    }

写到这里,有人会想到使用axios去请求接口会出现这种情况,那使用fetch呢?window对象自带的请求接口的方法,目前兼容性已经满足大部分浏览器了,所以我又做了以下内容

使用fetch请求

我们直接改造上面的uploading函数就行,说干就干

    // 上传excel,失败下载excel,成功返回msg
    const uploading = async (data) => {
      uploadLoading.value = true
      let file = data.file
      let params = new FormData()
      params.append('file', file)
      window.fetch("https//www.xxx.com/importExcelOptLessNum", {
          method: 'post',
          body: params,
          headers: {
              'Authorization': accessToken,
          }
      }).then(res => {
          console.log(res, '----1---')
          return res.json();
          // return res.blob();
       }).tnen(res => {
           console.log(res, '0----3---------')
           blobData.value = res;
           isShowDownload.value = true
           uploadMessage.value = '处理失败,请整理表格后重新上传文档';
       }).catch(err => {
           console.error(err)
       });
      uploadLoading.value = false
      dialogVisible.value = true
    }

在这里插入图片描述

结果就是不行,从上图中可以看到,fetch确实不需要在请求头这里设置响应体类型,但是当数据响应给客户端时,必须要调用Response中的json()方法或者blob()方法,但在这之前,无法判断res是什么数据类型。

在这里插入图片描述

总结一下:

虽然情况1和情况2两次的blob的类型不一致,一个是application/vnd.ms-excel类型,一个是text/xml,但是由于第三种情况的介入,必须要获取到后端返回的json格式数据显示。

设置了responseType为blob类型,接口返回的数据如下:

在这里插入图片描述

没有设置responseType时,接口返回的数据如下:

在这里插入图片描述

目前是 解析 text/xml类型的blob, 成为JSON对象。

更多关于Blob对象的知识,可以点击链接查看:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob

如果大家有什么更好的处理方法,欢迎评论区讨论~~~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值