場景
在某些場景下需要前端瀏覽器從服務器端下載文件,比如需要下載導入Excel的模板。

注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程序猿
獲取編程相關電子書、教程推送與免費下載。
實現
既然是實現通用下載接口,就要實現在后端配置一個下載文件的路徑,在前端進行下載請求時傳遞要下載的文件的名字,然后請求公共接口進行下載。
首先是在前端使用ElementUI的el-link添加一個下載鏈接
<el-link type="info" style="font-size:12px" @click="downloadTemplate('lxszTemplate.xlsx')" >下載模板</el-link>
這里設置了其點擊事件是調用downloadTemplate方法並傳遞一個文件名參數,這個文件名就是要下載的文件名。
然后在對應的點擊事件中
downloadTemplate(value) { download(value) .then((response) => {}) .catch((error) => { alert("錯誤:" + error); }); },
這里執行了一個download方法並傳遞文件名參數。
這個download方法是引用的第三方js中作為公共方法的。
引入方式
import { download } from "@/utils/badao";
在utils下的badao.js中
// 通用下載方法 export function download(fileName) { window.location.href = baseURL + "/common/download?fileName=" + encodeURI(fileName) + "&delete=" + false; }
將此方法進行暴露,作為公共方法。
在通用下載方法中使頁面跳轉window.location.href ,對應的url是SpringBoot中后台的接口。
這里的baseURL是在badao.js中聲明的常量
const baseURL = process.env.VUE_APP_BASE_API
對此常量的賦值是取得全局變量process的屬性,它對應的是在vue.config.js中配置的代理的地址
proxy: { [process.env.VUE_APP_BASE_API]: { target: `http://localhost:8080`, changeOrigin: true, pathRewrite: { ['^' + process.env.VUE_APP_BASE_API]: '' } } },
這里是我本地的8080端口。
然后在上面的通用的下載方法中在URL中還拼接了兩個參數
一個是文件名參數,調用的js的encodeURI方法可以將字符串作為URL進行編碼,一個是是否刪除的參數,默認是false,作為下載成功后是否將文件給刪除,即實現單次下載還是多次下載。
然后在這個url對應SprinBoot后台接口方法中
@GetMapping("common/download") public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) { try { if (!FileUtils.isValidFilename(fileName)) { throw new Exception(StringUtils.format("文件名稱({})非法,不允許下載。 ", fileName)); } String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); String filePath = RuoYiConfig.getDownloadPath() + fileName; response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); response.setHeader("Content-Disposition", "attachment;fileName=" + FileUtils.setFileDownloadHeader(request, realFileName)); FileUtils.writeBytes(filePath, response.getOutputStream()); if (delete) { FileUtils.deleteFile(filePath); } } catch (Exception e) { log.error("下載文件失敗", e); } }
首先調用了文件處理工具類的驗證方法,驗證文件名稱是否合法。
這里是設置了指定文件名稱格式。
方法實現
public static boolean isValidFilename(String filename) { return filename.matches(FILENAME_PATTERN); }
其中參數為常量
public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
下面是對服務器上文件路徑的獲取
String filePath = RuoYiConfig.getDownloadPath() + fileName;
其中RuoyiConfig是配置類,用來讀取項目相關配置,即配置在application.yml中的內容。
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 讀取項目相關配置 * * @author ruoyi */ @Component @ConfigurationProperties(prefix = "ruoyi") public class RuoYiConfig { /** 項目名稱 */ private String name; /** 版本 */ private String version; /** 版權年份 */ private String copyrightYear; /** 實例演示開關 */ private boolean demoEnabled; /** 上傳路徑 */ private static String profile; /** 獲取地址開關 */ private static boolean addressEnabled; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getCopyrightYear() { return copyrightYear; } public void setCopyrightYear(String copyrightYear) { this.copyrightYear = copyrightYear; } public boolean isDemoEnabled() { return demoEnabled; } public void setDemoEnabled(boolean demoEnabled) { this.demoEnabled = demoEnabled; } public static String getProfile() { return profile; } public void setProfile(String profile) { RuoYiConfig.profile = profile; } public static boolean isAddressEnabled() { return addressEnabled; } public void setAddressEnabled(boolean addressEnabled) { RuoYiConfig.addressEnabled = addressEnabled; } /** * 獲取頭像上傳路徑 */ public static String getAvatarPath() { return getProfile() + "/avatar"; } /** * 獲取下載路徑 */ public static String getDownloadPath() { return getProfile() + "/download/"; } /** * 獲取上傳路徑 */ public static String getUploadPath() { return getProfile() + "/upload"; } }
這里的getDownloadPath就是獲取設置的下載路徑的方法,方法具體實現
public static String getDownloadPath() { return getProfile() + "/download/"; }
就是返回profile這個節點
public static String getProfile() { return profile; }
因為使用了@ConfigurationProperties(prefix = "ruoyi")
所以在對應的application.yml中獲取profile就是獲取ruoyi下的profile的值

