RAR/ZIP文件解压(兼容RAR5)

  • 前言:

     记录压缩包解压功能开发过程遇见的一些问题,及最终的解决方案;
    
  • 原始需求:

     客户提出需要批量上传文档,上传文件为包含一系列文件的压缩包,格式为zip或rar;
    
  • 历史实现方式:

    zip格式:使用net.lingala.zip4j.core.ZipFile包下的api进行解压:ZipFile.extractAll,默认设置”GBK“编码,然后通过FileHeader.getFileName的文件名有没有乱码来设置解压时的编码”UTF-8“;
    存在的问题:因为我们粗暴的设置了一种编码,如果压缩包里的文件名包含了不同的编码,会导致某些文件解压出来后文件名乱码;
    
    rar格式:使用de.innosystec.unrar.Archive包下的api进行解压:Archive.extractFile;
    存在的问题:Archive不支持解析RAR5格式的压缩包;
    
  • 初步改进后的实现:

    在原来的基础上,增加通过命令调用解压工具(unrar/unzip)进行解压;
    存在的问题:zip格式的压缩包超过80M或rar格式的压缩包超过30M就只解压限制之内的文件出来,超过限制的文件会不解压;(限制原因未明,在服务器上调用命令解压是可以全部解压的,但程序调用就会有限制,怀疑JVM有关);
    
  • 再次优化后的实现:

    zip格式:
    	通过查询资料,可以把压缩包中的所有文件的FileHeader拿出来进行单独解压,进行到这一步发现文件名还是可能出现乱码;通过阅读ZipFile源码发现,api内部会判断文件名是否是UTF-8编码,并回填到fileHeader.isFileNameUTF8Encoded属性中,所以先默认使用GBK编码,在遍历解压文件时,根据fileHeader.isFileNameUTF8Encoded判断是否是UTF-8,如果是则修改ZipFile.Charset,同时修改FileHeader.FileName为正确的文件名称,这样就可以防止压缩包中包含不同编码的文件名乱码;
    	存在的问题:在使用过程中发现,台湾客户发回来的压缩包发现在转换文件名的时候,已经乱码文件名无法转换为源文件名,会解压失败:File header and local file header mismatch;
    
  • 最终的实现:

    rar/zip格式:使用net.sf.sevenzipjbinding包下的api进行解压,需要自定义一个MyExtractCallback类去实现IArchiveExtractCallback,并重写getStream方法,此方式兼容rar5及rar5以下版本的解压;
    
  • 实现代码:

maven依赖:

<dependency>
	<groupId>net.sf.sevenzipjbinding</groupId>
	<artifactId>sevenzipjbinding</artifactId>
	<version>16.02-2.01</version>
</dependency>
<dependency>
	<groupId>net.sf.sevenzipjbinding</groupId>
	<artifactId>sevenzipjbinding-all-platforms</artifactId>
	<version>16.02-2.01</version>
</dependency>

1、自定义ExtractCallback类,实现IArchiveExtractCallback,重写getStream;

import net.sf.sevenzipjbinding.*;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;

public class MyExtractCallback implements IArchiveExtractCallback {

    private static final Logger LOG = LoggerFactory.getLogger(MyExtractCallback.class);

    private int index;
    private IInArchive inArchive;
    private String outDir;
    private String errorInfo;// 输出异常信息

    public MyExtractCallback(IInArchive inArchive, String outDir) {
        this.inArchive = inArchive;
        this.outDir = outDir;
    }

    @Override
    public void setCompleted(long arg0) throws SevenZipException {
    }

    @Override
    public void setTotal(long arg0) throws SevenZipException {
    }

    @Override
    public ISequentialOutStream getStream(int index, ExtractAskMode extractAskMode) throws SevenZipException {
        this.index = index;
        String archivePath = (String) inArchive.getProperty(index, PropID.PATH);
        final boolean isFolder = (boolean) inArchive.getProperty(index, PropID.IS_FOLDER);
        final String path;
        try {
            // 判断是否是ISO-8859-1编码,如果是则转换为gbk
            if (archivePath.equals(new String(archivePath.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.ISO_8859_1))) {
                archivePath = new String(archivePath.getBytes(StandardCharsets.ISO_8859_1), CHARSET_GBK);
            }
            path = archivePath;
            String savePath = outDir + File.separator + path;
            File outFile = FileUtil.getFile(savePath);
            if (!outFile.exists()) {
                if (isFolder) {
                    FileUtils.forceMkdir(outFile);
                } else {
                    FileUtils.forceMkdir(outFile.getParentFile());
                    outFile.createNewFile();
                }
            }
        } catch (UnsupportedEncodingException e) {
            this.errorInfo = "文件[" + archivePath + "]的名称编码不正确,请修改为GBK/UTF-8格式!";
            throw new SevenZipException(e.getMessage(), e);
        } catch (IOException e) {
            this.errorInfo = "文件[" + archivePath + "]解压失败,请检查:" + e.getMessage();
            throw new SevenZipException(e.getMessage(), e);
        }
        return data -> {
            if (!isFolder) {
                File file = FileUtil.getFile(outDir + File.separator + path);
                this.saveToFile(file, data);
            }
            return data.length;
        };
    }

