您好,登錄后才能下訂單哦!
Java多文件壓縮下載解決方案,供大家參考,具體內容如下
需求:
會員運營平臺經過改版后頁面增加了許多全部下載鏈接,上周上線比較倉促,全部下載是一個直接下載ZIP壓縮文件的鏈接,每個ZIP壓縮文件都是由公司運營人員將頁面需要下載的文件全部壓縮成一個ZIP壓縮文件,然后通過公司的交易運營平臺上傳至文件資料系統,會員運營平臺則可以直接獲取ZIP壓縮文件地址進行下載
下面是一個頁面示例:
需求分析:
通過上面需求和頁面可以分析出,公司運營人員將頁面全部需要下載的文件進行ZIP壓縮后上傳文件資料系統確實是一個緊急的解決方案,但是考慮到后面需求變更,頁面需要下載的文件也會跟著變更,每次變更需求,公司運營人員都需要重新進行壓縮文件,程序也需要進行相應的修改,這樣對于程序的維護性不友好,站在使用系統的客戶角度,每次都需要重新上傳,因此臨時解決方案不再符合軟件的良好擴展性和操作方便,因此才有了對頁面需要全部下載的文件使用程序壓縮處理并下載。
解決思路:
第一步:前端傳遞Ids字符串
由于會員運營系統顯示需要下載的文件是資料系統中的每條文件記錄的Id,因此前端頁面只需要將需要下載的所有文件Ids字符串(比如:'12,13,14')傳遞到后臺即可.
第二步:后臺處理
首先獲取到前端傳遞的ids字符串,將其轉換為Integer[]的ids數組,然后調用文件資料微服務根據id列表查詢對應的文件記錄(包含文件類型和文件地址路徑等信息),獲取到所有需要下載的文件路徑后壓縮成ZIP格式的文件進行下載。
具體實現壓縮下載方案:
第一種:先壓縮成ZIP格式文件,再下載
第二種:邊壓縮ZIP格式文件邊下載(直接輸出ZIP流)
前端具體實現代碼:
由于全部下載是一個a鏈接標簽,于是使用Ajax異步下載,后來功能實現后點擊下載一點反應都沒有,一度懷疑是后臺出錯,但是后臺始終沒有報錯,在網上看了一下Ajax異步不能下載文件(也就是Ajax不支持流類型數據),具體原因可以百度這篇博客,給到的解釋是:
原因
ajax的返回值類型是json,text,html,xml類型,或者可以說ajax的接收類型只能是string字符串,不是流類型,所以無法實現文件下載。但用ajax仍然可以獲得文件的內容,該文件將被保留在內存中,無法將文件保存到磁盤。這是因為JavaScript無法和磁盤進行交互,否則這會是一個嚴重的安全問題,js無法調用到瀏覽器的下載處理機制和程序,會被瀏覽器阻塞。
解釋的還算是比較好的。后面會寫一篇=文章詳細分析Ajax異步下載解決方案。
接下來考慮使用form表單標簽實現,最終配合使用input標簽實現了前端傳遞ids列表的問題,點擊a鏈接標簽觸發提交form標簽即可。
在每一個需要下載的文件增加一個隱藏的input標簽,value值是這個文件的id值
具體點擊a鏈接標簽提交表單的JS代碼:
后端具體實現代碼:
第一種方案實現:
第二種方案實現:
附上完整代碼:
壓縮下載Controller
package com.huajin.jgoms.controller.user; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.huajin.baymax.logger.XMsgError; import com.huajin.baymax.logger.Xlogger; import com.huajin.common.util.UUIDUtil; import com.huajin.exchange.domain.sys.FeFileCenter; import com.huajin.exchange.enums.sys.SysParamKey; import com.huajin.exchange.po.sys.SysParamPo; import com.huajin.jgoms.controller.HjBaseController; import com.huajin.jgoms.service.FeFileCenterService; import com.huajin.jgoms.service.SysParamService; import com.huajin.jgoms.util.CompressDownloadUtil; /** * 壓縮下載文件 * * @author hongwei.lian * @date 2018年9月6日 下午6:29:05 */ @Controller @RequestMapping("/compressdownload") public class CompressDownloadController extends HjBaseController { @Autowired private FeFileCenterService feFileCenterService; @Autowired private SysParamService sysParamService; /** * 多文件壓縮下載 * * @author hongwei.lian * @date 2018年9月6日 下午6:28:56 */ @RequestMapping("/downloadallfiles") public void downloadallfiles() { //-- 1、根據ids查詢下載的文件地址列表 String ids = request().getParameter("ids"); if (StringUtils.isEmpty(ids)) { return ; } //-- 將字符串數組改變為整型數組 Integer[] idsInteger = CompressDownloadUtil.toIntegerArray(ids); List<FeFileCenter> fileCenters = feFileCenterService.getFeFileByIds(super.getExchangeId(), idsInteger); if (CollectionUtils.isNotEmpty(fileCenters) && ObjectUtils.notEqual(idsInteger.length, fileCenters.size())) { //-- 要下載文件Id數組個數和返回的文件地址個數不一致 return ; } //-- 2、轉換成文件列表 List<File> files = this.toFileList(fileCenters); //-- 檢查需要下載多文件列表中文件路徑是否都存在 for (File file : files) { if (!file.exists()) { //-- 需要下載的文件中存在不存在地址 return ; } } //-- 3、響應頭的設置 String downloadName = UUIDUtil.getUUID() + ".zip"; HttpServletResponse response = CompressDownloadUtil.setDownloadResponse(super.response(), downloadName); //-- 4、第一種方案: //-- 指定ZIP壓縮包路徑 // String zipFilePath = this.setZipFilePath(downloadName); // try { // //-- 將多個文件壓縮到指定路徑下 // CompressDownloadUtil.compressZip(files, new FileOutputStream(zipFilePath)); // //-- 下載壓縮包 // CompressDownloadUtil.downloadFile(response.getOutputStream(), zipFilePath); // //-- 刪除臨時生成的ZIP文件 // CompressDownloadUtil.deleteFile(zipFilePath); // } catch (IOException e) { // Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadallfiles", e)); // } //-- 5、第二種方案: try { //-- 將多個文件壓縮寫進響應的輸出流 CompressDownloadUtil.compressZip(files, response.getOutputStream()); } catch (IOException e) { Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadallfiles", e)); } } /** * 設置臨時生成的ZIP文件路徑 * * @param fileName * @return * @author hongwei.lian * @date 2018年9月7日 下午3:54:13 */ private String setZipFilePath(String fileName) { String zipPath = sysParamService.getCompressDownloadFilePath(); File zipPathFile = new File(zipPath); if (!zipPathFile.exists()) { zipPathFile.mkdirs(); } return zipPath + File.separator + fileName; } /** * 將fileCenters列表轉換為File列表 * * @param fileCenters * @return * @author hongwei.lian * @date 2018年9月6日 下午6:54:16 */ private List<File> toFileList(List<FeFileCenter> fileCenters) { return fileCenters.stream() .map(feFileCenter -> { //-- 獲取每個文件的路徑 String filePath = this.getSysFilePath(feFileCenter.getFileTypeId()); return new File(filePath + feFileCenter.fileLink());}) .collect(Collectors.toList()); } /** * 獲取文件類型對應存儲路徑 * * @param fileTypeId * @return * @author hongwei.lian * @date 2018年9月5日 下午2:01:53 */ private String getSysFilePath(Integer fileTypeId){ SysParamPo sysmParam = sysParamService.getByParamKey(SysParamKey.FC_UPLOAD_ADDRESS.value); String filePath = Objects.nonNull(sysmParam) ? sysmParam.getParamValue() : ""; return filePath + fileTypeId + File.separator; } }
壓縮下載工具類
package com.huajin.jgoms.util; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.servlet.http.HttpServletResponse; import com.huajin.baymax.logger.XMsgError; import com.huajin.baymax.logger.Xlogger; /** * 壓縮下載工具類 * * @author hongwei.lian * @date 2018年9月6日 下午6:34:56 */ public class CompressDownloadUtil { private CompressDownloadUtil() {} /** * 設置下載響應頭 * * @param response * @return * @author hongwei.lian * @date 2018年9月7日 下午3:01:59 */ public static HttpServletResponse setDownloadResponse(HttpServletResponse response, String downloadName) { response.reset(); response.setCharacterEncoding("utf-8"); response.setContentType("application/octet-stream"); response.setHeader("Content-Disposition", "attachment;fileName*=UTF-8''"+ downloadName); return response; } /** * 字符串轉換為整型數組 * * @param param * @return * @author hongwei.lian * @date 2018年9月6日 下午6:38:39 */ public static Integer[] toIntegerArray(String param) { return Arrays.stream(param.split(",")) .map(Integer::valueOf) .toArray(Integer[]::new); } /** * 將多個文件壓縮到指定輸出流中 * * @param files 需要壓縮的文件列表 * @param outputStream 壓縮到指定的輸出流 * @author hongwei.lian * @date 2018年9月7日 下午3:11:59 */ public static void compressZip(List<File> files, OutputStream outputStream) { ZipOutputStream zipOutStream = null; try { //-- 包裝成ZIP格式輸出流 zipOutStream = new ZipOutputStream(new BufferedOutputStream(outputStream)); // -- 設置壓縮方法 zipOutStream.setMethod(ZipOutputStream.DEFLATED); //-- 將多文件循環寫入壓縮包 for (int i = 0; i < files.size(); i++) { File file = files.get(i); FileInputStream filenputStream = new FileInputStream(file); byte[] data = new byte[(int) file.length()]; filenputStream.read(data); //-- 添加ZipEntry,并ZipEntry中寫入文件流,這里,加上i是防止要下載的文件有重名的導致下載失敗 zipOutStream.putNextEntry(new ZipEntry(i + file.getName())); zipOutStream.write(data); filenputStream.close(); zipOutStream.closeEntry(); } } catch (IOException e) { Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadallfiles", e)); } finally { try { if (Objects.nonNull(zipOutStream)) { zipOutStream.flush(); zipOutStream.close(); } if (Objects.nonNull(outputStream)) { outputStream.close(); } } catch (IOException e) { Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadallfiles", e)); } } } /** * 下載文件 * * @param outputStream 下載輸出流 * @param zipFilePath 需要下載文件的路徑 * @author hongwei.lian * @date 2018年9月7日 下午3:27:08 */ public static void downloadFile(OutputStream outputStream, String zipFilePath) { File zipFile = new File(zipFilePath); if (!zipFile.exists()) { //-- 需要下載壓塑包文件不存在 return ; } FileInputStream inputStream = null; try { inputStream = new FileInputStream(zipFile); byte[] data = new byte[(int) zipFile.length()]; inputStream.read(data); outputStream.write(data); outputStream.flush(); } catch (IOException e) { Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadZip", e)); } finally { try { if (Objects.nonNull(inputStream)) { inputStream.close(); } if (Objects.nonNull(outputStream)) { outputStream.close(); } } catch (IOException e) { Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadZip", e)); } } } /** * 刪除指定路徑的文件 * * @param filepath * @author hongwei.lian * @date 2018年9月7日 下午3:44:53 */ public static void deleteFile(String filepath) { File file = new File(filepath); deleteFile(file); } /** * 刪除指定文件 * * @param file * @author hongwei.lian * @date 2018年9月7日 下午3:45:58 */ public static void deleteFile(File file) { //-- 路徑為文件且不為空則進行刪除 if (file.isFile() && file.exists()) { file.delete(); } } }
測試
通過交易運營平臺上傳測試資料
登錄會員運營平臺進行下載
下載下來的ZIP格式為文件
解壓后,打開文件是否可用:
總結:
這個過程中出現了很多問題,后面會有文章逐步分析出錯和解決方案。
上述兩種方案都行,但是為了響應速度更快,可以省略壓縮成ZIP的臨時文件的時間,因此采用了第二種解決方案。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。