工作中遇到很多多格式文件下载压缩及解压处理,现将通用文件下载工具类做一个总结。包含格式(doc/docx、xls/xlsx、lrm/lrmx、txt、zip/rar等)。

一、解压处理

文件解压主要处理rar及zip两种格式压缩文件。其中rar5.0及以上版本需采用命令行方式解压,所以rar格式解压直接使用命令行方式(需注意程序运行环境windows/linux命令行方式不同)。zip格式解压使用jar包。

解压所需jar包:

<!-- 解压zip -->
<dependency><groupId>org.apache.ant</groupId><artifactId>ant</artifactId><version>1.9.4</version>
</dependency>
<!-- io流 -->
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version>
</dependency>

通用解压方法:

/*** 解压zip文件* @param zipFile* @param outDir* @throws IOException*/
public static void unZip(File zipFile, String outDir) throws IOException {File outFileDir = new File(outDir);if (!outFileDir.exists()) {boolean isMakDir = outFileDir.mkdirs();if (isMakDir) {System.out.println("创建压缩目录成功");}}ZipFile zip = new ZipFile(zipFile);for (Enumeration enumeration = zip.getEntries(); enumeration.hasMoreElements(); ) {ZipEntry entry = (ZipEntry) enumeration.nextElement();String zipEntryName = entry.getName();InputStream in = zip.getInputStream(entry);if (entry.isDirectory()) {      //处理压缩文件包含文件夹的情况File fileDir = new File(outDir + zipEntryName);fileDir.mkdir();continue;}File file = new File(outDir, zipEntryName);file.createNewFile();OutputStream out = new FileOutputStream(file);byte[] buff = new byte[1024];int len;while ((len = in.read(buff)) > 0) {out.write(buff, 0, len);}in.close();out.close();}
}/*** 采用命令行方式解压文件* 解决rar5.0及以上版本压缩包解压不了问题* @param rarFile 压缩文件流* @param destDir 解压结果路径* @param cmdPath 命令所在路径* @return*/
public static boolean unRar(File rarFile, String destDir, String cmdPath) throws Exception {boolean bool = false;if (!rarFile.exists()) {return false;}File destDirPath = new File(destDir);if (!destDirPath.exists()) {destDirPath.mkdirs();}// 开始调用命令行解压,参数-o+是表示覆盖的意思// String cmdPath = "C:\\Program Files\\WinRAR\\WinRAR.exe"; // windows中的路径// String cmdPath = "/usr/local/bin/unrar"; 如果linux做了软连接 不需要这里配置路径String cmd = cmdPath + " X -o+ " + rarFile + " " + destDir;Process proc = Runtime.getRuntime().exec(cmd);if (proc.waitFor() != 0) {if (proc.exitValue() == 0) {bool = false;}} else {bool = true;}return bool;
}

需注意的是:

方法仅可以解压一层压缩文件,压缩文件套压缩文件格式本方法不适用。多层压缩文件解压思路:解压外层压缩包后依次判断文件类型,如果检测到有压缩包则进行解压,整个过程可迭代进行。(仅提供思路)

二、文件下载

文件下载在日常工作中很常见,常见的下载格式:doc/docx、xls/xlsx、txt、json、zip等。下载方法都大同小异。

下载使用说明:

  • Word:使用的是freemarker生成固定模板进行下载导出。
  • Excel:poi导出
  • 其他格式:使用io流导出

具体怎么使用freemarker生成模板、poi导出文件本篇不做深入讨论(百度一大堆)。

涉及到的jar包(按需添加即可):

<!-- io流 -->
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version>
</dependency><!-- poi -->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.7</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.7</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>3.7</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>3.7</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>ooxml-schemas</artifactId><version>1.3</version>
</dependency>
<dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId><version>2.6.0</version>
</dependency><!-- freemarker -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

通用文件名格式处理工具代码:

public static String checkEncode(HttpServletRequest request, String value) throws UnsupportedEncodingException {String agent = request.getHeader("USER-AGENT");if (null != agent){if (-1 != agent.indexOf("Chrome") || -1 != agent.indexOf("Firefox") || -1 != agent.indexOf("Safari")) {value = new String(value.getBytes("utf-8"), "ISO8859-1");} else {//IE7+value = java.net.URLEncoder.encode(value, "UTF-8");}}return value;
}
使用io流下载文件

使用io流下载文件适用于很多文件格式,需注意的是字符编码集。出现文件下载下来中文乱码的十有八九都是编码集的问题。

  1. 示例1:zip文件下载
/*** 根据文件,进行压缩,批量下载* @throws Exception*/
public static void downloadBatchByFile(List<FileDocDTO> files, HttpServletResponse response, HttpServletRequest request) {ZipOutputStream zos = null;BufferedOutputStream bos = null;try {response.setCharacterEncoding("utf-8");response.setContentType("applicatoin/octet-stream");response.addHeader("Content-Disposition", "attachment; filename=" + checkEncode(request, "cadre.zip"));zos = new ZipOutputStream(response.getOutputStream());bos = new BufferedOutputStream(zos);for (FileDocDTO entry : files) {String fileName = entry.getName();            //每个zip文件名File file = entry.getFile();            //这个zip文件的字节BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));zos.putNextEntry(new ZipEntry(fileName));int len = 0;byte[] buf = new byte[10 * 1024];while ((len = bis.read(buf, 0, buf.length)) != -1) {bos.write(buf, 0, len);}bis.close();bos.flush();}zos.flush();} catch (Exception e) {e.printStackTrace();}finally {try {if (zos != null){zos.close();}if (bos != null){bos.close();}response.getOutputStream().close();}catch (Exception e){e.printStackTrace();}}
}

FileDocDTO类:用于存放需要压缩的文件名及文件流

@Data
public class FileDocDTO {private String name;private File file;
}

这种压缩下载方式可压缩各种格式文件。

  1. 示例2:lrm/lrmx文件下载(格式为任免表格式)
/*** lrm lrmx下载    --  浏览器下载* @param dataInfoStr  数据信息字符串* @param fileName  文件名称* @param exportType  文件下载格式 lrm lrmx* @param response* @param request* @return*/
public static boolean singleDataLRMOrLRMXExport(String dataInfoStr, String fileName, String exportType, HttpServletResponse response, HttpServletRequest request){Writer out = null;boolean flag = false;ServletOutputStream servletOutputStream = null;try {String dataName = fileName + "." + exportType;response.setCharacterEncoding("utf-8");response.setContentType("applicatoin/octet-stream");response.addHeader("Content-Disposition", "attachment; filename=" + checkEncode(request, dataName));servletOutputStream = response.getOutputStream();out = new BufferedWriter(new OutputStreamWriter(servletOutputStream));if (exportType.equals("lrmx")){out = new BufferedWriter(new OutputStreamWriter(servletOutputStream, "UTF-8"));}else {out = new BufferedWriter(new OutputStreamWriter(servletOutputStream, "GB2312"));}out.write(dataInfoStr);flag = true;} catch (Exception e){e.printStackTrace();} finally {try {if (out != null){out.close();}if (servletOutputStream != null){servletOutputStream.close();}}catch (Exception e){e.printStackTrace();}}return flag;
}/*** lrm lrmx下载    --  下载到指定文件夹* @param dataInfoStr 数据信息字符串* @param fileName 文件名称* @param exportType 文件下载格式 lrm lrmx* @param index 处理下载有多个同名文件   例如: xx(1).lrm* @param filePath 文件指定下载位置* @return*/
public static FileDocDTO singleDataLRMOrLRMXExport(String dataInfoStr, String fileName, String exportType, String index, String filePath){Writer out = null;String sourceFilePath = "";FileDocDTO fileDoc = new FileDocDTO();try {String dataName = fileName + index + "." + exportType;sourceFilePath = filePath + dataName;File file = new File(sourceFilePath);if (!file.exists()) {// 先得到文件的上级目录,并创建上级目录,在创建文件file.getParentFile().mkdir();file.createNewFile();}if (exportType.equals("lrmx")){out = new PrintWriter(file, "UTF-8");}else {out = new PrintWriter(file,"GB2312");}out.write(dataInfoStr);fileDoc.setName(dataName);fileDoc.setFile(new File(sourceFilePath));} catch (Exception e){e.printStackTrace();} finally {try {if (out != null){out.close();}}catch (Exception e){e.printStackTrace();}}return fileDoc;
}

提供两种下载方式,一种直接下载到浏览器,另一种下载到指定文件夹。

需注意的是:

这种方式下载也适用于txt、json等格式下载,只需更改文件后缀名和对应的字符编码。

Word/Excel文件下载

Word/Excel文件下载主要区别在于数据存入形式不一样。Word需提前固定格式并进行占位符数据替换,Excel可通过代码自定义数据格式。当然word也可以通过内置代码进行表格合并等样式处理。(具体实现操作请百度。内容太多这里不做讨论。)

  1. Word文件下载
/*** word下载  单个下载   -- 浏览器下载* @param fileMap* @param wordName 文件名称* @param templateFilePath 模板位置* @return*/
public static boolean singleDataWordExport(Map fileMap, String wordName, String templateFilePath, HttpServletResponse response, HttpServletRequest request){String fileName = "无效";boolean flag = false;Writer out = null;Template template = null;ServletOutputStream servletOutputStream = null;try {fileName = wordName + ".docx";Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);configuration.setDefaultEncoding("UTF-8");String ftlPath = templateFilePath.substring(0, templateFilePath.lastIndexOf("/"));configuration.setDirectoryForTemplateLoading(new File(ftlPath)); // FTL文件所存在的位置String ftlFile = templateFilePath.substring(templateFilePath.lastIndexOf("/")+1);template = configuration.getTemplate(ftlFile); // 模板文件名response.setCharacterEncoding("utf-8");response.setContentType("applicatoin/octet-stream");response.addHeader("Content-Disposition", "attachment; filename=" + checkEncode(request, fileName));servletOutputStream = response.getOutputStream();out = new BufferedWriter(new OutputStreamWriter(servletOutputStream));template.process(fileMap, out);flag = true;} catch (Exception e) {e.printStackTrace();} finally {try {if (out != null){out.close();}if (servletOutputStream != null){servletOutputStream.close();}}catch (Exception e){e.printStackTrace();}}return flag;
}/*** word下载  单个下载   -- 下载到指定位置* @param fileMap* @param wordName 文件名称* @param templateFilePath 模板位置* @param index 处理下载有多个同名文件   例如: xx简历(1).docx* @param filePath 文件指定下载位置* @return*/
public static FileDocDTO singleDataWordExport(Map fileMap, String wordName, String templateFilePath, String index, String filePath){String fileName = "无效";String sourceFilePath = "";FileOutputStream outPut = null;Writer out = null;Template template = null;FileDocDTO fileDoc = new FileDocDTO();try {fileName = wordName + index + ".docx";Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);configuration.setDefaultEncoding("UTF-8");String ftlPath = templateFilePath.substring(0, templateFilePath.lastIndexOf("/"));configuration.setDirectoryForTemplateLoading(new File(ftlPath)); // FTL文件所存在的位置String ftlFile = templateFilePath.substring(templateFilePath.lastIndexOf("/")+1);template = configuration.getTemplate(ftlFile); // 模板文件名File file = new File(filePath + fileName);if (!file.exists()) {// 先得到文件的上级目录,并创建上级目录,在创建文件file.getParentFile().mkdir();file.createNewFile();}outPut = new FileOutputStream(file);out = new BufferedWriter(new OutputStreamWriter(outPut));template.process(fileMap, out);outPut.flush();fileDoc.setName(fileName);fileDoc.setFile(new File(sourceFilePath));} catch (Exception e) {e.printStackTrace();} finally {try {if (outPut != null){outPut.close();}if (out != null){out.close();}}catch (Exception e){e.printStackTrace();}}return fileDoc;
}
  1. Excel文件下载

excel下载还需导入jar包:

<dependency><groupId>net.sourceforge.jexcelapi</groupId><artifactId>jxl</artifactId><version>2.6.10</version>
</dependency>

示例代码:

/*** excel格式下载* @param dataList 下载数据* @param cl 数据类* @param fileName 下载文件名称* @param table_head 表头数据* @param table_field 表格数据对应字段* @param response* @param request* @return*/
public static boolean excelExport(List dataList, Class cl, String fileName, String[] table_head, String[] table_field, HttpServletResponse response, HttpServletRequest request){boolean flag = false;WritableWorkbook workbook = null;try {// 导出excelresponse.setContentType("application/x-download");response.setCharacterEncoding("utf-8");response.setHeader("Content-Disposition", "attachment;filename=" + checkEncode(request, fileName + ".xls"));workbook = Workbook.createWorkbook(response.getOutputStream());WritableSheet workSheet = workbook.createSheet("first sheet", 0);// 表头样式  单元格样式暂不涉及WritableFont wf_title = new WritableFont(WritableFont.ARIAL, 11, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, Colour.WHITE);WritableCellFormat wcf_title = new WritableCellFormat(wf_title);Color color = Color.decode("#1f4e78");workbook.setColourRGB(Colour.ORANGE, color.getRed(), color.getGreen(), color.getBlue());wcf_title.setBackground(Colour.ORANGE);wcf_title.setAlignment(Alignment.CENTRE);wcf_title.setVerticalAlignment(VerticalAlignment.CENTRE);wcf_title.setBorder(Border.ALL, BorderLineStyle.THIN, Colour.BLACK);wcf_title.setWrap(true);//构造excel 表头for (int i = 0; i < table_head.length; i++) {workSheet.addCell(new jxl.write.Label(i, 0, table_head[i], wcf_title));}//填充表格内容Field[] fields = cl.getDeclaredFields();for (int i = 1; i <= dataList.size(); i++) {for (int j = 0; j < table_field.length; j++) {for (int k = 0; k < fields.length; k++) {if (table_field[j].equals(fields[k].getName())) {CellView cellView = new CellView();cellView.setAutosize(true); //设置自动大小fields[k].setAccessible(true);workSheet.setColumnView(j, cellView);if (dataList.get(i - 1) == null) {workSheet.addCell(new jxl.write.Label(j, i, "暂无"));} else {String str = (fields[k].get(dataList.get(i - 1)) + "");if (CommonUtil.isEmpty(str)){str = "";}workSheet.addCell(new jxl.write.Label(j, i, str));}}}}}workbook.write();flag = true;}catch (Exception e){e.printStackTrace();}finally {try {if (workbook != null){workbook.close();}response.getOutputStream().close();}catch (Exception e){e.printStackTrace();}}return flag;
}

好了 暂时总结就这么多。

——————————————-分隔线————————————————-
2021年2月使用zip解压遇到一些新问题,问题如下:
1.解压后文件出现乱码问题。
2.解压文件报错(文件路径不存在)。
3.解压文件之后控制台出现: Cleaning up unclosed ZipFile for archive。

出现问题后经过一系列调试解决结果如下:
1.文件乱码问题:在解压时添加字符编码集

ZipFile zip = new ZipFile(zipFile, "gbk");

2.控制台出现: Cleaning up unclosed ZipFile for archive;
解决方式:解压后关闭文件流即可
3.解压文件报文件路径不存在:
这是最有意思的一个bug,值得记录;

场景
压缩文件内包含文件夹,文件夹内文件存在数字顺序(例:1.学习.docx、2.学习.docx等)。

过程:通过debug一步步调试可看到zip解压之后出现顺序为:文件夹/1.学习.docx、文件夹/2.学习.docx、文件夹/;而之前代码逻辑顺序应为:文件夹/、文件夹/1.学习.docx、文件夹/2.学习.docx;解析出来的顺序不一致从而导致问题出现。

问题结论:在做解压文件时需先做判定文件的上级文件夹是否存在,如不存在需先创建上级文件夹。解压代码优化调整如下:

public static void unZip(File zipFile, String outDir) throws IOException {File outFileDir = new File(outDir);if (!outFileDir.exists()) {boolean isMakDir = outFileDir.mkdirs();if (isMakDir) {System.out.println("创建压缩目录成功");}}ZipFile zip = new ZipFile(zipFile, "gbk");for (Enumeration enumeration = zip.getEntries(); enumeration.hasMoreElements(); ) {ZipEntry entry = (ZipEntry) enumeration.nextElement();String zipEntryName = entry.getName();InputStream in = zip.getInputStream(entry);if (entry.isDirectory()) {      //处理压缩文件包含文件夹的情况File fileDir = new File(outDir + zipEntryName);fileDir.mkdir();continue;}File file = new File(outDir, zipEntryName);if (!file.getParentFile().exists()){file.getParentFile().mkdir();}file.createNewFile();OutputStream out = new FileOutputStream(file);byte[] buff = new byte[1024];int len;while ((len = in.read(buff)) > 0) {out.write(buff, 0, len);}in.close();out.close();}zip.close();zipFile.delete();}

——————————————-分隔线————————————————-
补充:Excel单元格合并操作

Excel单元格合并

WritableSheet mergeCells(a,b,c,d)单元格合并函数

参数含义

  • a 单元格的列号
  • b 单元格的行号
  • c 从单元格[a,b]起,向左合并到c列
  • b 从单元格[a,b]起,向下合并到d行

注:单元格的列号和行号都是从0开始计算