freemarker导出word模板(填充数据)

469 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第N天,点击查看活动详情

背景:

公司新接了个需求。需要将word模板里面的待定数据,用数据库的数据填充并且以word形式导出来

前置准备

原始的word模板文件如图:

image.png

查阅资料后,发现freemarker能满足我的需求,所以我使用freemarker来导出文件。 注意:首先要将上图的word转化为freemarker规定的格式(将待填充的数据用${xxxx}表示

转化后的word模板为(${}里面的字段就是后端要返回的字段名)

image.png

然后就将其另存为xml格式,然后将.xml的后缀改为ftl。但是千万要注意,一定要选择2003XML文档格式。不然后续导出的word文件打不开。

image.png

image.png

然后使用notepad++打开该ftl文件,可以使用该插件,格式一目了然。不然数据都在一行。

image.png 查找我们word中的替换的字符,会发现原有的字符中间被标签隔开了,所以我们需要手动删除中间的标签

image.png

改好后(以此类推)

image.png

同时如果你要传图片,一开始一定要传一个图片,占位。在ftl文件中查询pkg:binaryData这个标签,然后将里面的原有数据删除,替换为自定义的字符。

image.png

image.png

代码编写:

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,否则会报错 image.png

如果传图片,我的图片都是存到阿里云,直接获取到它的网络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文件里面加上这个,打包的时候才可以将模板文件打出来,不然会报找不到的错误

image.png

补充(填充列表):

1.list数据填充

像我这里其实是列表。那么如何取数据呢

image.png 需要在ftl里面找到对应的位置,然后包上<#list goodsList as good></#list>标签。其中goodsList是dataMap的key,good就是代指列表中的对象,用于在ftl中展示具体的字段值,如good.price,good.num等。

image.png

image.png

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;

让前端去做文件的下载即可。