    @Override
    public void prepareOperation(ExtractAskMode arg0) throws SevenZipException {
    }

    @Override
    public void setOperationResult(ExtractOperationResult extractOperationResult) throws SevenZipException {

    }

    private boolean saveToFile(File file, byte[] msg) {
        File parent = file.getParentFile();
        if ((!parent.exists()) && (!parent.mkdirs())) {
            return false;
        }
        try (OutputStream fos = new FileOutputStream(file, true)) {
            fos.write(msg);
            fos.flush();
            return true;
        } catch (IOException e) {
            LOG.error("保存文件失败:{}", e);
            return false;
        }
    }

	public String getErrorInfo(){
        return errorInfo;
    }
}

2、解压文件:

	/**
     * 解压文件
     *
     * @param targetPath    解压路径
     * @param sourceFile    源文件
     * @param archiveFormat 压缩格式,zip文件设置ArchiveFormat.ZIP,rar和rar5可不设置值
     * @throws Exception
     */
    public static void unRarOrZipFiles(String targetPath, String sourceFile, ArchiveFormat archiveFormat) throws Exception {
        MyExtractCallback extractCallback = null;
        try (
                RandomAccessFile randomAccessFile = new RandomAccessFile(sourceFile, "rw");
                IInArchive archive = SevenZip.openInArchive(archiveFormat,
                        new RandomAccessFileInStream(randomAccessFile))
        ) {
            int[] in = new int[archive.getNumberOfItems()];
            for (int i = 0; i < in.length; i++) {
                in[i] = i;
            }
            extractCallback = new MyExtractCallback(archive, targetPath);
            archive.extract(in, false, extractCallback);
        } catch (IOException e) {
            if (e.getMessage().contains(RAR_FILE_NAME_TOO_LONG_ERROR)) {
                throw new BaseTextException(TrustBaseException.ERROR_IO_EXCEPTION,
                        "文件名长度超过操作系统限制,请检查!");
            } else if (!e.getMessage().contains(RAR_FILE_NAME_TOO_LONG_ERROR) && extractCallback != null) {
                throw new BaseTextException(TrustBaseException.ERROR_IO_EXCEPTION, extractCallback.getErrorInfo());
            } else {
                throw e;
            }
        }
    }
### 如何使用 Jupyter Notebook 打开 `.ipynb` 文件 #### 启动 Jupyter Notebook 服务 通过命令行启动 Jupyter Notebook 的服务器,可以在浏览器中访问并操作已有的 `.ipynb` 文件。具体做法是在终端或命令提示符下输入 `jupyter notebook` 命令来开启本地的服务[^2]。 ```bash jupyter notebook ``` 执行上述命令之后,Jupyter Notebook 应用程序将会自动在默认的网页浏览器里打开一个新的标签页,并展示当前工作目录下的文件列表以及一些可用的操作选项。 #### 浏览和加载现有的 `.ipynb` 文件 一旦进入了 Jupyter Notebook 的界面,在左侧可以看到一个文件夹视图,这里会显示计算机上该路径下的所有文件与子文件夹。找到想要打开的那个 `.ipynb` 笔记本文件后点击它即可载入到新的页面中进行交互式的编程、修改或是运行其中的内容[^1]。 如果希望直接在线预览而不必安装额外软件的话,则可利用像[nbviewer](https://nbviewer.jupyter.org/)这样的第三方平台上传或者链接分享`.ipynb`文档来进行阅读。 对于那些需要先执行再转换成其他格式的情况,比如HTML, 可以采用如下命令完成指定笔记本文件(`mynotebook.ipynb`)的自动化执行与保存过程: ```bash jupyter nbconvert --to notebook --execute mynotebook.ipynb ``` 此命令不仅能够确保所有的代码单元格都被正确评估过一遍,而且还会保留原有的结构作为另一个同名但带有 `_all_output` 后缀的新版`.ipynb`文件存放在同一位置[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值