Apache POI读取第三方Excel报ZipEntry异常 求自动修复方案
报错信息
java.io.IOException: Failed to read zip entry source
at org.apache.poi.openxml4j.opc.ZipPackage.(ZipPackage.java:103)
at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:324)
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:185)
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:144)
at com.apsec.groovy.job.util.POIUtil.openWorkbook(POIUtil.groovy:60)
at com.apsec.groovy.job.axiom.AxiomAToMReportV2Load.run(AxiomAToMReportV2Load.groovy:213)
at com.apsec.groovy.job.AbstractJob.run(AbstractJob.groovy:67)
at com.apsec.groovy.job.developer.LocalEnvironment.run(LocalEnvironment.groovy:106)
at com.apsec.groovy.job.axiom.AxiomAToMReportV2Load.main(AxiomAToMReportV2Load.groovy:359)
Caused by: java.util.zip.ZipException: invalid entry size (expected 0 but got 1053 bytes)
at java.util.zip.ZipInputStream.readEnd(ZipInputStream.java:384)
at java.util.zip.ZipInputStream.read(ZipInputStream.java:196)
at org.apache.poi.openxml4j.util.ZipSecureFile$ThresholdInputStream.read(ZipSecureFile.java:219)
at java.io.FilterInputStream.read(FilterInputStream.java:107)
at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource$FakeZipEntry.(ZipInputStreamZipEntrySource.java:132)
at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.(ZipInputStreamZipEntrySource.java:56)
at org.apache.poi.openxml4j.opc.ZipPackage.(ZipPackage.java:100)
... 8 more
环境与现有代码
- Apache POI版本:3.16
- 当前尝试的修复方法代码:
import org.apache.poi.ss.usermodel.Workbook import org.apache.poi.ss.usermodel.WorkbookFactory import org.apache.poi.openxml4j.util.ZipSecureFile import org.apache.poi.openxml4j.exceptions.InvalidFormatException import groovy.util.logging.Slf4j import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.io.FileNotFoundException @Slf4j static Workbook openAndRepairWorkbook(File file) throws FileNotFoundException, IOException, InvalidFormatException { FileInputStream fis = null Workbook workbook = null File repairedFile = new File(file.getParent(), "repaired_" + file.getName()) try { if (!file.exists() || !file.canRead()) { log.error("File does not exist or cannot be read: {}", file.getAbsolutePath()) throw new FileNotFoundException("File does not exist or cannot be read: " + file.getAbsolutePath()) } log.info("Attempting to open workbook from file: {}", file.getAbsolutePath()) fis = new FileInputStream(file) ZipSecureFile.setMinInflateRatio(0.0001) // Adjust the zip bomb detection threshold try { workbook = WorkbookFactory.create(fis) log.info("Workbook successfully opened.") // Save the workbook to a new file to "repair" it log.info("Saving workbook to repaired file: {}", repairedFile.getAbsolutePath()) FileOutputStream fos = new FileOutputStream(repairedFile) workbook.write(fos) fos.close() workbook.close() // Reopen the repaired file log.info("Reopening the repaired workbook.") fis = new FileInputStream(repairedFile) workbook = WorkbookFactory.create(fis) log.info("Repaired workbook successfully opened.") } catch (InvalidFormatException e) { log.error("InvalidFormatException encountered: {}", e.getMessage()) throw e } catch (IOException e) { log.error("IOException encountered: {}", e.getMessage()) throw e } } finally { if (fis != null) { try { fis.close() } catch (IOException e) { log.error("Error closing FileInputStream: {}", e.getMessage()) } } } return workbook }
已尝试的方案
- 手动修复:用Microsoft Excel打开问题文件并直接保存,文件即可被代码正常处理;
- 代码模拟修复:通过上述方法尝试打开后保存再重新读取,但仍报相同错误。
需求
- 定位该zip entry source相关IOException的根因;
- 实现可自动打开、修复并处理文件的代码方案。
解决建议
根因分析
报错核心是java.util.zip.ZipException: invalid entry size (expected 0 but got 1053 bytes),说明Excel文件(本质是ZIP包)内存在一个条目,其ZIP头声明的大小为0,但实际读取到1053字节。这是第三方生成Excel时的格式不规范导致:可能是写入ZIP条目时未正确更新头信息,或文件生成过程中出现截断、损坏,造成ZIP结构不一致。
你的代码修复方案无效,是因为POI在初始读取时就因ZIP结构错误抛出异常,根本没成功打开原文件,无法执行后续保存修复步骤。
代码修复方案
方案1:先修复ZIP结构再读取
借助Java原生ZIP工具,重新写入ZIP条目以修正结构错误:
@Slf4j static File repairZipFile(File damagedFile) throws IOException { File tempRepairedZip = new File(damagedFile.getParent(), "temp_repaired_" + damagedFile.getName()) ZipInputStream zis = new ZipInputStream(new FileInputStream(damagedFile)) ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(tempRepairedZip)) ZipEntry entry while ((entry = zis.getNextEntry()) != null) { try { // 创建新条目,忽略原条目的错误大小声明 ZipEntry newEntry = new ZipEntry(entry.getName()) zos.putNextEntry(newEntry) byte[] buffer = new byte[1024] int len while ((len = zis.read(buffer)) > 0) { zos.write(buffer, 0, len) } zos.closeEntry() } catch (ZipException e) { log.warn("跳过损坏的ZIP条目: {}", entry.getName(), e) continue } } zis.close() zos.close() return tempRepairedZip } static Workbook openAndRepairWorkbook(File file) throws FileNotFoundException, IOException, InvalidFormatException { if (!file.exists() || !file.canRead()) { log.error("文件不存在或无法读取: {}", file.getAbsolutePath()) throw new FileNotFoundException("文件不存在或无法读取: " + file.getAbsolutePath()) } // 先修复ZIP结构 File tempZip = repairZipFile(file) try { ZipSecureFile.setMinInflateRatio(0.0001) Workbook workbook = WorkbookFactory.create(new FileInputStream(tempZip)) // 保存为标准Excel文件 File finalRepairedFile = new File(file.getParent(), "repaired_" + file.getName()) FileOutputStream fos = new FileOutputStream(finalRepairedFile) workbook.write(fos) fos.close() // 删除临时修复文件 tempZip.delete() return workbook } finally { // 确保临时文件被清理 if (tempZip.exists()) { tempZip.delete() } } }
方案2:升级Apache POI版本
POI 3.16版本较老,后续版本(如4.1.2及以上)对损坏ZIP结构的兼容性更强,可能直接处理这类不规范文件。若条件允许,升级版本是更简便的方案。
方案3:使用OPCPackage手动修复
绕过WorkbookFactory,直接调用OPCPackage的修复逻辑:
import org.apache.poi.openxml4j.opc.OPCPackage import org.apache.poi.openxml4j.opc.PackageAccess @Slf4j static Workbook openDamagedWorkbook(File file) throws IOException, InvalidFormatException { OPCPackage pkg = OPCPackage.open(file, PackageAccess.READ_WRITE) // 触发POI内部修复逻辑 pkg.repair() Workbook workbook = WorkbookFactory.create(pkg) File repairedFile = new File(file.getParent(), "repaired_" + file.getName()) FileOutputStream fos = new FileOutputStream(repairedFile) workbook.write(fos) fos.close() pkg.close() return WorkbookFactory.create(new FileInputStream(repairedFile)) }
注意事项
- 修复后的文件可能丢失第三方工具添加的自定义元数据,但核心数据通常能保留;
- 临时文件需在finally块中清理,避免占用磁盘空间;
- 升级POI版本时,需注意API兼容性,部分旧方法可能已被废弃。
内容的提问来源于stack exchange,提问作者Baldie47

