Java ZipOutputStream 创建具有部分重复内容的损坏文件

问题描述 投票:0回答:1

我有一个 Spring Boot 3 项目(除了其他事情)必须将一些 CSV 文件保存到磁盘,然后从这些文件创建一个 ZIP 到另一个文件夹作为备份。 CSV 文件已正确生成。我使用 Jackson CSV 映射器为每个文件(3 个基类)单独生成 CSV 内容,然后使用 ZipOutputStream 将所有 3 个文件放入一个 zip 中。在磁盘上检查时,生成的 CSV 文件是正确的,但 zip 中的同一文件不正确。从原始文件中采样正确的 CSV 内容:

"ID";"DATE";"REC_STATUS";"ORIG_ID";"PARAM_NAME";"PARAM_VALUE"
"109";"2023-11-27";"0";"116";"transfer1 param";"transfer1 value"
"110";"2023-11-27";"0";"116";"transfer2";"transfer2 value2"
"111";"2023-11-27";"0";"117";"transfer1 param";"transfer1 value"
"112";"2023-11-27";"0";"117";"transfer2";"transfer2 value2"
"113";"2023-11-27";"0";"118";"transfer1 param";"transfer1 value"
"114";"2023-11-27";"0";"118";"transfer2";"transfer2 value2"
"115";"2023-11-27";"0";"119";"transfer1 param";"transfer1 value"
"116";"2023-11-27";"0";"119";"transfer2";"transfer2 value2"
"117";"2023-11-27";"0";"120";"param name1";"param1 value1"
"118";"2023-11-27";"0";"120";"name2";"value2"
"119";"2023-11-27";"0";"121";"param name1";"param1 value1"
"120";"2023-11-27";"0";"121";"name2";"value2"
"121";"2023-11-27";"0";"122";"param name1";"param1 value1"
"122";"2023-11-27";"0";"122";"name2";"value2"
"123";"2023-11-27";"0";"123";"param name1";"param1 value1"
"124";"2023-11-27";"0";"123";"name2";"value2"
"125";"2023-11-27";"0";"124";"param name1";"param1 value1"
"126";"2023-11-27";"0";"124";"name2";"value2"

从 zip 中获取的同一文件包含损坏的数据:

"ID";"DATE";"REC_STATUS";"ORIG_ID";"PARAM_NAME";"PARAM_VALUE"
"109";"2023-11-27";"0";"116";"transfer1 param";"transfer1 value"
"110";"2023-11-27";"0";"116";"transfer2";"transfer2 value2"
"111";"2023-11-27";"0";"117";"transfer1 param";"transfer1 value"
"112";"2023-11-27";"0";"117";"transfer2";"transfer2 value2"
"113";"2023-11-27";"0";"118";"transfer1 param";"transfer1 value"
"114";"2023-11-27";"0";"118";"transfer2";"transfer2 value2"
"115";"2023-11-27";"0";"119";"transfer1 param";"transfer1 value"
"116";"2023-11-27";"0";"119";"transfer2";"transfer2 value2"
"117";"2023-11-27";"0";"120";"param name1";"param1 value1"
"118";"2023-11-27";"0";"120";"name2";"value2"
"119";"2023-11-27";"0";"121";"param name1";"param1 value1"
"120";"2023-11-27";"0";"121";"name2";"value2"
"121";"2023-11-27";"0";"122";"param name1";"param1 value1"
"122";"2023-11-27";"0";"122";"name2";"value2"
"123";"2023-11-27";"0";"123";"param name1";"param1 value1"
"124";"2023-11-27";"0";"123";"name2";"value2"
"125";"2023-11-27";"0";"124";"param name1";"param1 value1"
"126";"2023-11-27";"0";"124";"name2";"value2"
109";"2023-11-27";"0";"116";"transfer1 param";"transfer1 value"
"110";"2023-11-27";"0";"116";"transfer2";"transfer2 value2"
"111";"2023-11-27";"0";"117";"transfer1 param";"transfer1 value"
"112";"2023-11-27";"0";"117";"transfer2";"transfer2 value2"
"113";"2023-11-27";"0";"118";"transfer1 param";"transfer1 value"
"114";"2023-11-27";"0";"118";"transfer2";"transfer2 value2"
"115";"2023-11-27";"0";"119";"transfer1 param";"transfer1 value"
"116";"2023-11-27";"0";"119";"transfer2";"transfer2 value2"
"117";"2023-11-27";"0";"120";"param name1";"param1 value1"
"118";"2023-11-27";"0";"120";"name2";"value2"
"119";"2023-11-27";"0";"121";"param name1";"param1 value1"
"120";"2023-11-27";"0";"121";"name2";"value2"
"121";"2023-11-27";"0";"122";"param name1";"param1 value1"
"122";"2023-11-27";"0";"122";"name2";"value2"
"123";"2023-11-27";"0";"123";"param name1";"param1 value1"
"124";"2023-11-27";"0";"123";"name2";"value2"
"

