背景
最近有一个需求是需要动态导出合同、订单等信息,导出一个word文档供客户进行下载查看。
需要导出的word文件,主要可以分为两种类型。
- 导出固定内容和图片的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
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
快速开始
流程:制作模板->提供数据->渲染模板->下载word
注意:需要填充的数据需要使用{{}}来表示。
1. 导出固定内容和图片的word文档
准备模板
模板保存为docx格式,存放在resource目录下
提供数据
private Map<String, Object> assertMap() {
Map<String, Object> 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.组装数据
Map<String, Object> params = 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
效果如下:
2. 导出表格内容不固定的word文档
表格动态内容填充,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 List<Student> studentList;
private List<Student> studentList1;
public List<Student> getStudentList1() {
return studentList1;
}
public void setStudentList1(List<Student> studentList1) {
this.studentList1 = studentList1;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
}
表格数据
private StudentTable assertData() {
StudentTable table = new StudentTable();
table.setTitle("我是标题");
List<Student> studentList = 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
效果如下: