Springboot —— 根据docx填充生成word文件,并导出pdf

news2024/9/29 21:25:36

文章目录

  • 前言
  • 将docx模板填充数据生成doc文件
    • 1、依赖引入
    • 2、doc文件转换docx,并标注别名
    • 3、编写java代码实现数据填充
  • docx文件填充数据导出pdf(web)
    • 1、依赖引入
    • 2、字体文件
    • 3、编写工具类
    • 4、编写测试接口
  • 请求测试
  • 参考资料

前言

在项目中碰见一个需求,需要将.doc的合同,转换为pdf实现打印与预览功能。

将docx模板填充数据生成doc文件

1、依赖引入

填充docx模板,只需要引入一个pom依赖即可实现。

<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.5.0</version>
</dependency>

2、doc文件转换docx,并标注别名

用office或者wps,创建一个001.doc文件,绘制表格,保存。

更改后缀为.docx,确定后,在指定的位置,表示数据接受变量名称。

如下图所示:
在这里插入图片描述

3、编写java代码实现数据填充

import com.deepoove.poi.XWPFTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

/**
 * word 填充测试
 */
public class TestWord {
    public static void main(String[] args) throws IOException {
        Map<String, Object> params = new HashMap<>();
        params.put("username","xiangjiao1");
        params.put("password","******");
        params.put("age",22);
        params.put("email","专注写bug测试中文");
        Resource resource = new ClassPathResource("templates_report/001.docx");
        File file = resource.getFile();
        // 数据填充
        XWPFTemplate template = XWPFTemplate.compile(file).render(params);

        String docOutPath = System.getProperty("user.dir")+File.separator+"springboot-poi"+File.separator+"pdf"+File.separator+ "1.doc";
        OutputStream outputStream = new FileOutputStream(docOutPath);
        template.write(outputStream);

    }
}

运行程序,查看结果。

测试项目结构如下:
在这里插入图片描述

docx文件填充数据导出pdf(web)

1、依赖引入

docx模板中填充数据,并导出pdf类型的文件,除了上面的pom依赖之外,还需要引入其他的依赖信息,完整依赖如下所示:

<!-- docx 数据填充生成 doc文件  这个是主要 -->
<dependency>
    <groupId>com.deepoove</groupId>
    <artifactId>poi-tl</artifactId>
    <version>1.5.0</version>
</dependency>
<!-- doc 转 pdf -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13</version>
</dependency>
<!-- docx4j docx2pdf -->
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j</artifactId>
    <version>6.1.2</version>
</dependency>
<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-export-fo</artifactId>
    <version>6.0.0</version>
</dependency>

2、字体文件

src\main\resources下创建一个font文件夹,其中放入simsun.ttc字体文件。

3、编写工具类

思想很简单

1、先使用上面的docx模板填充数据生成临时doc文件,
2、再将doc文件转换为pdf文件
3、删除临时文件

【注意:】

为了避免出现多人同时操作,导致文件误删的问题,
需要尽可能地保证临时文件名称的唯一性。

import com.deepoove.poi.XWPFTemplate;
import com.itextpdf.text.*;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.docx4j.Docx4J;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.springframework.stereotype.Component;
import java.io.*;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipOutputStream;
 
/**
 * pdf 导出工具类
 */
@Component
@Slf4j
public final class FreeMarkUtils {
 
    /**
     * 根据docx模板填充数据  并生成pdf文件
     *
     * @param dataMap      数据源
     * @param docxFile     docx模板的文件名
     * @return 生成的文件路径
     */
    public static byte[] createDocx2Pdf(Map<String, Object> dataMap, String docxFile) {
        //输出word文件路径和名称 (临时文件名,本次为测试,最好使用雪花算法生成,或者用uuid)
        String fileName = UUID.randomUUID().toString() + ".docx";

        // word 数据填充
        // 生成docx临时文件
        final File tempPath = new File(fileName);
        final File docxTempFile = getTempFile(docxFile);
        XWPFTemplate template = XWPFTemplate.compile(docxTempFile).render(dataMap);
        try {
            template.write(new FileOutputStream(tempPath));
        } catch (IOException e) {
            e.printStackTrace();
        }

        // word转pdf
        final String pdfFile = convertDocx2Pdf(fileName);
        return getFileOutputStream(new File(pdfFile)).toByteArray();
    }
 