注意 CSV 文件似乎在中间重复,但该行中没有前导“,并且最后一行中也有一个不需要的”。

CSV 生成:

private String collectValuesToCSV(List<? extends Object> values, CsvMapper csvMapper, CsvSchema csvSchema) {
    String result;
    
    try (StringWriter strW = new StringWriter();
            SequenceWriter seqW = csvMapper.writer(csvSchema).writeValues(strW)) {
        for (Object value : values) {
            seqW.write(value);
        }
        
        seqW.flush();
        strW.flush();
    
        result = strW.toString();
    } catch (IOException e) {
        log.fatal(String.format("%s - Unable to generate CSV", LOG_PREFIX), e);
        throw e;
    }
    
    return result;
}

// usage in other parts of the code
CsvMapper csvMapper = CsvMapper.builder()
        .enable(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS)
        .enable(CsvGenerator.Feature.ALWAYS_QUOTE_EMPTY_STRINGS)
        .build();

CsvSchema csvSchema = csvMapper
        .schemaFor(MyDTO.class)
        .withColumnSeparator(';')
        .withHeader();

List<MyDTO> objects = generateObjects();

String myCsv = collectValuesToCSV(objects, csvMapper, csvSchema);

byte[] contents = myCsv.getBytes(StandardCharsets.UTF_8);

// optionally the contents may be encrypted, so a byte array is written to disk,
// but the same problem occurs even without encryption, simply writing this byte[] to disk

Path filePath = Path.of(config.baseFolder, "output_" + dateStr + ".csv");
Files.write(filePath, contents);

创建 ZIP 文件:

private void zipCsvFiles(String dateStr, Path path1, Path path2, Path path3) throws IOException {
    String zipFileName = Path.of(config.zipFolder, "output_" + dateStr + ".zip").toString();
    
    try (FileOutputStream fos = new FileOutputStream(zipFileName);
            ZipOutputStream zipOut = new ZipOutputStream(fos)) {
        addZipEntry(zipOut, path1);
        addZipEntry(zipOut, path2);
        addZipEntry(zipOut, path3);
    }
}

private void addZipEntry(ZipOutputStream zipOut, Path path) throws IOException {
    try (FileInputStream fis = new FileInputStream(path.toFile()) {
        ZipEntry entry = new ZipEntry(path.getFileName().toString());
        zipOut.putNextEntry(entry);

        byte[] bytes = new byte[1024];

        while ((fis.read(bytes)) >= 0) {
            zipOut.write(bytes);
        }
        
        zipOut.closeEntry();
    }
}

生成的 zip 看起来不错,它显示了 3 个文件,并且文件名正确。但是当打开其中任何一个时,内容都会被破坏...我研究了有关此主题的其他问题,并已经尝试了一些想法(手动调用流上的刷新和完成),但据我所知,这些是多余的close() 方法根据需要调用它们,并且当我处理 try-with-resources 中的所有内容时,总是调用 close() 方法。所以我不确定是什么导致了这个问题。

spring-boot csv zip
1个回答
0
投票

问题出在

addZipEntry
中的以下几行:

    byte[] bytes = new byte[1024];

    while ((fis.read(bytes)) >= 0) {
        zipOut.write(bytes);
    }

方法

read
返回读取的字节数,但您不使用它,并且始终将整个数组发送到
zipOut

将其更改为以下内容:

    byte[] bytes = new byte[1024];
    int read = fis.read(bytes);
            
    while (read >= 0) {
        zipOut.write(bytes, 0, read);
        read = fis.read(bytes);
    }
© www.soinside.com 2019 - 2024. All rights reserved.