java多文件壓縮下載的解決方法
Java多文件壓縮下載解決方案,供大家參考,具體內(nèi)容如下
需求:
會員運(yùn)營平臺經(jīng)過改版后頁面增加了許多全部下載鏈接,上周上線比較倉促,全部下載是一個直接下載ZIP壓縮文件的鏈接,每個ZIP壓縮文件都是由公司運(yùn)營人員將頁面需要下載的文件全部壓縮成一個ZIP壓縮文件,然后通過公司的交易運(yùn)營平臺上傳至文件資料系統(tǒng),會員運(yùn)營平臺則可以直接獲取ZIP壓縮文件地址進(jìn)行下載
下面是一個頁面示例:

需求分析:
通過上面需求和頁面可以分析出,公司運(yùn)營人員將頁面全部需要下載的文件進(jìn)行ZIP壓縮后上傳文件資料系統(tǒng)確實是一個緊急的解決方案,但是考慮到后面需求變更,頁面需要下載的文件也會跟著變更,每次變更需求,公司運(yùn)營人員都需要重新進(jìn)行壓縮文件,程序也需要進(jìn)行相應(yīng)的修改,這樣對于程序的維護(hù)性不友好,站在使用系統(tǒng)的客戶角度,每次都需要重新上傳,因此臨時解決方案不再符合軟件的良好擴(kuò)展性和操作方便,因此才有了對頁面需要全部下載的文件使用程序壓縮處理并下載。
解決思路:
第一步:前端傳遞Ids字符串
由于會員運(yùn)營系統(tǒng)顯示需要下載的文件是資料系統(tǒng)中的每條文件記錄的Id,因此前端頁面只需要將需要下載的所有文件Ids字符串(比如:'12,13,14')傳遞到后臺即可.
第二步:后臺處理
首先獲取到前端傳遞的ids字符串,將其轉(zhuǎn)換為Integer[]的ids數(shù)組,然后調(diào)用文件資料微服務(wù)根據(jù)id列表查詢對應(yīng)的文件記錄(包含文件類型和文件地址路徑等信息),獲取到所有需要下載的文件路徑后壓縮成ZIP格式的文件進(jìn)行下載。
具體實現(xiàn)壓縮下載方案:
第一種:先壓縮成ZIP格式文件,再下載
第二種:邊壓縮ZIP格式文件邊下載(直接輸出ZIP流)
前端具體實現(xiàn)代碼:
由于全部下載是一個a鏈接標(biāo)簽,于是使用Ajax異步下載,后來功能實現(xiàn)后點(diǎn)擊下載一點(diǎn)反應(yīng)都沒有,一度懷疑是后臺出錯,但是后臺始終沒有報錯,在網(wǎng)上看了一下Ajax異步不能下載文件(也就是Ajax不支持流類型數(shù)據(jù)),具體原因可以百度這篇博客,給到的解釋是:
原因
ajax的返回值類型是json,text,html,xml類型,或者可以說ajax的接收類型只能是string字符串,不是流類型,所以無法實現(xiàn)文件下載。但用ajax仍然可以獲得文件的內(nèi)容,該文件將被保留在內(nèi)存中,無法將文件保存到磁盤。這是因為JavaScript無法和磁盤進(jìn)行交互,否則這會是一個嚴(yán)重的安全問題,js無法調(diào)用到瀏覽器的下載處理機(jī)制和程序,會被瀏覽器阻塞。
解釋的還算是比較好的。后面會寫一篇=文章詳細(xì)分析Ajax異步下載解決方案。
接下來考慮使用form表單標(biāo)簽實現(xiàn),最終配合使用input標(biāo)簽實現(xiàn)了前端傳遞ids列表的問題,點(diǎn)擊a鏈接標(biāo)簽觸發(fā)提交form標(biāo)簽即可。

在每一個需要下載的文件增加一個隱藏的input標(biāo)簽,value值是這個文件的id值

具體點(diǎn)擊a鏈接標(biāo)簽提交表單的JS代碼:

后端具體實現(xiàn)代碼:
第一種方案實現(xiàn):

第二種方案實現(xiàn):

附上完整代碼:
壓縮下載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、根據(jù)ids查詢下載的文件地址列表
String ids = request().getParameter("ids");
if (StringUtils.isEmpty(ids)) {
return ;
}
//-- 將字符串?dāng)?shù)組改變?yōu)檎蛿?shù)組
Integer[] idsInteger = CompressDownloadUtil.toIntegerArray(ids);
List<FeFileCenter> fileCenters = feFileCenterService.getFeFileByIds(super.getExchangeId(), idsInteger);
if (CollectionUtils.isNotEmpty(fileCenters) && ObjectUtils.notEqual(idsInteger.length, fileCenters.size())) {
//-- 要下載文件Id數(shù)組個數(shù)和返回的文件地址個數(shù)不一致
return ;
}
//-- 2、轉(zhuǎn)換成文件列表
List<File> files = this.toFileList(fileCenters);
//-- 檢查需要下載多文件列表中文件路徑是否都存在
for (File file : files) {
if (!file.exists()) {
//-- 需要下載的文件中存在不存在地址
return ;
}
}
//-- 3、響應(yīng)頭的設(shè)置
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 {
//-- 將多個文件壓縮寫進(jìn)響應(yīng)的輸出流
CompressDownloadUtil.compressZip(files, response.getOutputStream());
} catch (IOException e) {
Xlogger.error(XMsgError.buildSimple(CompressDownloadUtil.class.getName(), "downloadallfiles", e));
}
}
/**
* 設(shè)置臨時生成的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列表轉(zhuǎn)換為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());
}
/**
* 獲取文件類型對應(yīng)存儲路徑
*
* @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() {}
/**
* 設(shè)置下載響應(yīng)頭
*
* @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;
}
/**
* 字符串轉(zhuǎn)換為整型數(shù)組
*
* @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));
// -- 設(shè)置壓縮方法
zipOutStream.setMethod(ZipOutputStream.DEFLATED);
//-- 將多文件循環(huán)寫入壓縮包
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是防止要下載的文件有重名的導(dǎo)致下載失敗
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) {
//-- 路徑為文件且不為空則進(jìn)行刪除
if (file.isFile() && file.exists()) {
file.delete();
}
}
}
測試
通過交易運(yùn)營平臺上傳測試資料

登錄會員運(yùn)營平臺進(jìn)行下載

下載下來的ZIP格式為文件

解壓后,打開文件是否可用:

總結(jié):
這個過程中出現(xiàn)了很多問題,后面會有文章逐步分析出錯和解決方案。
上述兩種方案都行,但是為了響應(yīng)速度更快,可以省略壓縮成ZIP的臨時文件的時間,因此采用了第二種解決方案。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java中BigDecimal的舍入模式解析(RoundingMode)
這篇文章主要介紹了Java中BigDecimal的舍入模式解析(RoundingMode),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
關(guān)于在IDEA中SpringBoot項目中activiti工作流的使用詳解
這篇文章主要介紹了關(guān)于在IDEA中SpringBoot項目中activiti工作流的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
JAVA實現(xiàn) SpringMVC方式的微信接入、實現(xiàn)簡單的自動回復(fù)功能
這篇文章主要介紹了JAVA實現(xiàn) SpringMVC方式的微信接入、實現(xiàn)簡單的自動回復(fù)功能的相關(guān)資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-11-11
MyBatis Plus 將查詢結(jié)果封裝到指定實體的方法步驟
這篇文章主要介紹了MyBatis Plus 將查詢結(jié)果封裝到指定實體的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