這里配置的路徑加上/download/再加上一個文件名就是服務器上要下載的模板文件的位置。
所以要提前將此文件放置在服務器上對應的位置。
這里服務器是我本地

然后對文件名進行一個添加時間戳的操作,防止多次下載重名問題。
然后設置響應編碼、響應頭、響應類型。
然后調用了文件工具類的輸出文件到Byte數組的方法writeBytes
方法實現
/** * 輸出指定文件的byte數組 * * @param filePath 文件路徑 * @param os 輸出流 * @return */ public static void writeBytes(String filePath, OutputStream os) throws IOException { FileInputStream fis = null; try { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } fis = new FileInputStream(file); byte[] b = new byte[1024]; int length; while ((length = fis.read(b)) > 0) { os.write(b, 0, length); } } catch (IOException e) { throw e; } finally { if (os != null) { try { os.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e1) { e1.printStackTrace(); } } } }
以及下載文件名重新編碼的方法setFileDownloadHeader
方法實現
/** * 下載文件名重新編碼 * * @param request 請求對象 * @param fileName 文件名 * @return 編碼后的文件名 */ public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { final String agent = request.getHeader("USER-AGENT"); String filename = fileName; if (agent.contains("MSIE")) { // IE瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐瀏覽器 filename = new String(fileName.getBytes(), "ISO8859-1"); } else if (agent.contains("Chrome")) { // google瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); } else { // 其它瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; }
然后根據傳遞的參數是否刪除模板文件,執行刪除的工具類方法deleteFile
方法實現
/** * 刪除文件 * * @param filePath 文件 * @return */ public static boolean deleteFile(String filePath) { boolean flag = false; File file = new File(filePath); // 路徑為文件且不為空則進行刪除 if (file.isFile() && file.exists()) { file.delete(); flag = true; } return flag; }
完整的文件操作工具類代碼
package com.ruoyi.common.utils.file; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import javax.servlet.http.HttpServletRequest; /** * 文件處理工具類 * * @author ruoyi */ public class FileUtils { public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; /** * 輸出指定文件的byte數組 * * @param filePath 文件路徑 * @param os 輸出流 * @return */ public static void writeBytes(String filePath, OutputStream os) throws IOException { FileInputStream fis = null; try { File file = new File(filePath); if (!file.exists()) { throw new FileNotFoundException(filePath); } fis = new FileInputStream(file); byte[] b = new byte[1024]; int length; while ((length = fis.read(b)) > 0) { os.write(b, 0, length); } } catch (IOException e) { throw e; } finally { if (os != null) { try { os.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } /** * 刪除文件 * * @param filePath 文件 * @return */ public static boolean deleteFile(String filePath) { boolean flag = false; File file = new File(filePath); // 路徑為文件且不為空則進行刪除 if (file.isFile() && file.exists()) { file.delete(); flag = true; } return flag; } /** * 文件名稱驗證 * * @param filename 文件名稱 * @return true 正常 false 非法 */ public static boolean isValidFilename(String filename) { return filename.matches(FILENAME_PATTERN); } /** * 下載文件名重新編碼 * * @param request 請求對象 * @param fileName 文件名 * @return 編碼后的文件名 */ public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { final String agent = request.getHeader("USER-AGENT"); String filename = fileName; if (agent.contains("MSIE")) { // IE瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); filename = filename.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐瀏覽器 filename = new String(fileName.getBytes(), "ISO8859-1"); } else if (agent.contains("Chrome")) { // google瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); } else { // 其它瀏覽器 filename = URLEncoder.encode(filename, "utf-8"); } return filename; } }
