最近有一个需求是需要动态导出合同、订单等信息,导出一个word文档供客户进行下载查看。
需要导出的word文件,主要可以分为两种类型。
经过对比工具,我实践过两种实现方式。第一种是FreeMarker模板来进行填充;第二种就是文中介绍的POI-TL。
这里我推荐使用POI-TL。
POI-TL是word模板引擎,基于Apache POI,提供更友好的API。
目前最新的版本是1.12.X,POI对应版本是5.2.2。
这里需要注意的是POI和POI-TL有一个对应的关系。
我使用的POI-TL版本是1.10.0
com.deepoove poi-tl1.10.0 org.apache.poi poi4.1.2 org.apache.poi poi-ooxml4.1.2 org.apache.poi poi-ooxml-schemas4.1.2 commons-io commons-io2.7
流程:制作模板->提供数据->渲染模板->下载word
注意:需要填充的数据需要使用{{}}来表示。
模板保存为docx格式,存放在resource目录下
private MapassertMap() { Map params = new HashMap<>(); params.put("name", "努力的蚂蚁"); params.put("age", "18"); params.put("image", Pictures.ofUrl("http://deepoove.com/images/icecream.png").size(100, 100).create()); return params; }
/** * 将项目中的模板文件拷贝到根目录下 * @return */ private String copyTempFile(String templeFilePath) { InputStream inputStream = getClass().getClassLoader().getResourceAsStream(templeFilePath); String tempFileName = System.getProperty("user.home") + "/" + "1.docx"; File tempFile = new File(tempFileName); try { FileUtils.copyInputStreamToFile(inputStream, tempFile); } catch (IOException e) { throw new RuntimeException(e); } return tempFile.getPath(); }
private void down(HttpServletResponse response, String filePath, String realFileName) { String percentEncodedFileName = null; try { percentEncodedFileName = percentEncode(realFileName); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } StringBuilder contentDispositionValue = new StringBuilder(); contentDispositionValue.append("attachment; filename=").append(percentEncodedFileName).append(";").append("filename*=").append("utf-8''").append(percentEncodedFileName); response.addHeader("Access-Control-Allow-Origin", "*"); response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); response.setHeader("Content-disposition", contentDispositionValue.toString()); response.setHeader("download-filename", percentEncodedFileName); try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath)); // 输出流 BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());) { byte[] buff = new byte[1024]; int len = 0; while ((len = bis.read(buff)) > 0) { bos.write(buff, 0, len); } } catch (Exception e) { e.printStackTrace(); } }
/** * 百分号编码工具方法 * @param s 需要百分号编码的字符串 * @return 百分号编码后的字符串 */ public static String percentEncode(String s) throws UnsupportedEncodingException { String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); return encode.replaceAll("\+", "%20"); }
@RequestMapping("genera") public void genera(HttpServletResponse response) { //1.组装数据 Mapparams = assertMap(); //2.获取根目录,创建模板文件 String path = copyTempFile("word/1.docx"); String fileName = System.currentTimeMillis() + ".docx"; String tmpPath = "D:\\" + fileName; try { //3.将模板文件写入到根目录 //4.编译模板,渲染数据 XWPFTemplate template = XWPFTemplate.compile(path).render(params); //5.写入到指定目录位置 FileOutputStream fos = new FileOutputStream(tmpPath); template.write(fos); fos.flush(); fos.close(); template.close(); //6.提供前端下载 down(response, tmpPath, fileName); } catch (Exception e) { e.printStackTrace(); } finally { //7.删除临时文件 File file = new File(tmpPath); file.delete(); File copyFile = new File(path); copyFile.delete(); } }
对于图片的格式,POI-TL也提供了几种方式来提供支撑。
请求接口:http://127.0.0.1:1000/file/genera
效果如下:
表格动态内容填充,POI-TL提供了3种方式。
第二种和第三种都可以实现表格填充,但我个人感觉第一种更方便一点,这里我只介绍【表格行循环】实现方式。
LoopRowTableRenderPolicy 是一个特定场景的插件,根据集合数据循环表格行。
注意:
- 模板中有两个list,这两个list需要置于循环行的上一行。
- 循环行设置要循环的标签和内容,注意此时的标签应该使用[]
学生实体类
public class Student { private String name; private String age; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } }
学生word类
public class StudentTable { private String title; private ListstudentList; private List studentList1; public List getStudentList1() { return studentList1; } public void setStudentList1(List studentList1) { this.studentList1 = studentList1; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public List getStudentList() { return studentList; } public void setStudentList(List studentList) { this.studentList = studentList; } }
private StudentTable assertData() { StudentTable table = new StudentTable(); table.setTitle("我是标题"); ListstudentList = new ArrayList<>(); Student student = new Student(); student.setName("张三"); student.setAge("18"); studentList.add(student); Student student1 = new Student(); student1.setName("李四"); student1.setAge("20"); studentList.add(student1); Student student2 = new Student(); student2.setName("王五"); student2.setAge("21"); studentList.add(student2); Student student3 = new Student(); student3.setName("马六"); student3.setAge("19"); studentList.add(student3); table.setStudentList(studentList); table.setStudentList1(studentList); return table; }
@RequestMapping("dynamicTable") public void dynamicTable(HttpServletResponse response) { //1.组装数据 StudentTable table = assertData(); //2.获取根目录,创建模板文件 String path = copyTempFile("word/2.docx"); //3.获取临时文件 String fileName = System.currentTimeMillis() + ".docx"; String tmpPath = "D:\\" + fileName; try { //4.编译模板,渲染数据 LoopRowTableRenderPolicy hackLoopTableRenderPolicy = new LoopRowTableRenderPolicy(); Configure config = Configure.builder().bind("studentList", hackLoopTableRenderPolicy).bind("studentList1", hackLoopTableRenderPolicy).build(); XWPFTemplate template = XWPFTemplate.compile(path, config).render(table); //5.写入到指定目录位置 FileOutputStream fos = new FileOutputStream(tmpPath); template.write(fos); fos.flush(); fos.close(); template.close(); //6.提供下载 down(response, tmpPath, fileName); } catch (Exception e) { e.printStackTrace(); } finally { //7.删除临时文件 File file = new File(tmpPath); file.delete(); File copyFile = new File(path); copyFile.delete(); } }
请求接口:http://127.0.0.1:1000/file/dynamicTable
效果如下: