Java 使用 poi 和 aspose 实现 word 模板数据写入并转换 pdf 增加水印

news2024/11/20 0:37:08

本项目所有源码和依赖资源都在文章顶部链接,有需要可以下载使用

1. 需求描述


  1. 从指定位置读取一个 word 模板
  2. 获取业务数据并写入该 word 模板,生成新的 word 文档
  3. 将新生成的 word 文档转换为 pdf 格式
  4. 对 pdf 文档添加水印

2. 效果预览


  1. word 模板
    在这里插入图片描述
  2. 带水印的 pdf 文档
    在这里插入图片描述

3. 实现思路


  • word 模板数据写入:使用 poi-tl 库实现
  • word 转 pdf 格式:aspose-words 库实现
  • pdf 增加水印:aspose-pdf 库实现

4. 实现过程


4.1 依赖库准备

poi-tl 可以使用 maven 直接从中央仓库下载,但是 aspose 无法下载,需要从网上下载 jar 包并导入本地仓库

  • poi-tl

        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.1</version>
        </dependency>
    
  • aspose-word
    将 jar 包导入本地仓库

        mvn install:install-file \
          -DgroupId="com.aspose" \
          -DartifactId="aspose-words" \
          -Dversion="15.8.0" \
          -Dpackaging="jar" \
          -Dfile="aspose-words-15.8.0-jdk16.jar"
    

    项目中添加依赖

        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-words</artifactId>
            <version>15.8.0</version>
        </dependency>
    
  • aspose-pdf
    将 jar 包导入本地仓库

        mvn install:install-file \
          -DgroupId="com.aspose" \
          -DartifactId="aspose-pdf" \
          -Dversion="17.8" \
          -Dpackaging="jar" \
          -Dfile="aspose.pdf-17.8.jar"
    

    项目中添加依赖

        <dependency>
            <groupId>com.aspose</groupId>
            <artifactId>aspose-pdf</artifactId>
            <version>17.8</version>
        </dependency>
    
  • license.xml
    由于 aspose 库分为免费版和收费版,免费版导出的文档带有试用水印,所以需要添加 license.xml,版权关系不在文章中写出,有需要的可以下载文章顶部链接的完整源码包。

4.2 核心实现方法
@SpringBootApplication
public class Word2PDFApplication {

    public static void main(String[] args) {

        SpringApplication.run(Word2PDFApplication.class, args);

        // word 模板
        String wordTemplatePath = "src/main/resources/templates/简历模板.docx";
        // 写入数据后的 word
        String wordOutputPath = "src/main/resources/templates/简历模板-output.docx";
        // word 转换为 pdf
        String pdfOutputPath = "src/main/resources/templates/简历模板.pdf";
        // pdf 增加水印
        String pdfWithMarkerOutputPath = "src/main/resources/templates/简历模板-marker.pdf";

        // step 1: 封装模板数据
        Map<String, Object> dataMap = getPersonDataMap();

        // step 2: 将数据写入 word 模板
        writeDataToWord(dataMap, wordTemplatePath, wordOutputPath);

        // step 3: 将 word 转换为 pdf
        convertWordToPdf(wordOutputPath, pdfOutputPath);

        // step 4: 将 pdf 增加水印
        addMarkerToPdf(pdfOutputPath, pdfWithMarkerOutputPath);
    }

	// 封装业务数据,用于填入模板对应占位符中
    private static Map<String, Object> getPersonDataMap() {
        Map<String, Object> personDataMap = new HashMap<>();
        personDataMap.put("name", "张三");
        personDataMap.put("sex", "男");
        personDataMap.put("birthDate", "1998-12-02");
        personDataMap.put("id", "420202199812020011");
        personDataMap.put("phone", "18819297766");
        personDataMap.put("skills", "java Spring MySQL ...");
        return personDataMap;
    }

