Excel document downloaded by vue+springboot file stream

Import and export functions are often encountered in work. Recently, I encountered the problem that the Excel document downloaded by vue+springboot file stream cannot be opened, which prompted that the file format is incorrect. It took me a long time and a lot of energy to solve this problem. Record the problems and solutions here, and there will be such problems in future work, which can be used as a reference.

1. First write the background code
Create a new project file - > New - > Project - > spring initializer check web dependency - > next - > finish.

Then add poi dependency.

pom.xml


4.0.0

org.springframework.boot
spring-boot-starter-parent
2.3.0.RELEASE


com.szile.excel
export-excel
0.0.1-SNAPSHOT
export-excel
Demo project for Spring Boot

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.17</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
A file named "test. xlsx" and "test. zip" are placed in the templates directory under resources.

Write interface: ExportFileController

@CrossOrigin / / solve cross domain problems
@RestController
public class ExportFileController {

private static final Logger logger = LoggerFactory.getLogger(ExportFileController.class);

@GetMapping("download")
public ResponseEntity<byte[]> exportExcel() throws IOException {
    logger.info("download start");
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ClassPathResource pathResource = new ClassPathResource("templates/test.xlsx");
    HttpHeaders httpHeaders = new HttpHeaders();
    InputStream inputStream = pathResource.getInputStream();
    String filename = Optional.ofNullable(pathResource.getFilename()).orElse("Template.xlsx");
    IOUtils.copy(inputStream, outputStream);
    filename = URLEncoder.encode(filename, "UTF-8"); // Solve the problem of file name scrambling, encode the file name, and decode the file name after the front end gets it
    // Make the front end available through response Headers ['content disposition '] got the file name
    httpHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
    httpHeaders.setContentDispositionFormData("filename", filename);
    // After experimental verification, setting ContentType does not work
    // final MediaType mediaType = MediaType
    //       .parseMediaType("application/force-download; charset=UTF-8");
    // httpHeaders.setContentType(mediaType);
    logger.info("done");
    return new ResponseEntity<>(outputStream.toByteArray(), httpHeaders, HttpStatus.OK);
}

@GetMapping("download_2")
public void exportExcel2(HttpServletResponse response) throws IOException {
    logger.info("download_2 start");
    ClassPathResource pathResource = new ClassPathResource("templates/test.zip");
    InputStream inputStream = pathResource.getInputStream();
    String filename = Optional.ofNullable(pathResource.getFilename()).orElse("Template.xlsx");
    filename = URLEncoder.encode(filename, "UTF-8"); // Solve the problem of file name scrambling, encode the file name, and decode the file name after the front end gets it
    response.setHeader("Content-Disposition", "attachment; filename=".concat(filename));
    // Make the front end available through response Headers ['content disposition '] got the file name
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    // After experimental verification, setting ContentType does not work
    // response.setContentType("application/octet-stream; charset=UTF-8");
    ServletOutputStream outputStream = response.getOutputStream();
    IOUtils.copy(inputStream, outputStream);
    logger.info("done");
}

}
There are two different implementations.

Start the project, send the request directly through the browser and download the file successfully by Postman (select Send and Download). The file name downloaded by the browser is correct, and the file name downloaded by Postman is garbled. Because the browser will decode the file name, but Postman will not.

2. Front end Vue project
In an empty folder, open terminal, enter vue init webpack, and press enter

? Generate project in current directory? Yes
? Project name test-demo
? Project description A Vue.js project
? Author szile
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Set up unit tests No
? Setup e2e tests with Nightwatch? No
? Should we run npm install for you after the project has been created? (recommended) npm

A new Vue project is created.

Because it's just a demo, for simplicity, for HelloWorld Vue modifies, adds two button s, and clicks to trigger two methods.

HelloWorld.vue

  
    download     download_2     @click="download_2"> @click="download">
 class="hello">

npm install axios - installs axios dependencies and encapsulates requests.

request.js

import axios from 'axios'
import {resolveFileName, convertBlobToFile} from './utils/util'
export const httpRequsetBlob = axios.create({
  baseURL: '',
  timeout: 1000
})
/**
* request to get file stream
 */
httpRequsetBlob.interceptors.request.use(function (config) {
  // Do something before request is sent
  config.responseType = 'blob' / / set the corresponding type to blob
  return config
}, function (error) {
  // Do something with request error
  return Promise.reject(error)
})
// Add a response interceptor
httpRequsetBlob.interceptors.response.use(function (response) {
  // Do something with response data
  if (response.status === 200) {
/ / you need to set the backend response setHeader("Access-Control-Expose-Headers", "Content-Disposition")
    const contentDisposition = response.headers['content-disposition']
    console.log(contentDisposition)
    const filename = resolveFileName(contentDisposition)
/ / transfer the file to file download
    convertBlobToFile(response.data, filename)
    return response
  }
}, function (error) {
  // Do something with response error
  return Promise.reject(error)
})
util.js encapsulation tool and method

/**
* add replaceAll method to String object
 /
/ eslint-disable /
String.prototype.replaceAll = function (searchStr, replaceStr) {
  return this.replace(new RegExp(searchStr, 'g'), replaceStr)
}
/*
* get the download file name from content disposition
 * @param {} contentDisposition response. Value of headers ['content-disposition ']
 *
 * contentDisposition = form-data; name="filename"; filename="%E6%B5%8B%E8%AF%95.xlsx"
 * contentDisposition = attachment; filename=%E6%B5%8B%E8%AF%95.zip
 /
export const resolveFileName = function(contentDisposition) {
  /*
* get the file name in headers # content disposition
* you need to set} response at the back end Setheader ("access control expose headers", "content disposition"), otherwise it cannot be obtained
   /
  if (contentDisposition) {
    const contents = contentDisposition.replaceAll('"', '').split(';').map(item => { return item.trim() })
    if (contents.indexOf('attachment') !== -1 || contents.indexOf('form-data') !== -1) {
      let filename
      contents.forEach(item => {
        const values = item.split('=')
        if (values.length > 1 && values[0] === 'filename') {
filename = decodeURI(values[1]) / / decode the file name decodeURI to solve the problem of Chinese names being garbled
        }
      })
      return filename
    }
  }
  return
}
/*
* file transfer to file download
* @ param {} data file stream
* @ param {*} filename
 */
export const convertBlobToFile = function (data, filename) {
const} blob = new} Blob([data]) / / no need to specify} type
  const downloadElement = document.createElement('a')
  const href = window.URL.createObjectURL(blob) / / create a download link
  downloadElement.href = href
  downloadElement.download = filename / / file name after downloading
  document.body.appendChild(downloadElement)
  downloadElement.click() / / click download
  document.body.removeChild(downloadElement) / / after downloading, remove the element
  window.URL.revokeObjectURL(href) / / release the blob object
}

Source code connection: https://gitee.com/szile/VueSpringBootExportFileDemo.git

Added by LostNights on Thu, 30 Dec 2021 13:35:38 +0200