最近遇到一个这样的需求:传一个压缩包给后台,后台保存后解压读取里面的文件。在这里做个记录

1、MultipartFile上传文件

文件上传有很多方法,这里推荐一种,代码:

@PostMapping(value = "/import", headers = "content-type=multipart/*")
    public HttpResponse importSqlLite(@RequestParam("file") MultipartFile file) {
             String path = "C:/filePath/";            File newFile = new File(path + file.getOriginalFilename())
            file.transferTo(newFile);
            return HttpResponse;
     }

 

2、JDK内置操作Zip文件

其实,在JDK中已经存在操作ZIP的工具类:ZipInputStream

java上传多个文件 java上传文件 multipartfile_API

 

基本使用:

例子1:

  

public static Map<String, String> readZipFile(String file) throws Exception {
         Map<String, String> resultMap = new HashMap<String, String>();
         Charset gbk = Charset.forName("GBK");
         ZipFile zf = new ZipFile(file, gbk);  // 此处可以用无Charset的构造函数,但是即使是设置为GBK也是处理不了中文的,后面会再说
         InputStream in = new BufferedInputStream(new FileInputStream(file));  
         ZipInputStream zin = new ZipInputStream(in);  
         ZipEntry ze;
         
         while ((ze = zin.getNextEntry()) != null) {  
             if (ze.isDirectory()) {
             } else {  
                 long size = ze.getSize();    // 文件的大小
                 String name = ze.getName();    // 获取文件名称
                 // 具体对其中每个文件的操作和获取信息,可以参考JDK API
                 if (size > 0) {
                     InputStream inputStream = zf.getInputStream(ze);    // 拿到文件流
                     // ……    业务逻辑
                 }
             }  
         }  
         zin.closeEntry();
         
         return resultMap;
     }

 

例子2:

public class FileUtils {  
   
     //日志  
     private static final Logger LOGGER = LoggerFactory.getLogger(FileUtils.class);  
       
     /** 
     * 对zip类型的文件进行解压 
     */  
     public static List<FileModel> unzip(MultipartFile file) {  
         // 判断文件是否为zip文件  
         String filename = file.getOriginalFilename();  
         if (!filename.endsWith("zip")) {  
             LOGGER.info("传入文件格式不是zip文件" + filename);  
             new BusinessException("传入文件格式错误" + filename);  
         }  
         List<FileModel> fileModelList = new ArrayList<FileModel>();  
         String zipFileName = null;  
         // 对文件进行解析  
         try {  
             ZipInputStream zipInputStream = new ZipInputStream(file.getInputStream(), Charset.forName("GBK"));  
             BufferedInputStream bs = new BufferedInputStream(zipInputStream);  
             ZipEntry zipEntry;  
             byte[] bytes = null;  
             while ((zipEntry = zipInputStream.getNextEntry()) != null) { // 获取zip包中的每一个zip file entry  
                 zipFileName = zipEntry.getName();  
                 Assert.notNull(zipFileName, "压缩文件中子文件的名字格式不正确");  
                 FileModel fileModel = new FileModel();  
                 fileModel.setFileName(zipFileName);  
                 bytes = new byte[(int) zipEntry.getSize()];  
                 bs.read(bytes, 0, (int) zipEntry.getSize());  
                 InputStream byteArrayInputStream = new ByteArrayInputStream(bytes);  
                 fileModel.setFileInputstream(byteArrayInputStream);  
                 fileModelList.add(fileModel);  
             }  
         } catch (Exception e) {  
             LOGGER.error("读取部署包文件内容失败,请确认部署包格式正确:" + zipFileName, e);  
             new BusinessException("读取部署包文件内容失败,请确认部署包格式正确:" + zipFileName);  
         }  
         return fileModelList;  
     }  
 }

更多的操作可以参考其他人的总结,本文的重点在于描述JDK自带zip操作API的不便之处,从而引出Zip4J。

JDK自带ZIP API有个非常重大的问题:不支持ZIP中目录中的中文。如下异常:

java.lang.IllegalArgumentException: MALFORMED
    at java.util.zip.ZipCoder.toString(Unknown Source)
    at java.util.zip.ZipInputStream.readLOC(Unknown Source)
    at java.util.zip.ZipInputStream.getNextEntry(Unknown Source)
    at com.mskj.test.md5.BigFileMD5.readZipFile(BigFileMD5.java:131)
    at com.mskj.test.md5.BigFileMD5.checkFileMD5(BigFileMD5.java:42)
    at com.mskj.test.TestMain.main(TestMain.java:12)