	// 将业务数据写入 word 模板,并生成新的 word 文件
    private static void writeDataToWord(Map<String, Object> dataMap, String wordTemplatePath, String wordOutputPath) {

        XWPFTemplate render = XWPFTemplate.compile(wordTemplatePath).render(dataMap);
        File dest = new File(wordOutputPath);
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        try {
            render.writeToFile(wordOutputPath);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

	// 将新生成的带有业务数据的 word 文档转换为 pdf 格式
    private static void convertWordToPdf(String wordOutputPath, String pdfOutputPath) {
        // 验证 License 若不验证则转化出的 pdf 文档带有水印
        if (!getAsposeWordLicense()) {
            return;
        }
        FileOutputStream os = null;
        try {
            long old = System.currentTimeMillis();
            File file = new File(pdfOutputPath);
            os = new FileOutputStream(file);
            Document doc = new Document(wordOutputPath);
            doc.save(os, SaveFormat.PDF);
            long now = System.currentTimeMillis();
            System.out.println("pdf转换成功,共耗时:" + ((now - old) / 1000.0) + "秒");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.flush();
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

	// 对转换后的 pdf 文档添加水印效果
    private static void addMarkerToPdf(String pdfOutputPath, String pdfWithMarkerOutputPath) {
        // 验证 License 若不验证则增加水印后的 pdf 文档带有试用水印
        boolean asposePdfLicense = getAsposePdfLicense();
        if (!asposePdfLicense) {
            return;
        }

        com.aspose.pdf.Document pdfDocument = new com.aspose.pdf.Document(pdfOutputPath);

        TextStamp textStamp = new TextStamp("水印文本");
        textStamp.getTextState().setFontSize(14.0F);
        textStamp.getTextState().setFontStyle(FontStyles.Bold);
        textStamp.setRotateAngle(45);
        textStamp.setOpacity(0.2);

        // 设置水印间距
        float xOffset = 100;
        float yOffset = 100;

        // 添加水印到每一页
        for (Page page : pdfDocument.getPages()) {
            float xPosition = 0;
            float yPosition = 0;

            // 在页面上添加水印直到页面被覆盖
            while (yPosition < page.getRect().getHeight()) {
                textStamp.setXIndent(xPosition);
                textStamp.setYIndent(yPosition);
                page.addStamp(textStamp);

                xPosition += xOffset;

                // 如果水印超过页面宽度,移到下一行
                if (xPosition + textStamp.getWidth() > page.getRect().getWidth()) {
                    xPosition = 0;
                    yPosition += yOffset;
                }
            }
        }
        // 保存修改后的文档
        pdfDocument.save(pdfWithMarkerOutputPath);
    }

	// 验证 license,否则有试用水印
    private static boolean getAsposeWordLicense() {
        boolean result = false;
        InputStream is = null;
        try {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:license.xml");
            is = resources[0].getInputStream();
            License asposeLic = new License();
            asposeLic.setLicense(is);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

	// 验证 license,否则有试用水印
    private static boolean getAsposePdfLicense() {
        boolean result = false;
        InputStream is = null;
        try {
            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:license.xml");
            is = resources[0].getInputStream();
            com.aspose.pdf.License asposeLic = new com.aspose.pdf.License();
            asposeLic.setLicense(is);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

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

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

相关文章

【递归、搜索与回溯算法】第六节.98. 验证二叉搜索树和230. 二叉搜索树中第K小的元素

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;递归、搜索与回溯算法 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&am…

Golang Struct 继承的深入讨论和细节

1&#xff09;结构体可以使用嵌套匿名结构体所有的字段和方法&#xff0c;即&#xff1a;首字母大写或者小写的字段、方法&#xff0c;都可以使用。 package mainimport "fmt"type A struct {Name stringage int }func (a *A) SayName() {fmt.Println("A say …

澳大利亚量子计算制造商SQC获5000万美元投资用于硅量子计算

​&#xff08;图片来源&#xff1a;网络&#xff09; Silicon Quantum Computing&#xff08;SQC&#xff09;是一家总部位于悉尼的量子计算制造商初创公司。澳大利亚联邦政府、联邦银行&#xff08;CBA&#xff09;&#xff0c;新南威尔士大学和Telstra联合向其投入资金超过5…

算法--单链表

算法–单链表 1.合并链表 1.合并两个排序的链表 解法&#xff1a;这个比较容易&#xff0c;直接对比两个两个链表节点&#xff0c;小的节点直接插入到返回的新链表上 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullptr)…

2023高频前端面试题-TCP

1. TCP 协议是什么? TCP(Transmission Control Protocol 传输控制协议) 是一种面向连接(连接导向) 的、可靠的、 基于 IP 的传输层协议。 TCP 使⽤校验、确认和重传机制来保证可靠传输 而 HTTP 协议 就是建立在 TCP / IP 协议 之上的一种应用。 TCP: 三次握手, 四次挥手~ …

关于本地项目上传到gitee的详细流程

如何上传本地项目到Gitee的流程&#xff1a; 1.Gitee创建项目 2. 进入所在文件夹&#xff0c;右键点击Git Bash Here 3.配置用户名和邮箱 在gitee的官网找到命令&#xff0c;注意这里的用户名和邮箱一定要和你本地的Git相匹配&#xff0c;否则会出现问题。 解决方法如下&…

支付宝证书到期更新完整过程

如果用户收到 支付宝公钥证书 到期通知后&#xff0c;可以根据如下指引更新证书 确认上传成功后就会生成新的证书&#xff0c;把新的证书替换到生产环境就可以了

python DataFrame的用法

pandas 首先要import pandas as pd&#xff0c;如果运行时报错找不到pandas则需要下载对应库&#xff0c;下载流程参考以下链接《Phthon下载库函数》 https://blog.csdn.net/qq_33308135/article/details/134054352?spm1001.2014.3001.5502 创建一个DataFrame import pandas…

kubernetes(3)

目录 service微服务 ipvs模式 clusterip headless nodeport loadbalancer metallb nodeport默认端口 externalname ingress-nginx 部署 基于路径访问 基于域名访问 TLS加密 auth认证 rewrite重定向 canary金丝雀发布 基于header灰度 基于权重灰度 业务域拆分…

【C++】变参函数va_start,va_arg,va_end介绍及实现方式

如果写过JS的话&#xff0c;就知道在JS中定义一个函数&#xff0c;就算输入的实参和形参不一致&#xff0c;也可以同过arguments获取参数 function abc(x) {console.log(x)console.log(arguments[0])console.log(arguments[1])console.log(arguments[2]) } abc(1,2,3)上例输出…

算法通关村第三关-青铜挑战数组专题

本期大纲 线性表基础线性表概念数组概念 数组的基本操作数组创建和初始化查找一个元素增加一个元素修改一个元素删除一个元素 小题一道 - - 单调数组问题小题一道 - - 数组合并问题小结 线性表基础 线性表概念 我们先搞清楚几个基本概念&#xff0c;在很多地方会看到线性结构…

Adversarial attacks and defenses on AI in medical imaging informatics: A survey

Adversarial attacks and defenses on AI in medical imaging informatics: A survey----《AI在医学影像信息学中的对抗性攻击与防御&#xff1a;综述》 背景&#xff1a; 之前的研究表明&#xff0c;人们对医疗DNN及其易受对抗性攻击的脆弱性一直存在疑虑。 摘要&#xff1a;…

实力认可!安全狗入选2023年国产云原生安全技术代表厂商

10月25日&#xff0c;安全牛发布了《2023云原生安全技术应用指南》报告暨年度代表性厂商推荐。 作为国内云原生安全领导厂商&#xff0c;安全狗凭借突出的云原生安全产品与实力成为2023年国产云原生安全技术代表厂商之一。 厦门服云信息科技有限公司&#xff08;品牌名&#xf…

04MQ消息队列

一、同步异步通讯 1.同步通讯和异步通讯 同步通讯&#xff1a;当A服务调用B服务时&#xff0c;一直等待B服务执行完毕&#xff0c;A才继续往下走 优点&#xff1a;时效性强&#xff0c;可以立即得到结果。 缺点&#xff1a; ①耦合度高&#xff0c;加入新的需求就要修改原…

计算机网络【CN】TCP报文段格式【20B】

序号&#xff1a;本报文段所发送的数据的第一个字节的序号确认号&#xff1a;期望收到对方下一个报文段的第一个数据字节的序号。 重要控制位&#xff1a; 紧急位URG&#xff1a;URG1时&#xff0c;标明此报文段中有紧急数据&#xff0c;是高优先级的数据&#xff0c;应尽快传送…

卡巴斯基2009杀毒软件

下载地址&#xff1a;https://user.qzone.qq.com/512526231/main https://user.qzone.qq.com/3503787372/main

冯诺依曼体系结构与操作系统

文章目录 1. 冯诺依曼体系结构概念理解冯诺依曼体系结构的优势 2. 操作系统概念设计OS的目的和定位如何理解管理 1. 冯诺依曼体系结构 概念 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 对于各个部…

java--跳转关键字和随机数

1.跳转关键字 break&#xff1a;跳出并结束当前所在循环的执行 continue&#xff1a;用于跳出当前循环的当次执行&#xff0c;直接进入循环的下一次执行 2.注意事项 break&#xff1a;只能用于结束所在循环&#xff0c;或结束所在switch语句的执行 continue&#xff1a;只能…

工业交换机常用功能有哪些?

工业交换机&#xff0c;又称工业以太网交换机&#xff0c;是一种在OSI参考模型的第二层&#xff08;数据链路层&#xff09;上工作的网络设备。它基于MAC地址识别&#xff0c;并能够封装和转发数据包。那么&#xff0c;工业交换机的常见常用功能有哪些呢&#xff1f; 工业交换…

JavaWeb——关于servlet种mapping地址映射的一些问题

6、Servlet 6.4、Mapping问题 一个Servlet可以指定一个映射路径 <servlet-mapping><servlet-name>hello</servlet-name><url-pattern>/hello</url-pattern> </servlet-mapping>一个Servlet可以指定多个映射路径 <servlet-mapping>&…