    /**
     * word(doc)转pdf
     *
     * @param wordPath doc 生成的临时文件路径
     * @return 生成的带水印的pdf路径
     */
    public static String convertDocx2Pdf(String wordPath) {
        OutputStream os = null;
        InputStream is = null;
        //输出pdf文件路径和名称  (临时文件  尽可能保证文件名称的唯一性)
        final String fileName = UUID.randomUUID().toString() + ".pdf";
        try {
            is = new FileInputStream(wordPath);
            WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
            Mapper fontMapper = new IdentityPlusMapper();
            fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
            fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
            fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
            fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
            fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
            fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
            fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
            fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
            fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
            fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
            fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
            fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
            fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
            fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
            //解决宋体(正文)和宋体(标题)的乱码问题
            PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
            PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
            // 字体文件
            PhysicalFonts.addPhysicalFonts("SimSun", WordUtils.class.getResource("/font/simsun.ttc"));
 
            mlPackage.setFontMapper(fontMapper);
            os = new FileOutputStream(fileName);
 
            //docx4j  docx转pdf
            FOSettings foSettings = Docx4J.createFOSettings();
            foSettings.setWmlPackage(mlPackage);
            Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
            is.close();//关闭输入流
            os.close();//关闭输出流
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 删除docx 临时文件
            File file = new File(wordPath);
            if (file != null && file.isFile() && file.exists()) {
                file.delete();
            }
            try {
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return fileName;
    }
 
    /**
     * 文件转字节输出流
     *
     * @param outFile 文件
     * @return
     */
    public static ByteArrayOutputStream getFileOutputStream(File outFile) {
        // 获取生成临时文件的输出流
        InputStream input = null;
        ByteArrayOutputStream bytestream = null;
        try {
            input = new FileInputStream(outFile);
            bytestream = new ByteArrayOutputStream();
            int ch;
            while ((ch = input.read()) != -1) {
                bytestream.write(ch);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bytestream.close();
                input.close();
                log.info("删除临时文件");
                if (outFile.exists()) {
                    outFile.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bytestream;
    }
 
    /**
     * 获取资源文件的临时文件
     * 资源文件打jar包后,不能直接获取,需要通过流获取生成临时文件
     *
     * @param fileName 文件路径 templates/xxx.docx
     * @return
     */
    public static File getTempFile(String fileName) {
        final File tempFile = new File(fileName);
        InputStream fontTempStream = null;
        try {
            fontTempStream = FreeMarkUtils.class.getClassLoader().getResourceAsStream(fileName);
            FileUtils.copyInputStreamToFile(fontTempStream, tempFile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fontTempStream != null) {
                    fontTempStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return tempFile;
    }
 
 
    /**
     * 插入图片水印
     * @param srcByte 已生成PDF的字节数组(流转字节)
     * @param destFile 生成有水印的临时文件 temp.pdf
     * @return
     */
    public static FileOutputStream addWaterMark(byte[] srcByte, String destFile) {
        // 待加水印的文件
        PdfReader reader = null;
        // 加完水印的文件
        PdfStamper stamper = null;
        FileOutputStream fileOutputStream = null;
        try {
            reader = new PdfReader(srcByte);
            fileOutputStream = new FileOutputStream(destFile);
            stamper = new PdfStamper(reader, fileOutputStream);
            int total = reader.getNumberOfPages() + 1;
            PdfContentByte content;
            // 设置字体
            //BaseFont font = BaseFont.createFont();
            // 循环对每页插入水印
            for (int i = 1; i < total; i++) {
                final PdfGState gs = new PdfGState();
                // 水印的起始
                content = stamper.getUnderContent(i);
                // 开始
                content.beginText();
                // 设置颜色 默认为蓝色
                //content.setColorFill(BaseColor.BLUE);
                // content.setColorFill(Color.GRAY);
                // 设置字体及字号
                //content.setFontAndSize(font, 38);
                // 设置起始位置
                // content.setTextMatrix(400, 880);
                //content.setTextMatrix(textWidth, textHeight);
                // 开始写入水印
                //content.showTextAligned(Element.ALIGN_LEFT, text, textWidth, textHeight, 45);
 
                // 设置水印透明度
                // 设置笔触字体不透明度为0.4f
                gs.setStrokeOpacity(0f);
                Image image = null;
                image = Image.getInstance("url");
                // 设置坐标 绝对位置 X Y 这个位置大约在 A4纸 右上角展示LOGO
                image.setAbsolutePosition(472, 785);
                // 设置旋转弧度
                image.setRotation(0);// 旋转 弧度
                // 设置旋转角度
                image.setRotationDegrees(0);// 旋转 角度
                // 设置等比缩放 图片大小
                image.scalePercent(4);// 依照比例缩放
                // image.scaleAbsolute(200,100);//自定义大小
                // 设置透明度
                content.setGState(gs);
                // 添加水印图片
                content.addImage(image);
                // 设置透明度
                content.setGState(gs);
                //结束设置
                content.endText();
                content.stroke();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        } finally {
            try {
                stamper.close();
                fileOutputStream.close();
                reader.close();
            } catch (DocumentException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return fileOutputStream;
    }
}

4、编写测试接口

import cn.xj.util.FreeMarkUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/report")
public class ReportController {

    @GetMapping("/doc2pdf")
    public void doc2pdf(HttpServletResponse response) {
        Map<String, Object> params = new HashMap<>();
        params.put("username","xiangjiao1");
        params.put("password","******");
        params.put("age",22);
        params.put("email","专注写bug测试中文");

        final byte[] data = FreeMarkUtils.createDocx2Pdf(params, "templates_report/001.docx");
        String fileName = UUID.randomUUID().toString() + "_001_test.pdf";
        generateFile(response, data, fileName);
    }

    /**
     * 下载文件
     * @param response 相应
     * @param data 数据
     * @param fileName 文件名
     */
    private void generateFile(HttpServletResponse response, byte[] data, String fileName) {
        response.setHeader("content-Type", "application/octet-stream");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);
        try {
            response.getOutputStream().write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                response.getOutputStream().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

请求测试

http://localhost/report/doc2pdf

在这里插入图片描述

参考资料

Spring Boot Freemark HTML 生成 PDF、生成水印Logo、docx文件生成PDF,Jar包运行可读取模板文件、字体文件

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

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

相关文章

[4] 实现无头单向非循环链表

目录 一、框架 二、实现各个方法 三、测试各个方法 四、源码 一、框架 一个单向链表的节点&#xff0c;有数值域和下一个节点的地址 我们可以设计一个链表类&#xff0c;在这个链表类设计一个节点内部类&#xff0c;这里设计成内部类的形式&#xff0c;因为链表是由节点组…

《论文阅读》SetGNER:General Named Entity Recognition as Entity Set Generation

0.总结 不知道是不是大模型的流行还是什么其他原因&#xff0c;导致现在网上都没有人来分享NER模型的相关论文了~本文方法简单&#xff0c;代码应该也比较简单&#xff08;但是没见作者放出来&#xff09;。推荐指数&#xff1a;★★☆☆☆ 1. 动机 处理三种不同场景的NER 与…

python笔记:datetime

处理日期和时间 1 常量 MINYEAR datetime允许的最小年份 MAXYEAR datetime允许的最大年份 2 数据类型 datetime.date带有属性year,month,daydatetime.time带有属性hour,minute,second,microsecond,tzinfodatetime.datetime带有属性year,month,day,hour,minute,second,m…

【网络安全】文件包含漏洞

文件包含漏洞文件包含漏洞原理文件包含漏洞经常出现的函数尝试查看etc/passwd敏感文件渗透过程上传phpinfo和webshell到服务器并使用工具连接其他方式包含日志文件getshell包含环境变量getshell文件包含漏洞原理 文件包含漏洞是指&#xff0c;程序开发人员一般会把重复使用的函…

【C语言学习4——整型数据类型】

C语言学习4——整型数据类型整型数据类型用sizeof关键词来测量大小三位二进制表示的数值范围数值的补码表示法各种整型类型的数值范围是多少无符号整型整型数据类型 在上一节当中&#xff0c;我们遇到了用int关键词&#xff08;整数integer的缩写&#xff09;来表示一个整数的…

Python Qt5 入门教程

Python Qt5 入门教程 Python Qt5是一个强大的GUI工具包&#xff0c;可以用来设计各种桌面应用程序&#xff0c;包括图形用户界面、数据库应用程序等。本教程将带你入门Python Qt5&#xff0c;从安装开始到图形界面的设计以及常见的控件和事件。 安装 Python Qt5需要使用PyQt5…

好用的便签APP排行榜前十名?

我是一名时间管理与自律达人&#xff0c;而便签应用程序就是必备与理想的时间管理工具。经过自己长期的总结认为好用的电脑手机云便签APP应用程序应该具备以下功能。 1、多设备同步&#xff1a;可以方便地将电脑和手机之间的数据同步&#xff0c;随时随地管理便签内容。 2、分…

海思部署分类模型

1、原始模型 onnx转caffe报错没有globalaverage层。 于是转化成&#xff1a; onnx转化caffe之后&#xff0c;修改prototxt文件&#xff0c;加上globalaverage和reshape层. 参考&#xff1a;https://blog.csdn.net/z649431508/article/details/113425275 layer { name: “Glob…

PPO算法-理论篇

1. Policy Gradient 【李宏毅深度强化学习笔记】1、策略梯度方法&#xff08;Policy Gradient&#xff09; 李宏毅深度强化学习-B站 2. PPO PPO 算法 PPO算法更新过程如下&#xff1a; 初始化policy参数θ0\theta^0θ0在每一步迭代中&#xff1a; 使用θk\theta^kθk与环境…

架构师:不想当架构师的程序员不是好程序员

引言 不想当将军的士兵不是好士兵。 很多程序员的梦想&#xff0c;就是将来能成为一名架构师。 包括我刚学编程那时候&#xff0c;也是以当架构师为目标&#xff0c;觉得不想当架构师的程序员不是好程序员&#xff0c;希望将来能成为一个优秀的架构师。就像拿破仑那句名言&am…

进程调度算法(操作系统)

1、 前置知识 1.1 非抢占式与抢占式 1.1.1 非抢占式 非抢占式指的是一个线程的在执行期间&#xff0c;另一个线程的到达&#xff0c;尽管各项标准都优于执行线程&#xff08;例如优先级高于当前执行线程&#xff09;&#xff0c;也不会抢占CPU资源&#xff0c;会耐心的等待该…

【matlab程序】海图坐标轴单位的唯一

【matlab程序】海图坐标轴单位的唯一 【matlab程序】海图坐标轴单位的唯一 本文写作来源&#xff0c;从实际出发&#xff0c;用于实际&#xff1a; 热带海洋学报&#xff0c;投稿须知&#xff1a; 其中一条关于海图制作规范中&#xff1a; 经度标识&#xff08;E, W&#…

真题详解(0/1背包)-软件设计(四十九)

真题详解(线性表)-软件设计&#xff08;四十八)https://blog.csdn.net/ke1ying/article/details/130119249 多态有四种类型&#xff1a; 参数多态&#xff1a;应用比较广泛的多态&#xff0c;称为最纯多态。 包含多态&#xff1a;最常见的就是子类型化。 过载多态&#xff1…

Nfinity: YouTube创作者如何通过SocialFi变现

Nfinity推出了一个具有革命性的SocialFi平台&#xff1a;该平台通过生成NFT来帮助YouTube创作者实现内容变现。 YouTube做为全球第一大视频内容平台&#xff0c;尽管通过付费会员和广告收入的分成&#xff0c;为创作者们提供了大量的盈利机会&#xff0c;但它也存在很多的局限性…

【MybatisPlus快速入门】—— 基础入门

入门篇 我们先简单回顾一下 Mybatis 的用法&#xff0c;再引出MybatisPlus 1.1 Mybatis 框架回顾 &#x1f314; 1、什么是Mybatis框架呢&#xff1f; 一个持久层框架&#xff0c;目的是简化持久层的开发我们就使用springboot整合Mybatis&#xff0c;实现Mybatis框架的搭建…

NBT - 生成式蛋白设计,AI带来的蛋白质工程飞跃

科学家们创建了一个能够从头合成人工酶的人工智能系统。实验测试发现&#xff0c;一些酶与自然界中发现的酶具有相同的功效&#xff0c;即使人工生成的氨基酸序列与任何已知的天然蛋白质明显不同。实验表明&#xff0c;尽管自然语言处理是为了阅读和编写语言文本而开发的&#…

蓝牙技术|安卓将支持超宽带语音,蓝牙通话更清晰

Android 的蓝牙通话即将迎来质的飞跃&#xff0c;超宽带语音技术将让你的声音更清晰、更真实。 Android 专家 Mishaal Rahman 发现&#xff0c;Android 开源项目仓库中最近添加了一个补丁&#xff0c;实现了与蓝牙免提配置文件 v1.9 相关的功能。据IT之家了解&#xff0c;这个…

【初识C++】(关键字,命名空间)

文章目录一、C中的关键字二、命名空间1.命名空间规则展开命名空间域 和 #include 的区别2.正确使用命名空间三、C中的输入和输出一、C中的关键字 二、命名空间 命名空间是对于全局变量来说&#xff0c;我们在定义变量或函数时&#xff0c;函数名可能会和库中的函数名产生冲突。…

window 和 linux 安装 Tesseract-OCR

一、 Window 安装 Tesseract-OCR 1.安装 tesseract-ocr-w64-setup-5.3.1.20230401.exe 下载地址&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 2. 配置 PATH 环境变量 3. 配置TESSDATA_PREFIX 环境变量 4. 在 cmd 中查询是否安装成功 5. 在安装的目录 tessda…

算法刷题总结 (七) 双指针

算法总结7 双指针一、双指针的概念1.1、什么是双指针&#xff1f;1.2、常见类型1.2.1、快慢指针1.2.2、左右端点指针1.2.3、区间指针 - 滑动窗口汇总二、经典例题2.1、快慢指针&#xff08;1&#xff09;、链表判环141. 环形链表142. 环形链表 II287. 寻找重复数876. 链表的中间…