SpringBoot整合freemarker模板导出word文件

news2025/1/13 13:23:58

文章目录

  • 1、前言
  • 2、需求说明
  • 3、编码
    • 3.1、导入依赖
    • 3.2、接口编写
    • 3.3、工具类
    • 3.4、ftl文件
    • 3.5、测试
  • 4、word转pdf
  • 5、总结

1、前言

在项目中我们有时间需要根据一个word模板文档,批量生成其他的word文档,里面的有些值改变一下而已,那怎么做呢?

2、需求说明

假如说,现在我有个模板文档,内容如下:

在这里插入图片描述

现在上面文档里面有如下变量:

  • username:员工姓名
  • idno:身份证号码
  • hireDate:入职日期
  • work:职位
  • endDate:离职日期

现在我需要针对不同的员工一键生成一份离职证明出来,公司章是一张图片,也需要生成进去,下面我们开始测试

3、编码

基础框架代码地址:https://gitee.com/colinWu_java/spring-boot-base.git

我会在此主干基础上开发

3.1、导入依赖

<!--freemarker-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.30</version>
</dependency>

3.2、接口编写

TestController新增下面接口:

/**
     * 根据ftl模板模板下载word文档
     */
@GetMapping("/downloadWord")
public void downloadWord(HttpServletResponse response){
    Map<String, Object> map = new HashMap<>();
    map.put("username", "王天霸");//姓名
    map.put("idno", "429004199601521245");//身份证号
    map.put("hireDate", "2021年12月12日");//入职日期
    map.put("work", "Java软件工程师");//职务
    map.put("endDate", "2022年11月25日");//离职日期
    map.put("imageData", Tools.getBase64ByPath("D:\\demo\\test.png"));//盖章Base64数据(不包含头信息)
    try {
        FreemarkerExportWordUtil.exportWord(response, map, "离职证明.doc", "EmploymentSeparationCertificate.ftl");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

D:\demo\test.png这张图片就是下面这张图片:

在这里插入图片描述

3.3、工具类

Tools工具类:

package org.wujiangbo.utils;

import org.wujiangbo.domain.user.User;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>工具类</p>
 *
 * @author 波波老师(微信 : javabobo0513)
 * @date 2022/11/16-23:02
 */
public class Tools {

    /**
     * 获得指定图片文件的base64编码数据
     * @param filePath 文件路径
     * @return base64编码数据
     */
    public static String getBase64ByPath(String filePath) {
        if(!hasLength(filePath)){
            return "";
        }
        File file = new File(filePath);
        if(!file.exists()) {
            return "";
        }
        InputStream in = null;
        byte[] data = null;
        try {
            in = new FileInputStream(file);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
        try {
            assert in != null;
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }

    /**
     * @desc 判断字符串是否有长度
     */
    public static boolean hasLength(String str) {
        return org.springframework.util.StringUtils.hasLength(str);
    }
}

FreemarkerExportWordUtil工具类:

package org.wujiangbo.utils;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 模板word导出,工具类
 */
public class FreemarkerExportWordUtil {

    private static Configuration configuration = null;

    static {
        configuration = new Configuration(new Version("2.3.0"));
        configuration.setDefaultEncoding("utf-8");
        //获取模板路径    setClassForTemplateLoading 这个方法默认路径是webRoot 路径下
        configuration.setClassForTemplateLoading(FreemarkerExportWordUtil.class, "/templates");
    }

    private FreemarkerExportWordUtil() {
        throw new AssertionError();
    }

    /**
     * 根据 /resources/templates 目录下的ftl模板文件生成文件并写到客户端进行下载
     * @param response HttpServletResponse
     * @param map 数据集合
     * @param fileName 用户下载到的文件名称
     * @param ftlFileName ftl模板文件名称
     * @throws IOException
     */
    public static void exportWord(HttpServletResponse response, Map map, String fileName, String ftlFileName) throws IOException {
        Template freemarkerTemplate = configuration.getTemplate(ftlFileName);
        // 调用工具类的createDoc方法生成Word文档
        File file = createDoc(map, freemarkerTemplate);
        //将word文档写到前端
        download(file.getAbsolutePath(), response, fileName);
    }

    private static File createDoc(Map<?, ?> dataMap, Template template) {
        //临时文件
        String name = "template.doc";
        File f = new File(name);
        Template t = template;
        try {
            // 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }

    //下载文件的公共方法
    public static void download(String filePath, HttpServletResponse response, String fileName) {
        try {
            setAttachmentResponseHeader(response, fileName);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        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 (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载文件名重新编码
     * @param response 响应对象
     * @param realFileName 真实文件名
     * @return
     */
    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
    {
        String percentEncodedFileName = percentEncode(realFileName);

        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);
    }

    /**
     * 百分号编码工具方法
     *
     * @param s 需要百分号编码的字符串
     * @return 百分号编码后的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException
    {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\\+", "%20");
    }
}

3.4、ftl文件

那么如何制作出ftl模板文件呢?很简单,跟着下面步骤做即可

1、首先新建一个demo.doc文档,然后内容像下面这样写好:

在这里插入图片描述

2、将该word文档导出成demo.xml文档,如下:

在这里插入图片描述

然后:

在这里插入图片描述

这样桌面就会多一个demo.xml文件,此时关闭demo.doc文件,打开demo.xml文件进行编辑

3、修改Demo.xml文件

在demo.xml文件中找到刚才所有的变量,然后都用 包 裹 一 下 , 如 : u s e r n a m e 改 成 {}包裹一下,如:username改成 username{username},idno改成${idno},等等,全部改完

最后搜索pkg:binaryData字符串,用这个包裹着很大一段字符串,那就是文档中公章图片的Base64格式数据,我们将其全部删除,用${imageData}变量代替即可,待会从代码中给这个变量赋值即可,就可以将真正的公章图片替换到文档中了,而且图片大小格式什么的,都会和模板文档中保持一致,所以模板文档的格式先调整后之后再另存为xml文件

修改完成之后,将文件的后缀名由xml改成ftl,然后拷贝到项目resources下的templates目录下,我这里改成:EmploymentSeparationCertificate.ftl了

3.5、测试

打开浏览器,访问接口:http://localhost:8001/downloadWord,就可以下载一个word文档了,打开后内容如下:

在这里插入图片描述

OK,到此测试成功了

4、word转pdf

有些场景需要导出pdf,那么就需要将生成的临时word文件转成pdf后再导出了,下面就介绍一下word转成pdf的方法吧

首先需要导入依赖:

<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-local</artifactId>
    <version>1.0.3</version>
</dependency>
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-transformer-msoffice-word</artifactId>
    <version>1.0.3</version>
</dependency>

工具类:

/**
     * word文档转成PDF文档
     * @param wordPath word文档路径
     * @param pdfPath pdf文档路径
     */
public static void word2pdf(String wordPath, String pdfPath){
    File inputWord = new File(wordPath);
    File outputFile = new File(pdfPath);
    InputStream docxInputStream = null;
    OutputStream outputStream = null;
    IConverter converter = null;
    try  {
        docxInputStream = new FileInputStream(inputWord);
        outputStream = new FileOutputStream(outputFile);
        converter = LocalConverter.builder().build();
        converter.convert(docxInputStream).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
        log.error("word文档转成PDF文档时,发生异常:{}", e.getLocalizedMessage());
    } finally {
        //关闭资源
        try {
            if(docxInputStream != null){
                docxInputStream.close();
            }
            if(outputStream != null){
                outputStream.close();
            }
            if(converter != null){
                converter.shutDown();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

需要注意的是:要使用这个工具类的话,运行这个代码的机器上需要安装WPS或者office软件,否则转换过程中会报错(这可能是一个小瑕疵,我正在寻找其他更加优雅的转换方案,有更好办法的小伙伴,欢迎留言)

我们就调用这个工具类将刚才生成的word文档转成pdf,成功之后内容如下:

在这里插入图片描述

内容和word文档一致,测试成功

5、总结

  1. 本文主要介绍了根据word模板如何导出word文件,已经将word文档如何转成pdf文件
  2. 大家赶紧联系起来吧

如果本文对你有帮助的话,记得帮忙点个赞哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/19428.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Logback+Spring-Aop】实现全面生态化的全链路日志追踪系统服务插件「SpringAOP 整合篇」

承接前文 针对于上一篇【LogbackSpring-Aop】实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」的功能开发指南之后&#xff0c;相信你对于Sl4fj以及Log4j整个生态体系的功能已经有了一定的大致的了解了&#xff0c;接下来我们需要进行介绍关于实现如何将MDC的编程…

家庭实验室系列文章-如何迁移树莓派系统到更大的 SD 卡?

前言 其实这个专题很久很久之前就想写了&#xff0c;但是一直因为各种原因拖着没动笔。 因为没有资格&#xff0c;也没有钱在一线城市买房 (&#x1f602;&#x1f602;&#x1f602;); 但是在要结婚之前&#xff0c;婚房又是刚需。 我和太太最终一起在一线城市周边的某二线城…

【面试题】详解Cookie、localStorage、sessionStorage区别

【面试题】详解Cookie、localStorage、sessionStorage区别 三者基本概念 Cookie localStorage sessionStorage 安全性的考虑 Cookie、localStorage、sessionStorage、indexedDB对比 应用场景 Token一般放在哪里&#xff1f;&#xff1f;&#xff1f; 放在Cookie 放…

【openGauss】在WPS表格里制作连接到openGauss的实时刷新报表

前言 其实我的数据库启蒙&#xff0c;是在一家甲方公司。 当时一进这家公司&#xff0c;就见到了通过连接数据库自动刷新的excel表。当时学会了这招就一发不可收拾&#xff0c;制作出各种自动刷新的报表。 想象一下&#xff0c;有些高管不喜欢打开各种复杂的业务系统或者报表系…

【JS基础】在js中如何简单的使用正则表达式

文章目录前言创建正则字符类型的匹配方法searchreplacematch正则的匹配方法test转义特殊符号冲突正则创建问题记一些规则符号修饰符原子表[]和原子组()配合转义字符其他字符量词一些实用的正则前言 关于正则表达式的介绍&#xff0c;推荐看这篇文章正则表达式30分钟入门教程&a…

[附源码]java毕业设计商务酒店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

鲁棒无范围定位算法 (RRGA)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

LVS-NAT集群搭建

目录 一、环境准备 1、准备三台centos服务器 2、实验拓扑 3、NAT模式介绍 二、LVS-NAT模式部署 1、给lvs服务器安装LVS 2、新建LVS集群 3、添加Real Server服务器节点 4、开启路由转发 5、给后端web服务器配置网关 6、效果测试 一、环境准备 1、准备三台centos服务器…

【C++右值引用】左右值的交叉引用的具体情景,右值详讲

目录 1.右值和左值 2.左值引用和右值引用 3.左右值的交叉引用的具体情景 3.4当不接受返回值就没有办法优化 1.右值和左值 左值与右值是C语言中的概念&#xff0c;但C标准并没有给出严格的区分方式&#xff0c;一般认为&#xff1a;可以放在左边的&#xff0c;或者能 够取地…

基于stm32单片机的光照检测智能台灯

资料编号&#xff1a;101 下面是相关功能视频演示&#xff1a; 101-基于stm32单片机的光照检测智能台灯照明灯Proteus仿真&#xff08;仿真源码全套资料&#xff09;功能介绍&#xff1a; 1、设置为自动模式下&#xff1a;可以检测光照强度&#xff0c;当光照强度<100Lux的…

QT QThread 多线程操作

在QT中&#xff0c;QT应用程序所在的线程为主线程&#xff0c;也称为“GUI线程”&#xff0c;QT GUI必须运行在此线程上&#xff1b;而非主线程称为“工作者线程”&#xff0c;主要处理从主线程中卸下的一些工作&#xff0c;例如数据的同步访问等。需要明确的是&#xff0c;同一…

SSH框架过时了吗?那就最后分享一份阿里架构师整合的SSH框架实战心得吧!

记得当年 java 的企业级框架还是 ssh 的天下&#xff08;spring&#xff0c;struts和hibernate&#xff09;&#xff0c;但是现在感觉 spring 已经完全把那两个框架甩在后边了。用 spring 的人越来越多&#xff0c;用 struts 的人比原来少多了&#xff0c;用 hibernate 的就更少…

BI-SQL丨SNAPSHOT

快照&#xff08;SNAPSHOT&#xff09; 我们在做BI项目的过程&#xff0c;一旦数据涉及到数据库&#xff0c;那么需要考量到的点就比较多。 1.如果数仓是在项目过程中搭建的&#xff0c;那么需要考虑高可用、灾备机制以及安全性问题&#xff1b; 2.如果我们只是需要连接数据库…

Vue3留言墙项目——头部和底部静态页面搭建

文章目录创建项目头部底部创建项目 Vue中使用scss 头部 头部当中有两个按钮&#xff0c;然后根据设计稿可知&#xff0c;本留言墙中有4个按钮&#xff0c;所以可以自己封装一个按钮组件 按钮组件的博客 components/TopNav.vue <template><div class"topNav…

Vue封装一个按钮组件(不使用框架)

做留言墙项目&#xff0c;根据设计稿&#xff0c;发现有四种按钮&#xff0c;这里不使用框架&#xff0c;自己写一个按钮组件 在components下新建MyButton/MyButton.vue <template><button :class"my-btn btn-${type}"><slot></slot></b…

PLC学习笔记(一):概述

如今&#xff0c;电气装置的控制愈发复杂&#xff0c;仅仅依靠低压电器构建逻辑控制电路显得捉襟见肘&#xff0c;而将逻辑控制电路软件化是在满足控制需求前提下降低成本、提高可靠性的重要途经。 那么&#xff0c;我们是选择单片机还是PLC呢&#xff1f;若选择使用单片机&…

嵌入式开发学习之--初识stm32函数库

提示&#xff1a;本篇文章主要以了解为主。 文章目录前言一、库目录及文件简介二、常用资料总结前言 上一篇说到&#xff0c;其实我们不必去直接操作寄存器&#xff0c;也不必自己去写库函数&#xff0c;stm32官方函数库已经满足我们绝大部分的需求了&#xff0c;这一篇文章&a…

逻辑漏洞挖掘

逻辑漏洞# 逻辑漏洞是指由于程序逻辑输入管控不严或者逻辑太复杂&#xff0c;导致程序不能够正常处理或处理错误&#xff0c;逻辑漏洞根据功能需求的不同产生的漏洞方式也不同。一般出现在网站程序的登录注册、密码找回、验证方式、信息查看、交易支付金额等地方。 这类漏洞不…

【第十五章 java反射机制,获取Class类的实例,创建运行时类的对象,调用运行时类中指定的结构】

第十五章 java反射机制&#xff0c;获取Class类的实例&#xff0c;创建运行时类的对象&#xff0c;调用运行时类中指定的结构 1.java反射机制概述 加载完类之后&#xff0c;在堆内存的方法区中就产生了一个Class类型的对象&#xff08;一个类只有一个Class对象&#xff09;&am…

[计算机毕业设计]改进粒子群算法的监测资源调度

前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着准备考研,考公,考教资或者实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科同学来说是充满挑战。为帮助大家顺利通过…