vue+springboot文件流方式下載的Excel文檔


工作中常會遇到導入導出功能。最近遇到vue+springboot文件流方式下載的Excel文檔打不開,提示文件格式不正確的問題。解決這個問題花了我很長時間和很多精力。在這記錄一下問題和解決方法,在以后工作中再有這類問題,可作為參考。

1.首先先寫后台代碼
新建工程 File->New->Project -> spring initializer 勾選web依賴 -> Next -> Finish。

再添加poi依賴。

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>
在resources下的templates目錄下放了一個文件名為 “測試.xlsx”文件和“測試.zip”文件。

寫接口 ExportFileController

@CrossOrigin // 解決跨域問題
@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/測試.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"); // 解決文件名亂碼問題,對文件名編碼,前端拿到文件名后解碼
    // 使前端可以通過response.headers['Content-Disposition']獲取到文件名稱
    httpHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
    httpHeaders.setContentDispositionFormData("filename", filename);
    // 經過實驗驗證設置ContentType不起作用
    // 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/測試.zip");
    InputStream inputStream = pathResource.getInputStream();
    String filename = Optional.ofNullable(pathResource.getFilename()).orElse("Template.xlsx");
    filename = URLEncoder.encode(filename, "UTF-8"); // 解決文件名亂碼問題,對文件名編碼,前端拿到文件名后解碼
    response.setHeader("Content-Disposition", "attachment; filename=".concat(filename));
    // 使前端可以通過response.headers['Content-Disposition']獲取到文件名稱
    response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
    // 經過實驗驗證設置ContentType不起作用
    // response.setContentType("application/octet-stream; charset=UTF-8");
    ServletOutputStream outputStream = response.getOutputStream();
    IOUtils.copy(inputStream, outputStream);
    logger.info("done");
}

}
這里用了兩種不同的實現方式。

啟動項目,通過瀏覽器直接發送請求和Postman(選擇Send and Download方式)都順利下載文件。瀏覽器下載下來文件名稱正確,Postman下載下來的文件名亂碼。因為瀏覽器會對文件名進行decodeURI解碼,而Postman不會。

2.前端Vue項目
在一給個空文件夾中,打開terminal  輸入 vue init webpack,回車

? 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

這樣就新建一個Vue項目。

因為只是一個demo,為了簡單對HelloWorld.vue進行修改,加入兩個button,點擊觸發兩個方法。

HelloWorld.vue

npm install axios  安裝axios依賴 ,封裝請求。

request.js

import axios from 'axios'
import {resolveFileName, convertBlobToFile} from './utils/util'
export const httpRequsetBlob = axios.create({
  baseURL: '',
  timeout: 1000
})
/**
 * 獲取文件流的請求
 */
httpRequsetBlob.interceptors.request.use(function (config) {
  // Do something before request is sent
  config.responseType = 'blob' // 設置相應類型為 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) {
    // 需要后端設置 response.setHeader("Access-Control-Expose-Headers", "Content-Disposition")
    const contentDisposition = response.headers['content-disposition']
    console.log(contentDisposition)
    const filename = resolveFileName(contentDisposition)
    // 將文件流轉為文件下載
    convertBlobToFile(response.data, filename)
    return response
  }
}, function (error) {
  // Do something with response error
  return Promise.reject(error)
})
util.js 封裝工具方法

/**
 * String對象添加replaceAll方法
 /
/
 eslint-disable /
String.prototype.replaceAll = function (searchStr, replaceStr) {
  return this.replace(new RegExp(searchStr, 'g'), replaceStr)
}
/
*
 * 從Content-Disposition中獲取下載文件名
 * @param {} contentDisposition response.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) {
  /
*
   * 獲取headers content-disposition中的文件名稱
   * 需要后端設置 response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"),否則無法獲取
   /
  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]) // 對文件名decodeURI解碼,解決中文名亂碼問題
        }
      })
      return filename
    }
  }
  return
}
/
*
 * 將文件流轉為文件下載
 * @param {
} data 文件流
 * @param {*} filename 文件名
 */
export const convertBlobToFile = function (data, filename) {
  const blob = new Blob([data]) // 無需指定 type
  const downloadElement = document.createElement('a')
  const href = window.URL.createObjectURL(blob) // 創建下載的鏈接
  downloadElement.href = href
  downloadElement.download = filename // 下載后文件名
  document.body.appendChild(downloadElement)
  downloadElement.click() // 點擊下載
  document.body.removeChild(downloadElement) // 下載完成移除元素
  window.URL.revokeObjectURL(href) // 釋放掉blob對象
}

源碼連接:https://gitee.com/szile/VueSpringBootExportFileDemo.git


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM