开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第N天,点击查看活动详情
背景:
公司新接了个需求。需要将word模板里面的待定数据,用数据库的数据填充并且以word形式导出来。
前置准备
原始的word模板文件如图:
查阅资料后,发现freemarker能满足我的需求,所以我使用freemarker来导出文件。 注意:首先要将上图的word转化为freemarker规定的格式(将待填充的数据用${xxxx}表示)
转化后的word模板为(${}里面的字段就是后端要返回的字段名)
然后就将其另存为xml格式,然后将.xml的后缀改为ftl。但是千万要注意,一定要选择2003XML文档格式。不然后续导出的word文件打不开。
然后使用notepad++打开该ftl文件,可以使用该插件,格式一目了然。不然数据都在一行。
查找我们word中的替换的字符,会发现原有的字符中间被标签隔开了,所以我们需要手动删除中间的标签
改好后(以此类推)
同时如果你要传图片,一开始一定要传一个图片,占位。在ftl文件中查询pkg:binaryData这个标签,然后将里面的原有数据删除,替换为自定义的字符。
代码编写:
1.涉及到的依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.10</version>
</dependency>
2.代码
自己创建map,以key-value形式,存储数据。key就是上一步中ftl文件中的字符。注意:value不能为null,否则会报错
如果传图片,我的图片都是存到阿里云,直接获取到它的网络url。然后直接下载获取到他的byte。这里的HttpUtil用的是hutool工具类。将该接口返回的数据存入到dataMap中,
private String getImageStr(String imgUrl) {
byte[] data = null;
try {
data = HttpUtil.downloadBytes(imgUrl);
} catch (Exception e) {
log.error("下载公司Logo失败", e);
}
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
}
导出具体导出的代码
private void exportWordDoc(Map<String, Object> dataMap, HttpServletResponse response) {
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
configuration.setOutputFormat(XMLOutputFormat.INSTANCE);
ClassPathResource classPathResource = new ClassPathResource("");
//时间格式
String time = DateFormatUtils.format(new Date(), "yyyyMMddHHmmss");
String fileName = "导出的文件" + time + ".doc";
Writer out = null;
OutputStream os = null;
try {
configuration.setDirectoryForTemplateLoading(new File(classPathResource.getURL().getPath()));
Template template = configuration.getTemplate("模板文件.ftl");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
out = new BufferedWriter(new OutputStreamWriter(byteArrayOutputStream, "utf-8"), 10240);
template.process(dataMap, out);
byte[] bytes = byteArrayOutputStream.toByteArray();
response.reset();
response.setContentType("application/x-download; charset=utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes(), "ISO8859-1"));
os = response.getOutputStream();
// 将字节流传入到响应流里,响应到浏览器
os.write(bytes);
} catch (Exception e) {
log.error("导出合同失败",e);
} finally {
if(os != null){
try {
os.close();
} catch (IOException e) {
log.error("OutputStream流关闭失败",e);
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
log.error("Writer关闭失败",e);
}
}
}
}
记得再POM文件里面加上这个,打包的时候才可以将模板文件打出来,不然会报找不到的错误
补充(填充列表):
1.list数据填充
像我这里其实是列表。那么如何取数据呢
需要在ftl里面找到对应的位置,然后包上<#list goodsList as good></#list>标签。其中goodsList是dataMap的key,good就是代指列表中的对象,用于在ftl中展示具体的字段值,如good.price,good.num等。
2.与前端对接报错
现象:本地环境,我写好接口后。通过url路径输入到浏览器上,可以成功下载文件。但是通过前端下载的时候,控制台报IO异常,中断连接。网上找了半天,最终采用,后端传数据到前端,前端来进行下载的操作。最终即可成功下载数据。
操作:
将上述的exportWordDoc代码
// response.reset();
// response.setContentType("application/x-download; charset=utf-8");
// response.setHeader("Content-Disposition", "attachment; filename=" + new String(fileName.getBytes(), "ISO8859-1"));
// os = response.getOutputStream();
// // 将字节流传入到响应流里,响应到浏览器
// os.write(bytes);
给注释掉。然后把字节数组给返回去,即
return bytes;
让前端去做文件的下载即可。