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

Java EE Servlet下载Zip文件损坏问题排查求助

Servlet下载Zip文件损坏问题排查

开发Web服务器时,Servlet调用sendFile方法后,客户端下载的Zip文件出现损坏,服务器端生成的Zip文件正常,但客户端无法打开,使用unzip命令报错。

相关代码

Servlet调用代码

sendFile(directory, "s" + this.optimizerId + "-" + this.optimizerName + "-" + start.toDate(Defs.DB.DATE_PATTERN) + "-" + end.toDate(Defs.DB.DATE_PATTERN), FileExtension.NONE, ContentType.ZIP, response);

第一个sendFile重载方法

public default void sendFile(File file, String fileName, FileExtension extension, ContentType type, HttpServletResponse response) {
    if(type == ContentType.ZIP) {
        String[] files = file.list();
        if(files != null && files.length > 0) {
            byte[] bytes = FileUtils.getZipBytes(file, files);
            sendFile(new String(bytes), fileName, extension, type, response);
        }
    }
}

第二个sendFile重载方法

public default void sendFile(String content, String fileName, FileExtension extension, ContentType type, HttpServletResponse response) {
    response.setContentType(type.getExpression());
    response.setHeader("Content-Disposition", "attachment; filename=\"" + FileUtils.encodeForFileName(fileName) + "." + extension.getExtension() + "\"");
    OutputStream out = response.getOutputStream();
    out.write(content.getBytes());
    out.flush();
    out.close();
}

FileUtils.getZipBytes方法

public static byte[] getZipBytes(File directory, String[] files) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ZipOutputStream zos = new ZipOutputStream(baos);
    byte bytes[] = new byte[2048];
    for (String fileName : files) {
        FileInputStream fis = new FileInputStream(directory.getPath() + 
                System.getProperty("file.separator") + fileName);
        BufferedInputStream bis = new BufferedInputStream(fis);
        zos.putNextEntry(new ZipEntry(fileName));
        int bytesRead;
        while ((bytesRead = bis.read(bytes)) != -1) {
            zos.write(bytes, 0, bytesRead);
        }
        zos.closeEntry();
        bis.close();
        fis.close();
    }
    zos.flush();
    baos.flush();
    zos.close();
    baos.close();
    return baos.toByteArray();
}

客户端报错信息

Archive:  s192-Système U carquefou-2023-01-26 000000-2023-01-26 235959(1).zip

caution:  zipfile comment truncated
error [s192-Système U carquefou-2023-01-26 000000-2023-01-26 235959(1).zip]:  missing 3249308257 bytes in zipfile
  (attempting to process anyway)
error [s192-Système U carquefou-2023-01-26 000000-2023-01-26 235959(1).zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)

问题排查与修复

1. 核心问题:二进制数据转字符串导致损坏

Zip是二进制格式文件,绝对不能将二进制字节数组转成字符串再转回字节数组。第一个sendFile方法中,new String(bytes)会用JVM默认字符编码(如UTF-8)解析二进制字节,很多不可打印的二进制值会被替换成占位符(如�),后续调用content.getBytes()时,生成的字节数组已经和原Zip数据完全不一致,直接导致文件损坏。

2. 次要问题:流操作不规范与异常缺失

  • 第一个sendFile方法未处理非ZIP类型、空目录的情况,客户端会收到空响应
  • 流操作未使用try-with-resources,可能导致资源泄漏
  • 第二个sendFile方法直接关闭OutputStream,可能导致响应未完整发送,且未处理IOException

修复后的代码示例

新增字节数组处理的sendFile重载方法,替换字符串中转逻辑

public default void sendFile(File file, String fileName, FileExtension extension, ContentType type, HttpServletResponse response) throws IOException {
    if(type == ContentType.ZIP) {
        String[] files = file.list();
        if(files != null && files.length > 0) {
            byte[] bytes = FileUtils.getZipBytes(file, files);
            sendFile(bytes, fileName, extension, type, response);
        } else {
            // 处理空目录,返回404提示
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "No files available to zip");
        }
    } else {
        // 补充其他文件类型的处理逻辑
    }
}

// 新增处理二进制字节数组的重载方法
public default void sendFile(byte[] content, String fileName, FileExtension extension, ContentType type, HttpServletResponse response) throws IOException {
    response.setContentType(type.getExpression());
    response.setHeader("Content-Disposition", "attachment; filename=\"" + FileUtils.encodeForFileName(fileName) + "." + extension.getExtension() + "\"");
    // 设置Content-Length,让客户端知晓文件大小
    response.setContentLength(content.length);
    
    // 使用try-with-resources自动关闭流
    try(OutputStream out = response.getOutputStream()) {
        out.write(content);
        out.flush();
    }
}

优化FileUtils.getZipBytes方法的流操作

public static byte[] getZipBytes(File directory, String[] files) throws IOException {
    // try-with-resources自动关闭所有流
    try(ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(baos)) {
        byte bytes[] = new byte[2048];
        for (String fileName : files) {
            File fileToZip = new File(directory, fileName);
            try(FileInputStream fis = new FileInputStream(fileToZip);
                BufferedInputStream bis = new BufferedInputStream(fis)) {
                zos.putNextEntry(new ZipEntry(fileName));
                int bytesRead;
                while ((bytesRead = bis.read(bytes)) != -1) {
                    zos.write(bytes, 0, bytesRead);
                }
                zos.closeEntry();
            }
        }
        zos.finish(); // 完成Zip输出,确保所有数据写入ByteArrayOutputStream
        return baos.toByteArray();
    }
}

内容的提问来源于stack exchange,提问作者loupTerawatt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 14:20:40