当前位置: 代码网 > it编程>编程语言>Java > Java通过freemarker生成Word文档导出的方式详解

Java通过freemarker生成Word文档导出的方式详解

2025年03月04日 Java 我要评论
1.制作ftl模板在word中制作模板,制作完后以xml格式导出新建word文件,将所需参数用特殊符号 ${} 配置,如下图:注意:freemarker中的占位符是${},例如:这里使用的是【${na

1.制作ftl模板

在word中制作模板,制作完后以xml格式导出

 新建word文件,将所需参数用特殊符号 ${} 配置,如下图:

注意:freemarker中的占位符是${},例如:这里使用的是【${name}】的形式,那么传递的数据中就需要有一个叫做【name】的字段。

导出时选择.xml格式导出

用编译器(notepad++)打开看一下动态参数是否正确,并修改后缀名为ftl

导出xml文件之后,打开这个文件,此时你会看到里面都是xml标签,首先格式化一下,这样看起来会舒服些,可以检查一下你的占位符内容是否满足freemarker语法。因为有些时候,我们导出的xml文件中,可能会将【${xxx}】分隔成两行,从而导致占位符失效,所以有时候需要手动修改一下占位符。导出的word xml文件内容大致如下所示:

2.pom文件引入freemarke依赖,安装依赖

        <dependency>
            <groupid>org.freemarker</groupid>
            <artifactid>freemarker</artifactid>
            <version>2.3.30</version>
        </dependency>

3.java代码实现

3.1 将ftl模板移入项目中

3.2  创建freemarker工具类

引入freemarker依赖之后,就可以使用freemarker编写一个工具类,专门用于处理文件的导出和数据渲染。

import freemarker.template.configuration;
import freemarker.template.template;
 
import javax.servlet.http.httpservletresponse;
import java.io.bufferedwriter;
import java.io.outputstreamwriter;
import java.io.writer;
import java.nio.charset.standardcharsets;
import java.util.map;
 
/**
 * @version 1.0.0
 * @date: 2025/2/14 15:05
 * @author syg
 * @description: freemarker 工具类
 */
public class freemarkerutil {
 
    /**
     * 使用 freemarker 生成 word 文件并导出
     * @param templatename 模板文件路径名称
     * @param filename 生成的文件路径以及名称
     * @param datamodel 填充的数据对象
     * @param response httpservletresponse 对象
     */
    public static void exportword(string templatename, string filename, map<string, object> datamodel, object obj, httpservletresponse response) {
        generatefile(templatename, filename, datamodel, obj, response);
    }
 
    /**
     * 使用 freemarker 生成指定文件并导出
     * @param templatename 模板文件路径名称
     * @param filename 生成的文件路径以及名称
     * @param datamodel 填充的数据对象
     * @param obj 基准类对象
     * @param response httpservletresponse 对象
     */
    private static void generatefile(string templatename, string filename, map<string, object> datamodel, object obj, httpservletresponse response) {
        try {
            // 1、创建配置对象
            configuration config = new configuration(configuration.version_2_3_30);
            config.setdefaultencoding("utf-8");
            //设置了模板文件的加载类和路径,使得freemarker可以从指定的类路径下的/templates目录中加载模板文件。
            config.setclassfortemplateloading(obj.getclass(), "/templates");
            // 2、获取模板文件
            template template = config.gettemplate(templatename);
            // 3、设置响应头
            response.setcontenttype("application/msword");
            response.setheader("content-disposition", "attachment; filename=\"" + filename + "\"");
            // 4、创建写入器
            writer writer = new bufferedwriter(new outputstreamwriter(response.getoutputstream(), standardcharsets.utf_8));
            // 5、渲染模板文件
            template.process(datamodel, writer);
            // 6、关闭写入器
            writer.close();
        } catch (exception e) {
            e.printstacktrace();
        }
    }
 
}

3.3 测试案例代码

 
public class exportworddemo {
    public static void main(string[] args) {
        string templatename = "opinioncollect.ftl";
        string filename = "opinioncollect.docx";
        map<string, object> datamodel = new hashmap<>();
        datamodel.put("orgname", "测试机构02141151");
        datamodel.put("data", "2025-02-14");
        // 执行导出
        freemarkerutil.exportword(templatename, filename, datamodel,this,response);
    }
}

案例所使用的ftl文件共有两个变量data和orgname,若map集合里只给data或orgname一个变量赋值(例如我们只给data赋值        

datamodel.put("data", "2025-02-14");
),则会报错:

freemarker template error:
the following has evaluated to null or missing:

==> orgname [in template "payment.ftl" at line 3, column 23752]

为解决当前异常,这里有俩种方案:

  • 给orgname赋一个空值,即datamodel.put("orgname", ""); 
  • (推荐)修改模板,根据数据结构字段,将需要填充数据的地方,修改为el表达式。${()!} 加括号!号表示可以为null ,也可以解决上述问题,如图所示:

3.4 解析

config.setclassfortemplateloading(obj.getclass(), "/templates"); 中的第一个参数 obj.getclass() 是用于指定模板加载的基准类。具体作用如下:

确定类路径:freemarker 根据传入的类来确定模板文件所在的类路径。这里使用 obj.getclass() 表示以 obj 所属类的类路径为基准。加载模板资源:freemarker 会从该类所在的类路径下查找并加载模板文件,路径为 /templates。

简而言之,第一个参数决定了 freemarker 在哪里查找模板文件。

此处ftl文件和opinioncollectcontroller是在同一个工程目录下,同一个target目录下,所以可以直接传opinioncollectcontroller的对象本身,如果ftl文件和freemarkerutil工具类在同一个工程目录下,我们也可以将config.setclassfortemplateloading(obj.getclass(), "/templates");改成config.setclassfortemplateloading(freemarkerutil.class, "/templates");

3.5  运行代码

运行测试案例的代码,可以看到文件正常导出,且变量已经被替换

4.使用模板导出word勾选框

实际业务需求需要根据业务勾选上对应的项目类型,同时导出的文件需要有点击方框自动勾选的功能,我们可以先在word模板里实现该功能,然后导出成xml格式的文件

打开xml文件,为了方便查看,我们需要安装xmltools的插件

安装完插件之后,我们可以通过notepad查看xml文件

如图所示,找到方框的位置,将方框替换为变量

添加代码对test1和test2进行赋值

public class exportworddemo {
    public static void main(string[] args) {
        string templatename = "opinioncollect.ftl";
        string filename = "opinioncollect.docx";
        map<string, object> datamodel = new hashmap<>();
        datamodel.put("orgname", "测试机构02141151");
        datamodel.put("data", "2025-02-14");
        //☑ □
        datamodel.put("test1", "□");
        datamodel.put("test2", "☑");
        
        // 执行导出
        freemarkerutil.exportword(templatename, filename, datamodel,this,response);
    }
}

执行代码我们可以看到导出的文件如下图,同时方框也可以点击勾选或取消勾选

5.生成动态列表格

模板如下图所示,具体业务需求如下:

1、一个任务编号下绑定的有多个直属单位

2、每个直属单位下需要汇总供应商管理部门、质量部门、技术部门、检测部门、其他相关意见的审核意见

5.1 list标签

//whyc 是集合(collection)的表达式,yc是循环变量的名字,不能是表达式
<#list whyc as yc>
 需要循环的部分 变量用${(yc.supplierreasondesc)!}
</#list>

首先修改ftl文件,找到我们需要循环的部门,进行添加<#list></#list>标签

此处我们要循环的是companyname及其其对应的征求部门的意见,我们在ftl文件找到“备注”这一行结束的位置,${companyname}这一行开始的位置,添加 <#list whyc as yc>

如下图所示:

注意: list一定要放在要循环开始的位置,即前一行结束的位置

然后我们需要找到循环结束的位置,本案例是到相关意见的 ${otherreasondesc}处结束,找到这一行结束的位置,添加 </#list> 标签,如图所示:

添加完<#list></#list>标签后,我们需要调整变量名,将要动态生成的变量改为 循环变量.变量

本案例中,以供应商管理部门为例,我们需要将 ${supplieropinion}改为{yc.supplieropinion}, ${suppliersituationdesc}改为{yc.suppliersituationdesc}, ${supplierreasondesc}改为{yc.supplierreasondesc},质量部门、技术部门等的变量相同方式修改,如图所示:

5.2 相关代码实现

    public void exportword(@requestbody opinioncollectfeedbackqueryvo query, httpservletresponse response) throws exception {
        string templatename = "problemsummary.ftl";
        string filename = "problemsummary.docx";
        map<string, object> datamodel = new hashmap<>();
        map<string, object> datamodel1 = new hashmap<>();
        map<string, object> datamodel2 = new hashmap<>();
        map<string, object> datamodel3 = new hashmap<>();
        list<map<string, object>> list = new arraylist<>();
        datamodel.put("aptitudetaskcode","202502211035");
        datamodel.put("companyname","com20250221");
        datamodel.put("supplieropinion","供应商管理部门审核意见");
        datamodel.put("suppliersituationdesc","供应商管理部门情况描述");
 
        datamodel1.put("companyname","com20250221001");
        datamodel1.put("supplierreasondesc","供应商管理部门原因11111");
        datamodel2.put("companyname","com20250221002");
        datamodel2.put("supplierreasondesc","供应商管理部门原因22222");
        datamodel3.put("companyname","com20250221003");
        datamodel3.put("supplierreasondesc","供应商管理部门原因33333");
 
        list.add(datamodel1);
        list.add(datamodel2);
        list.add(datamodel3);
        datamodel.put("whyc",list);
        // 执行导出
        freemarkerutil.exportword(templatename, filename, datamodel,this,response);
    }

测试导出结果如下: 

由图可见,我们实现了动态生成且数据和我们测试类里填写的数据一致 

5.3 合并单元格

要做到合并单元格,我们首先需要了解一下ftl文件一些属性的含义,我们先看下图:

单元格属性设置:

<w:tcpr>:定义单元格的属性。w:tcw:设置单元格宽度为1992单位(dxa类型)。w:vmerge:设置单元格为垂直合并的起始单元格(restart)。w:nowrap:设置单元格内容不换行(0表示不启用)。w:valign:设置单元格内容垂直居中对齐(center)。

<w:vmerge w:val="continue"/>:设置单元格为垂直合并的延续部分(continue),表示该单元格与上方单元格合并。 

如果生成的ftl文件里没有上述属性,我们可以 在循环的部门的第一行放 <w:vmerge w:val="restart"/> 从第二行开始,所有要合并的单元格放<w:vmerge w:val="continue"/>

5.3.1 编辑ftl文件:

start和end的位置,一定要放在循环开始的<w:tcpr>中:

代码可以这么处理:

    public list<map<string, object>> checklist(list<map<string, object>> list){
        string start = "<w:vmerge w:val=\"restart\"/>";
        string end = "<w:vmerge w:val=\"continue\"/>";
        list.get(0).put("start",start);
        for (int i = 1; i < list.size(); i++) {
            list.get(i).put("end",end);
        }
        return list;
    }

以上便可实现合并单元格 

以上就是java通过freemarker生成word文档导出的方式讲解的详细内容,更多关于java freemarker生成word导出的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com