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


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

    导入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) // 下载完成移除元素
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

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

    axios拦截器输出为

    在这里插入图片描述

    解决问题1

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

    // 上传excel
    const apiImportExcel = (params) => {
      return axiosBizcenterMarketingAct.request({
        url: 'https"//www.test.com/importExcelOptLessNum',
        method: 'post',
        responseType: 'blob',
        data: params
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    情况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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    情况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
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    看到这里,大家可能看到方法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
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

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

    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) // 下载完成移除元素
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    写到这里,有人会想到使用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
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述

    结果就是不行,从上图中可以看到,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

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

  • 相关阅读:
    Locust学习记录3-用户类属性【host attribute,tasks attribute】
    A-Level经济真题每期一练(22)
    JVM GC算法总结
    OpenCV10-图像直方图:直方图绘制、直方图归一化、直方图比较、直方图均衡化、直方图规定化、直方图反射投影
    QT-day3
    python opencv环境配置 保姆级教程
    SpringBoot 08 MVC扩展配置的原理
    【Android面试八股文】性能优化相关面试题:什么时候会发生内存泄漏?举几个你遇到过的例子
    jfreechart后台生成图片采样完美解决方案以及样式美化
    nlp(6)--构建找规律模型任务
  • 原文地址:https://blog.csdn.net/qq_44859233/article/details/127419799