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
A file named "test. xlsx" and "test. zip" are placed in the templates directory under resources.<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>
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
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