现在大部分的系统都采用前后端分离的方式来实现系统的开发,以前我们实现下载excel的时候,我们直接使用GET的方法的方式直接将数据写到页面上,用户直接下载即可,但是使用前后端分离的方式就不能使用传统的方式来实现文件的下载,这时候我们就会遇到各种坑,接下来我将带大家来了解里面遇到的各种坑以及相应的解决方案。

创建前端工程

首先我们创建一个前端的vue工程,然后打开我们的package.json文件引入axios依赖用于与后端进行交互:

"axios": "^0.15.3",

编写后端交互的axios工具类

首先我们在前端工程的src底下创建一个lib目录,然后分别创建axios.js、api.request.js、base.js代码如下:

axios.js

import Axios from 'axios'

class httpRequest {
  constructor () {
    this.options = {
      method: '',
      url: ''
    }
    // 存储请求队列
    this.queue = []
  }

  // 销毁请求实例
  destroy (url) {
    delete this.queue[url]
    const queue = Object.keys(this.queue)
    return queue.length
  }

  // 请求拦截
  interceptors (instance) {
    // 添加请求拦截器
    instance.interceptors.request.use(config => {
      return config
    }, error => {
      // 对请求错误做些什么
      return Promise.reject(error)
    })

    // 添加响应拦截器
    instance.interceptors.response.use((res) => {
      if (res.headers['content-type'] === 'application/json;charset=UTF-8') {
        let {data} = res
        return data
      } else {
        return res
      }
    }, (error) => {
      // 对响应错误做点什么
      return Promise.reject(error)
    })
  }

  // 创建实例
  create (isLoad) {
    let conf = {
      baseURL: 'http://127.0.0.1:8288',
      timeout: 10000,
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Content-Type': 'application/json; charset=utf-8',
        'X-URL-PATH': location.pathname
      }
    }
    if (isLoad) {
      // 若是下周xlsx需要增加这个标识,否则将会无法正常下载xlsx数据
      conf.responseType = 'blob'
    }
    return Axios.create(conf)
  }

  // 请求实例
  request (options) {
    let instance
    if (options.isLoad !== undefined && options.isLoad === true) {
      instance = this.create(true)
    } else {
      instance = this.create(false)
    }
    this.interceptors(instance, options.url)
    options = Object.assign({}, options)
    this.queue[options.url] = instance
    return instance(options)
  }
}

export default httpRequest

api.request.js

import HttpRequest from './axios'
const axios = new HttpRequest()
export default axios

base.js

import axios from './api.request'


export function fetchDownload(url, params = {}) {
    return new Promise((resolve) => {
        axios.request({
            url: url,
            data: params,
            method: 'post',
            isLoad: true
        }).then(res => {
            resolve(res)
        })
    })
}

export function fetch(url, params = {}) {
    return new Promise((resolve) => {
        axios.request({
            url: url,
            data: params,
            method: 'post',
            isLoad: false
        }).then(res => {
            resolve(res)
        })
    })
}

编写测试页面

直接改造我们的Helloword.vue页面,代码如下:

<template>
    <div class="hello">
        <button @click="downloadXlsWright">正确的下载方式</button>
        <button @click="downloadXlsWrong">错误的下载方式</button>
    </div>
</template>

<script>
    import {fetchDownload, fetch} from '../lib/base'

    export default {
        name: 'HelloWorld',
        props: {
            msg: String
        },
        methods: {
            fileDownload(data, fileName) {
                const blob = new Blob([data], {
                    type: 'application/octet-stream'
                })
                const filename = fileName || 'filename.xlsx'
                if (typeof window.navigator.msSaveBlob !== 'undefined') {
                    window.navigator.msSaveBlob(blob, filename)
                } else {
                    let blobURL = window.URL.createObjectURL(blob)
                    let tempLink = document.createElement('a')
                    tempLink.style.display = 'none'
                    tempLink.href = blobURL
                    tempLink.setAttribute('download', filename)
                    if (typeof tempLink.download === 'undefined') {
                        tempLink.setAttribute('target', '_blank')
                    }
                    document.body.appendChild(tempLink)
                    tempLink.click()
                    document.body.removeChild(tempLink)
                    window.URL.revokeObjectURL(blobURL)
                }
            },
            downloadXlsWright() {
                fetchDownload('/poi/downloadXlsWright', {}).then(res => {
                    const filename = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
                    this.fileDownload(res.data, filename)
                })
            },
            downloadXlsWrong() {
                fetch('/poi/downloadXlsWright', {}).then(res => {
                    const filename = decodeURI(res.headers['content-disposition'].split(';')[1].split('=')[1])
                    this.fileDownload(res.data, filename)
                })
            }
        }
    }
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    h3 {
        margin: 40px 0 0;
    }

    ul {
        list-style-type: none;
        padding: 0;
    }

    li {
        display: inline-block;
        margin: 0 10px;
    }

    a {
        color: #42b983;
    }
</style>

后端工程BUG修复

由于我们是前后端分离的,因此我们后端需要设置跨域,直接打开后端的工程加入添加一个config包,然后再添加一个CorsConfig.java配置文件代码如下:

package com.poi.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author linzf
 * @since 2019/4/25
 * 类描述:
 */
@Configuration
public class CorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        // 它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,也可以设置为*号支持全部
                        .allowedHeaders("*")
                        // 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。
                        .allowedMethods("*")
                        // 本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求
                        .allowedOrigins("*")
                        // 该字段可选,用来指定本次预检请求的有效期,单位为秒
                        .maxAge(1728000);
            }
        };
    }

}

验证结果

最后分别启动我们的前端工程和后端工程,访问http://localhost:8080/,我们会看到如下的页面:
在这里插入图片描述
这时候我们分别点击以上的两种下载方式,然后分别下载文件,这时候大家会发现错误的下载方式下载的文件是无法打开的。

解决方式

为什么我们上面的两个下载方式响应的都是同一个后端方法,但是一个可以正常打开,一个无法正常打开呢,这就说到我们的axios中的一个很重要的参数responseType,这个参数默认的时候是返回json,因此我们错误的原因就是我们没有设置其返回的数据的处理方式为blob导致默认使用json去解析因此我们下载的xlsx文件是无法打开的,因此当大家需要使用下载文件的时候建议采用base.js中的fetchDownload这个写法来实现,若需要获取本章的demo例子,大家可以直接扫描我的公众号,然后回复【poi-demo-2】就可以拿到本章的源代码了:
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