内存与硬盘生成Zip不一致导致下载后文件损坏问题排查与解决
解决Web应用Zip下载文件损坏且体积异常的问题
问题背景
在Web应用中实现Zip下载功能,用户点击按钮触发下载,原实现代码如下:
前端Vue按钮代码
<template> <button name="exportButton" id="exportButton" @click="convertConfigurationToZip" :class="['button']">{{ exportConfig }} </button> </template> methods: { async convertConfigurationToZip() { // Stores the current config in "store.config" RestResource.convertConfigurationToZip(); const configData = store.config; await new Promise((resolve) => setTimeout(resolve, 2000)); // A Blob represents raw binary data const blob = new Blob([configData], {type: 'application/zip'}); // Use file-saver to trigger the download saveAs(blob, 'config') } }
RestResource函数代码
convertConfigurationToZip: () => { axios .get("http://localhost:8080/api/conversion/convertConfigurationToZip/") .then((response: any) => { console.log(response.data); store.config = response.data; }) .catch((error: any) => { console.log(error); }) }
后端Java Controller代码
package controller; import io.swagger.annotations.*; import jakarta.annotation.Resource; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.container.AsyncResponse; import jakarta.ws.rs.container.Suspended; import jakarta.ws.rs.core.Response; import services.ConversionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; // This Controller should be used for creating zip files that contains the ProCake-Configuration @Api(tags = "Conversion Controller", authorizations = { @Authorization(value = "basicAuth") }) @Path("/conversion") public class ConversionController { @Resource ExecutorService executorService; @ApiOperation(value = "Convert Config into Zip") @ApiResponses({ @ApiResponse(code = 200, message = "Config as Zip"), @ApiResponse(code = 503, message = "ProCAKE not started/configured properly") }) @GET @Path("/convertConfigurationToZip") public void convertConfigurationToZip(@Suspended final AsyncResponse response) { executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { try { response.resume(Response.status(200).entity(new ConversionService().convertConfigurationToZip()).build()); } catch (Exception e) { response.resume(e); } executorService.shutdown(); }); } }
ConversionService代码
package services; import de.uni_trier.wi2.procake.data.model.Model; import de.uni_trier.wi2.procake.data.model.ModelFactory; import de.uni_trier.wi2.procake.similarity.SimilarityModel; import de.uni_trier.wi2.procake.similarity.SimilarityModelFactory; import de.uni_trier.wi2.procake.utils.io.IOUtil; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.io.IOUtils; import org.apache.jena.sparql.exec.RowSet; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.zip.ZipInputStream; public class ConversionService { public byte[] convertConfigurationToZip() throws IOException { try { // Create Zip on Harddrive List<File> listOfFiles = new ArrayList<File>(); File hello = new File("pathToFile"); File world = new File("pathToFile"); listOfFiles.add(hello); listOfFiles.add(world); IOUtil.createZipFile("pathToZip", listOfFiles); // --------------------- // Create Zip File in Memory ByteArrayOutputStream baos = IOUtil.createZipFileInMemory(listOfFiles); // --------------------- // Compare both Zip Files byte[] zipContentHardDrive = Files.readAllBytes(Paths.get("pathToFile")); byte[] zipContentMemory = baos.toByteArray(); int minLength = Math.min(zipContentMemory.length, zipContentHardDrive.length); if (!Arrays.equals( Arrays.copyOf(zipContentMemory, minLength), Arrays.copyOf(zipContentHardDrive, minLength))) { throw new IOException("The Zip Files are different."); } // --------------------- return zipContentMemory; }catch (Exception e) { System.out.println("Could not create Zip File"); } return null; } }
IOUtil相关方法代码
public static ByteArrayOutputStream createZipFileInMemory(List<File> files) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream( byteArrayOutputStream); for (File file : files) { addZipEntry(zipArchiveOutputStream, file); } zipArchiveOutputStream.finish(); return byteArrayOutputStream; } private static void addZipEntry(ArchiveOutputStream archiveOutputStream, File file) throws IOException { String entryName = file.getName(); ArchiveEntry archiveEntry = new ZipArchiveEntry(file, entryName); archiveOutputStream.putArchiveEntry(archiveEntry); if (file.isFile()) { Files.copy(file.toPath(), archiveOutputStream); } archiveOutputStream.closeArchiveEntry(); }
问题现象
- 硬盘生成的Zip文件正常可打开
- 下载的Zip文件比硬盘版大200字节,且无法正常打开
- 内存生成的Zip与硬盘版二进制内容不一致,但单独调用
createZipFileInMemory方法时结果正常
修复方案
修改前端请求逻辑,在axios请求中设置responseType: "blob",直接使用响应的二进制数据创建Blob:
修改后的前端方法
methods: { async convertConfigurationToZip() { if (this.conversionInProgress) { return; } this.conversionInProgress = true; const response = await RestResource.convertConfigurationToZip(); const blob = new Blob([response.data], {type: 'application/zip'}); saveAs(blob, 'config.zip'); this.conversionInProgress = false; } }
修改后的RestResource函数
convertConfigurationToZip: async () => { return axios.get("http://localhost:8080/api/conversion/convertConfigurationToZip/", { responseType: "blob", // 确保响应被处理为二进制Blob }) .then((response) => { return response; }) .catch((error) => { console.error(error); throw error; }); }
原因分析
原代码中,后端返回的二进制Zip数据被axios默认解析为字符串并存储到store.config中,再用该字符串创建Blob时,二进制数据已被损坏,导致生成的Zip文件体积异常且无法打开。设置responseType: "blob"后,axios会直接保留响应的二进制数据,避免了字符串转换带来的损坏。
内容的提问来源于stack exchange,提问作者Frouks
相关产品推荐
相关产品推荐

