Vue + SpringBoot + zip4j實現文件的壓縮下載

最近在項目當中遇到一個需要解壓文件為zip,通過前端進行下載,這和常規的文件下載不同,在 zip 文件還會存在目錄,目錄中再存放文件,當時找了很多資料都不盡人意,所以自己寫了這篇文字供以後參考。SpringBoot 的版本為2.5.15,zip4j 的版本為 2.11.5

Zip4j

zip4j是一個功能強大的 Java 庫,專門用于處理 ZIP 文件格式。它提供了一系列易于使用的 API,使得開發人員可以方便地在 Java 應用程序中執行各種 ZIP 文件操作,包括創建、讀取、更新和提取 ZIP 文件等。GitHub地址 裡面說明了各種 API 的使用和示例。

思路

具體思路是打算通過 zip4j 創建一個 zip 文件,然後通過 API 在 zip 文件裡面創建目錄和文件。當時的業務需求是下載每一個文件的附錄文件,每一個文件都會有多個附錄文件,不可能去一個個下載,所以需壓縮為 zip 格式。具體的代碼如下:

/**

創建 zip 文件

@param ids 文件id集合
@return zip 文件的路徑
*/
private String createZipFile(String ids) {
Integer[] intArray = Convert.toIntArray(ids);

// 創建一個列表用于存放所有附件文件的路徑,後續統一壓縮到一個zip文件中
List<String> allFilePaths = new ArrayList<>();

// 循環查詢每個id對應的附件文件路徑并添加到列表中
// 裡面的代碼大家可能會覺得麻煩,不用細看,隻需要知道這層for循環是為了找到附錄文件在磁盤的地址
for (int id : intArray) {
List<SysDownloadCenterAttachment> attachmentList = sysDownloadCenterAttachmentMapper.selectList(new LambdaQueryWrapper<SysDownloadCenterAttachment>()
.eq(SysDownloadCenterAttachment::getDownloadCenterId, id));
if (!CollectionUtils.isEmpty(attachmentList)) {
List<String> filePaths = attachmentList.stream().map(attachment -> {
String filePath = scjtConfig.getUploadPath() + attachment.getLocation();
if (!StringUtils.startsWith(filePath, "D:")) {
filePath = "D:" + filePath;
}
return filePath;
}).collect(Collectors.toList());
allFilePaths.addAll(filePaths);
}
}

// 創建ZipFile對象,指定zip文件的路徑
ZipFile zipFile = new ZipFile("d:/filename.zip");

// 循環附錄文件地址
for (String filePath : allFilePaths) {
File file = new File(filePath);
if (file.exists()) {
// 獲取文件名(包含後綴)
String fileName = file.getName();
// 這裡假設附件所屬的id可以通過某種方式獲取,比如從文件路徑解析或者其他邏輯判斷,示例中用一個方法 getFileIdFromPath 來表示獲取id的邏輯,你需要根據實際情況替換
String folderNameInsideZip = getFileTitleFromPath(intArray, filePath);
// 構造在zip文件中存放的路徑,格式為 "id标識的文件夾名/原文件名"
try {

// 嘗試獲取目标文件夾在zip文件中的FileHeader
FileHeader folderHeader = zipFile.getFileHeader(folderNameInsideZip + "/");
log.info("文件夾在zip文件中的FileHeader:" + folderHeader);

// 如果zip文件中指定目錄不存在,則創建,這一步判斷是必須的,不然會導緻下一次文件添加動作會覆蓋之前的文件
if (Objects.isNull(folderHeader)) {
new File(folderNameInsideZip).mkdirs(); // 創建文件夾
ZipParameters folderPar = new ZipParameters();
folderPar.setCompressionLevel(CompressionLevel.NORMAL);
folderPar.setEncryptFiles(false);
zipFile.addFolder(new File(folderNameInsideZip), folderPar);
log.info("文件夾已成功添加到zip文件中。");
}

// 創建ZipParameters對象,用于設置添加文件的參數
ZipParameters parameters = new ZipParameters();
// 設置壓縮方法,這裡使用默認壓縮方法
parameters.setCompressionMethod(CompressionMethod.DEFLATE);
// 設置壓縮級别,這裡使用默認壓縮級别
parameters.setCompressionLevel(CompressionLevel.NORMAL);
// 設置添加文件在zip文件中的路徑,格式為 "文件夾名/文件名"
parameters.setFileNameInZip(folderNameInsideZip + "/" + file.getName());

// 将文件添加到zip文件中的指定文件夾路徑下
zipFile.addFile(file, parameters);
log.info("文件已成功添加到zip文件中的指定文件夾。");
} catch (ZipException e) {
throw new RuntimeException(e);
}
}
}

return zipFile.getFile().getAbsolutePath();
}

上面代碼運行完畢,就會在d:/filename.zip的位置存在一個 zip 文件,打開後會看到目錄結構和文件信息。接下來就需要前端來下載這個 zip 文件,我這裡建立下載後就删除這個 zip 文件,這樣下次重新生成時就不會出現文件合并的問題。後端下載代碼如下:

`/**

下載文件的附件,附件較多 壓縮為zip下載

@param ids 文件id集合
@param response
@return
*/
@Override
@SneakyThrows
public ResponseEntity<Resource> downloadZip(String ids, HttpServletResponse response) {
// 創建zip文件
String zipFilePath = createZipFile(ids);

File file = new File(zipFilePath);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=sample.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(file.length())
.body(resource);

}

前段代碼如下:

export function downloadCenterZip(ids) { return request({ url:/system/download/center/downloadZip`,
method: 'post',
data: ids,
responseType: 'blob' // 标識返回類型為blob
})
}

`/**

下載按鈕操作
*/
function handleDownloadZip() {
downloadCenterZip(ids.value.toString()).then(res => {
console.log(res)
const url = window.URL.createObjectURL(new Blob([res], {type: 'application/zip'}));
let a = document.createElement('a')
a.href = url
a.download = 'download.zip'
a.click()
URL.revokeObjectURL(url) // 釋放内存
a.remove()
})
}

總結

對于其他格式文件的下載,其實思路都差不多,後端都需要返回一個blob或者byte數組,然後前端使用超鍊接的方式來觸發下載。

添加新評論

暱稱
郵箱
網站