我的ZIP中目录:“Test\Test-01\Te\你好\……”,当然导致该异常的原因不只是中文的问题,但是大部分情况下都是。对于JDK自带的ZIP API还有一些其他问题,比如操作解压和压缩API接口不方便(带有密码ZIP)等。当然,网上有许多人说是已经可以解决中文的问题,但是比较麻烦,并不是每次都能够成功,我本地也有尝试过。

在我们的项目中,通过不断的比较,最终还是选择了ZIP4J。

3、ZIP4J

官网:http://www.lingala.net/zip4j.html

mvn库:https://mvnrepository.com/artifact/net.lingala.zip4j/zip4j

目前最新版本是2.3.1,配置如下:

<!-- https://mvnrepository.com/artifact/net.lingala.zip4j/zip4j -->
 <dependency>
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
     <version>2.3.1</version>
 </dependency>

 

      zip4j默认采用的是UTF-8编码,所以本身支持中文(但是,个人建议还是在读取zip文件后,立即设置字符集),同时也支持密码,而且支持多种压缩算法,可以说功能强大,但使用起来却非常简单,当然,如果有其他需求,需要自己从官网上看API。如果你百度或者谷歌ZIP4J的用法,会有许多好的博客和教程,再此不再赘述。

 

4、不解压zip文件,直接通过InputStream的形式读取其中的文件信息

       我想结合一下我项目中需求以及网上许多同仁的问题(不解压zip文件,直接通过InputStream的形式读取其中的文件信息),说一个简单的应用:不解压ZIP文件的前提下,直接利用流(InuptStream)形式读取其中的文件,并读取文件的MD5值。

类似于JDK自带ZipInputStream的形式读取zip文件,由于ZIP4J的ZipInputStream不具备ZipInputStream.getNextEntry()),所以,在ZIP4J中只能通过FileHeader来进行循环。而且,JDK自带API中获取ZIP其中的文件流InputStream时,需要:

ZipFile zf = new ZipFile(file);
InputStream inputStream = zf.getInputStream(ZipEntry);

所以,对应ZIP4J就只能ZipInputStream(该类时InputStream的子类)。

具体代码如下:

import java.util.List;
 import net.lingala.zip4j.core.ZipFile;
 import net.lingala.zip4j.io.ZipInputStream;
 import net.lingala.zip4j.model.FileHeader;public class ZIP4JUtils {
     /**
      * @param file
      * @throws Exception
      */
     public static void readZipFileMD5ByZip4J(String file,String  passwd) throws Exception {
         file.replaceAll("\\\\", "/");
         ZipFile zFile = new ZipFile(file);
         // 此处最好立即设置字符集
         zFile.setFileNameCharset("GBK");
         if (!zFile.isValidZipFile()) {
             return ;
         }
         if (zFile.isEncrypted()) {
             zFile.setPassword(passwd.toCharArray());
         }         // 获取ZIP中所有文件的FileHeader,以便后面对zip中文件进行遍历
         List<FileHeader> list = zFile.getFileHeaders();
         // 此时list的size包括:文件夹、子文件夹、文件的个数
         System.out.println(list.size());
         // 遍历其中的文件
         for (FileHeader fileHeader : list) {
             String fileName = fileHeader.getFileName();
             // fileName会将目录单独读出来,而且带有路径分割符             if (fileHeader.isDirectory()) {
              //if (fileName.endsWith("/") || fileName.endsWith("\\\\") || fileName.endsWith("\\")) {
                 System.out.println(fileName + " 这是一个文件夹。");
                 continue;
             }else {
                 ZipInputStream inputStream = zFile.getInputStream(fileHeader);


                

//下面就可以获取或者保存ZipInputStream,转换为标准InputStream后获取文件内容。

                String Md5String = BigFileMD5.getStreamMD5(inputStream);
                System.out.println(fileName + " 这是一个文件,该文件的MD5值:" + Md5String);
            }
        }
    }
}

其中计算MD5值的类如下:

