之前做项目遇到一个导出word并压缩成zip的功能需求,当时翻了不少百度文章,没找到系统、详尽的实操教学,现在项目做完了,我就在这做一个简单的总结吧。
第一步:原型-创建WORD文件,画好样式,参数采用注入的写法,例${xxxx} 将word文件另存为 word xml文档或者是word 2003xml文档,建议存为2003版,这样输出的格式不会出现兼容问题。 第二步:使用notePad++查看并修改文件内容,使其符合模板要求。使用其他编辑器亦可,看个人习惯。 刚打开时内容很混乱,建议使用Pretty print(XML only - with line breaks)插件格式化,插件请自行安装,安装步骤自行百度。
格式化之后可以将无关紧要的系统自动生成的作者信息等删除,重点是修改上一步画的模板内容,将参数恢复模板样式。示例: 如果表格是动态加载的,需要在对应的动态加载起始及末尾位置加入list标签,用来循环插入数据。
模板画好后,保存,然后将文件格式改为Freemarker可识别的’.ftl’即可。 将模板放入templates文件夹,以便程序调用
至此,模板制作、准备工作完成
需要两个工具类,一是WordUtils(用于生成doc格式的WORD文档),二是ZipUtil(用于导出zip格式的压缩包) WordUtils
import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.Template; import freemarker.template.Version; import org.springframework.core.io.ClassPathResource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.util.List; import java.util.Map; import static java.nio.charset.StandardCharsets.UTF_8; /** * @author running * @description * @date 2020/9/22 */ public class WordUtils { private static Configuration configuration = null; static { configuration = new Configuration(new Version("2.3.30")); configuration.setDefaultEncoding("utf-8"); //以下配置只能在本地使用,linux环境下无法获取地址,注掉 // try { // ResourceLoader resourceLoader = new DefaultResourceLoader(); //指定模板目录在类路径:WEB-INF/classes // Resource resource = resourceLoader.getResource("/"); // File file = resource.getFile(); //设置要解析的模板所在的目录,并加载模板文件 // configuration.setDirectoryForTemplateLoading(file); ///设置包装器,并将对象包装为数据模型 configuration.setObjectWrapper(new DefaultObjectWrapper(new Version("2.3.30"))); // } catch (IOException e) { // e.printStackTrace(); // } } private WordUtils() { throw new AssertionError(); } /** * 导出单个word * * @param map 数据 * @param title 文件名 * @param ftlFile 模板文件 * @param response 响应 */ public static void exportWord(Map map, String title, String ftlFile, HttpServletResponse response) { File file = null; InputStream fin = null; ServletOutputStream out = null; try { //Template freemarkerTemplate = configuration.getTemplate(ftlFile, "UTF-8"); ClassPathResource classPathResource = new ClassPathResource(ftlFile); InputStream inputStream = classPathResource.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); Template freemarkerTemplate = new Template(ftlFile, reader, configuration); // 调用工具类的createDoc方法生成Word文档 String fileName = title + ".doc"; file = createDoc(map, freemarkerTemplate, fileName); fin = new FileInputStream(file); response.setCharacterEncoding("utf-8"); response.setContentType("application/msword"); // 设置浏览器以下载的方式处理该文件名 response.setHeader("Content-Disposition", "attachment;filename=" .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8")))); out = response.getOutputStream(); // 缓冲区 byte[] buffer = new byte[1024]; int bytesToRead = -1; // 通过循环将读入的Word文件的内容输出到浏览器中 while ((bytesToRead = fin.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (fin != null) { fin.close(); } if (out != null) { out.close(); } // 删除临时文件 if (file != null) { file.delete(); } } catch (Exception e2) { e2.printStackTrace(); } } } /** * 压缩包方式导出多个word * 由于一次请求浏览器只能响应一次,想导出多个必须打包,亲测for循环导出只能导一个 * 如果想做到分别单独下载,那就得用插件啦,这里不提供插件的做法 * 思路:生成临时目录-在临时目录生成word-将临时目录打zip包-zip文件下载-删除临时目录和zip包, * 回收系统资源 * * @param mapList * @param titleList * @param ftlFile */ public static void exportWordBatch(List<Map<String, Object>> mapList, List<String> titleList, String ftlFile, HttpServletResponse response, HttpServletRequest request) { File file = null; File zipfile = null; File directory = null; InputStream fin = null; ServletOutputStream out = null; response.setCharacterEncoding("utf-8"); response.setContentType("application/octet-stream"); response.addHeader("Content-Disposition", "attachment;filename=" + System.currentTimeMillis() + ".zip"); try { //以此方式,在Linux环境下也可获取到模板文件 ClassPathResource classPathResource = new ClassPathResource(ftlFile); InputStream inputStream = classPathResource.getInputStream(); InputStreamReader reader = new InputStreamReader(inputStream); Template freemarkerTemplate = new Template(ftlFile, reader, configuration); out = response.getOutputStream(); //根据当前时间创建临时目录 String path = request.getRealPath("/resources/word/" + System.currentTimeMillis()); directory = new File(path); directory.mkdirs(); for (int i = 0; i < mapList.size(); i++) { Map<String, Object> map = mapList.get(i); String title = titleList.get(i); // 调用工具类的createDoc方法在临时目录下生成Word文档 file = createDoc(map, freemarkerTemplate, directory.getPath() + "/" + title + ".doc"); } //压缩目录 ZipUtil.createZip(path, path + "zip.zip"); //根据路径获取刚生成的zip包文件 zipfile = new File(path + "zip.zip"); fin = new FileInputStream(zipfile); // 缓冲区 byte[] buffer = new byte[1024]; int bytesToRead = -1; // 通过循环将读入的Word文件的内容输出到浏览器中 while ((bytesToRead = fin.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } response.flushBuffer(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (fin != null) { fin.close(); } if (out != null) { out.close(); } if (zipfile != null) { zipfile.delete(); } if (directory != null) { //递归删除目录及目录下文件 ZipUtil.deleteFile(directory); } } catch (Exception e2) { e2.printStackTrace(); } } } //生成word文档方法 private static File createDoc(Map<?, ?> dataMap, Template template, String filename) { File f = new File(filename); Template t = template; Writer w = null; FileOutputStream fos = null; try { // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开 fos = new FileOutputStream(f); w = new OutputStreamWriter(fos, UTF_8); //不要偷懒写成下面酱紫: 否则无法关闭fos流,打zip包时存取被拒抛异常 //w = new OutputStreamWriter(new FileOutputStream(f), "utf-8"); t.process(dataMap, w); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex); } finally { try { fos.close(); w.close(); } catch (Exception e) { e.printStackTrace(); } } return f; } }ZipUtil
import java.io.*; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * @author running * @description * @date 2020/9/22 */ public class ZipUtil { public static void zip(String inputFileName, String zipFileName) throws Exception { zip(zipFileName, new File(inputFileName)); } private static void zip(String zipFileName, File inputFile) throws Exception { ZipOutputStream out = new ZipOutputStream(new FileOutputStream( zipFileName)); zip(out, inputFile, ""); System.out.println("zip done"); out.close(); } private static void zip(ZipOutputStream out, File f, String base) throws Exception { if (f.isDirectory()) { File[] fl = f.listFiles(); out.putNextEntry(new ZipEntry(base + "/")); base = base.length() == 0 ? "" : base + "/"; for (int i = 0; i < fl.length; i++) { zip(out, fl[i], base + fl[i].getName()); } } else { out.putNextEntry(new ZipEntry(base)); FileInputStream in = new FileInputStream(f); int b; //System.out.println(base); while ((b = in.read()) != -1) { out.write(b); } in.close(); } } /** * 创建ZIP文件 * * @param sourcePath 文件或文件夹路径 * @param zipPath 生成的zip文件存在路径(包括文件名) */ public static void createZip(String sourcePath, String zipPath) { FileOutputStream fos = null; ZipOutputStream zos = null; try { fos = new FileOutputStream(zipPath); zos = new ZipOutputStream(fos); writeZip(new File(sourcePath), "", zos); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { if (zos != null) { zos.close(); //压缩成功后,删除打包前的文件 deleteFile(new File(sourcePath)); } } catch (IOException e) { e.printStackTrace(); } } } private static void writeZip(File file, String parentPath, ZipOutputStream zos) { if (file.exists()) { // 处理文件夹 if (file.isDirectory()) { parentPath += file.getName() + File.separator; File[] files = file.listFiles(); for (File f : files) { writeZip(f, parentPath, zos); } } else { FileInputStream fis = null; try { fis = new FileInputStream(file); ZipEntry ze = new ZipEntry(parentPath + file.getName()); zos.putNextEntry(ze); byte[] content = new byte[1024]; int len; while ((len = fis.read(content)) != -1) { zos.write(content, 0, len); zos.flush(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if (fis != null) { fis.close(); } } catch (IOException e) { e.printStackTrace(); } } } } } public static void copyResource(List<String> oldResPath, String newResPath) { for (int m = 0; m < oldResPath.size(); m++) { try { // 如果文件夹不存在 则建立新文件夹 (new File(newResPath)).mkdirs(); File a = new File(oldResPath.get(m)); // 如果已经是具体文件,读取 if (a.isFile()) { FileInputStream input = new FileInputStream(a); FileOutputStream output = new FileOutputStream(newResPath + "/" + (a.getName()).toString()); byte[] b = new byte[1024 * 4]; int len; while ((len = input.read(b)) != -1) { output.write(b, 0, len); } output.flush(); output.close(); input.close(); // 如果文件夹下还存在文件,遍历,直到得到具体的文件 } else { String[] file = a.list(); File temp = null; for (int i = 0; i < file.length; i++) { if (oldResPath.get(m).endsWith(File.separator)) { temp = new File(oldResPath.get(m) + file[i]); } else { temp = new File(oldResPath.get(m) + File.separator + file[i]); } if (temp.isFile()) { FileInputStream input = new FileInputStream(temp); FileOutputStream output = new FileOutputStream(newResPath + "/" + (temp.getName()).toString()); byte[] b = new byte[1024 * 4]; int len; while ((len = input.read(b)) != -1) { output.write(b, 0, len); } output.flush(); output.close(); input.close(); } if (temp.isDirectory()) { List<String> oldChildPath = new ArrayList<String>(); oldChildPath.add(oldResPath.get(m) + "/" + file[i]); newResPath = newResPath + "/" + file[i]; // 如果是子文件夹 递归循环 copyResource(oldChildPath, newResPath); } } } } catch (Exception e) { e.printStackTrace(); } } } /** * 删除文件夹 * * @param file */ public static void deleteFile(File file) { // 判断文件是否存在 if (file.exists()) { // 判断是否是文件 if (file.isFile()) { file.delete(); } else if (file.isDirectory()) { // 否则如果它是一个目录 // 声明目录下所有的文件 files[]; File files[] = file.listFiles(); // 遍历目录下所有的文件 for (int i = 0; i < files.length; i++) { // 把每个文件 用这个方法进行迭代 deleteFile(files[i]); } } file.delete(); } } /** * 时间格式化 * * @return */ public static String dateToString() { Date d = new Date(); SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); String time = formatter.format(d); return time; } }个人感觉,导出文件最需要注意的是模板的制作,往往一个小的偏差就可能导致模板导出失败,不过好处是IDEA会有报错的日志,通过日志很容易修改。至于工具类,都是现成的,直接用即可。 今天的记录就到这里。good luck!