You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

内存与硬盘生成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 03:00:54