import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.commons.codec.binary.Hex;
  
 public class BigFileMD5 {
     static MessageDigest MD5 = null;
     static List<File> list = new ArrayList<File>();
     static{
         try{
             MD5 = MessageDigest.getInstance("MD5");
         }catch(NoSuchAlgorithmException e){
             e.printStackTrace();
         }
     }
     
     /**
      * 对一个文件获取md5值
      * @return md5串
      */
     public static String getStreamMD5(InputStream fileInputStream) {
         try {
             byte[] buffer = new byte[8192];
             int length;
             while ((length = fileInputStream.read(buffer)) != -1) {
                 MD5.update(buffer, 0, length);
             }
  
             return new String(Hex.encodeHex(MD5.digest()));
         } catch (FileNotFoundException e) {
         e.printStackTrace();
             return null;
         } catch (IOException e) {
         e.printStackTrace();
             return null;
         } finally {
             try {
                 if (fileInputStream != null)
                 fileInputStream.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
     
     /**
      * 对一个文件获取md5值
      * @return md5串
      */
     public static String getMD5(File file) {
         FileInputStream fileInputStream = null;
         try {
             fileInputStream = new FileInputStream(file);
             byte[] buffer = new byte[8192];
             int length;
             while ((length = fileInputStream.read(buffer)) != -1) {
                 MD5.update(buffer, 0, length);
             }
  
             return new String(Hex.encodeHex(MD5.digest()));
         } catch (FileNotFoundException e) {
         e.printStackTrace();
             return null;
         } catch (IOException e) {
         e.printStackTrace();
             return null;
         } finally {
             try {
                 if (fileInputStream != null)
                 fileInputStream.close();
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 }



输出结果:

Test-01/ 这是一个文件夹。
Test-01/BigFielMD5.jar 这是一个文件,该文件的MD5值:db55e6677f7087754f11eaf84b2d728e
Test-01/Te/ 这是一个文件夹。
Test-01/Te/Test.txt 这是一个文件,该文件的MD5值:c6354a0eb36fac331c138eec7a4826ef
Test-01/Te/你好/ 这是一个文件夹。
Test-01/Te/你好/Hello/ 这是一个文件夹。
Test-01/Te/你好/Hello/antlr-2.7.2.jar 这是一个文件,该文件的MD5值:a73459120df5cadf75eaa98453433a01
Test-01/Te/你好/Hello/antlr-2.7.2.jar.sha1 这是一个文件,该文件的MD5值:3aa5bc052867b339a0b6ed0546f9b336
Test-01/Te/你好/Hello/antlr-2.7.2.pom 这是一个文件,该文件的MD5值:b1136da0c12ce8ffc18d00f8742256ee
Test-01/Te/你好/Hello/antlr-2.7.2.pom.sha1 这是一个文件,该文件的MD5值:ebd38323fc24c8aab361b2a7660ec666
Test-01/测试.txt 这是一个文件,该文件的MD5值:0b60aa5d9aa241ca6063495086e38e95

这样就实现了,Java不解压直接读取zip文件和文件内容(InputStream的形式)。
 

5、自定义一个unzip接口

 

/** 
      * 使用给定密码解压指定的ZIP压缩文件到指定目录 
      * <p> 
      * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 
      * @param zip 指定的ZIP压缩文件 
      * @param dest 解压目录 
      * @param passwd ZIP文件的密码 
      * @return  解压后文件数组 
      * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 
      */  
     public static File [] unzip(File zipFile, String dest, String passwd) throws ZipException {  
         ZipFile zFile = new ZipFile(zipFile);  
         zFile.setFileNameCharset("GBK");  
         if (!zFile.isValidZipFile()) {  
             throw new ZipException("压缩文件不合法,可能被损坏.");  
         }  
         File destDir = new File(dest);  
         if (destDir.isDirectory() && !destDir.exists()) {  
             destDir.mkdir();  
         }  
         if (zFile.isEncrypted()) {  
             zFile.setPassword(passwd.toCharArray());  
         }  
         zFile.extractAll(dest);  
           
         List<FileHeader> headerList = zFile.getFileHeaders();  
         List<File> extractedFileList = new ArrayList<File>();  
         for(FileHeader fileHeader : headerList) {  
             if (!fileHeader.isDirectory()) {  
                 extractedFileList.add(new File(destDir,fileHeader.getFileName()));  
             }  
         }  
         File [] extractedFiles = new File[extractedFileList.size()];  
         extractedFileList.toArray(extractedFiles);  
         return extractedFiles;  
     }

-------------------或者----------------------------------

//解压路径
     private String dest = "C:\\Users\\aaa\\Desktop\\新建文件夹";
     //解压后图片保存的路径
     private String picPath = "C:/Users/aaa/Desktop/新建文件夹/pic";     public String Uncompress(String source) {
         List<String> picPaths = new ArrayList<>();
         try {
             File zipFile = new File(source);
             ZipFile zFile = new ZipFile(zipFile);// 首先创建ZipFile指向磁盘上的.zip文件            zFile.setFileNameCharset("GBK");
            File destDir = new File(dest);// 解压目录 
             zFile.extractAll(dest);// 将文件抽出到解压目录
               if (zFile.isEncrypted()) {   
                   zFile.setPassword(password.toCharArray());  // 设置密码   
               }
               zFile.extractAll(dest);      // 将文件抽出到解压目录(解压)   
      
              List<net.lingala.zip4j.model.FileHeader > headerList = zFile.getFileHeaders(); 
               List<File> extractedFileList= newArrayList<File>(); 
               for(FileHeader fileHeader : headerList) { 
                   if (!fileHeader.isDirectory()) { 
                       extractedFileList.add(new File(destDir,fileHeader.getFileName())); 
                   } 
               } 
               File [] extractedFiles = new File[extractedFileList.size()]; 
              extractedFileList.toArray(extractedFiles); 
               for(File f:extractedFileList){
                 System.out.println(f.getAbsolutePath()+"....");
               }      }catch(ZipException e) {
       }

7.另外上传文件,下载文件

 

  1. 上传文件(且保存到i数据库中)
@PostMapping("upload")
    public String upload(MultipartFile aaa, HttpSession session) throws IOException {
        //获取上传文件用户id
        User user = (User) session.getAttribute("user");
        //获取文件原始名称
        String oldFileName = aaa.getOriginalFilename();
        //获取文件后缀
        String extension = "." + FilenameUtils.getExtension(aaa.getOriginalFilename());
        //生成新的文件名称
        String newFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + UUID.randomUUID().toString().replace("-", "") + extension;
        //文件大小
        Long size = aaa.getSize();
        //文件类型
        String type = aaa.getContentType();
        //处理根据日期生成目录
        //String realPath = ResourceUtils.getURL("classpath:").getPath() + "/static/files";
        String dateFormat = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        String dateDirPath = uploadPath + "/files/" + dateFormat;
        File dateDir = new File(dateDirPath);
        if (!dateDir.exists()) dateDir.mkdirs();
        //处理文件上传
        aaa.transferTo(new File(dateDir, newFileName));
        //将文件信息放入数据库保存
        UserFile userFile = new UserFile();
        userFile.setOldFileName(oldFileName).setNewFileName(newFileName).setExt(extension).setSize(String.valueOf(size))
                .setType(type).setPath("/files/" + dateFormat).setUserId(user.getId());
        userFileService.save(userFile);
        return "redirect:/file/showAll";
    }
复制代码
  1. 下载文件(在线预览)
@GetMapping("download")
    public void download(String openStyle, String id, HttpServletResponse response) throws IOException {
        //获取打开方式
        openStyle = openStyle == null ? "attachment" : openStyle;
        //获取文件信息
        UserFile userFile = userFileService.findById(id);
        //点击下载链接更新下载次数
        if ("attachment".equals(openStyle)) {
            userFile.setDowncounts(userFile.getDowncounts() + 1);
            userFileService.update(userFile);
        }
        //根据文件信息中文件名字 和 文件存储路径获取文件输入流
        String realpath = ResourceUtils.getURL("classpath:").getPath() + "/static" + userFile.getPath();
        //获取文件输入流
        FileInputStream is = new FileInputStream(new File(realpath, userFile.getNewFileName()));
        //附件下载
        response.setHeader("content-disposition", openStyle + ";fileName=" + URLEncoder.encode(userFile.getOldFileName(), "UTF-8"));
        //获取响应输出流
        ServletOutputStream os = response.getOutputStream();
        //文件拷贝
        IOUtils.copy(is, os);
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(os);
    }