Java / Spring Boot + POI 给 Word 添加水印

1、前言(瞎扯)

有个需求:整一个给 Word 加水印的demo,于是我就网上找呗~
看到那个 Aspose 好像是收费的,然后就把目光转向了 POI,看到各种形形色色的也不知道哪个能用。整了一会,自己拷贝出一个比较精简的能用的 demo 了。

2、人狠话不多,上效果图

我一般都是直接上图的,先看效果图(每一页都有的):

水印的分布如果不理想,只能小伙伴们自行研究调整了~

在这里插入图片描述

3、人狠话不多,直接来代码

3.1、我的代码结构

在这里插入图片描述

3.2 、直接贴代码了

pom 依赖的版本不要改,修改版本可能会导致一些东西缺失

3.2.1、pom 依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.lyk</groupId><artifactId>springboot-word-finger</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-word-finger</name><description>springboot-word-finger</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.11.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-scratchpad</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.0</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml-schemas</artifactId><version>4.1.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

3.2.2、处理工具类

import com.microsoft.schemas.office.office.CTLock;
import com.microsoft.schemas.vml.*;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.wp.usermodel.HeaderFooterType;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHeader;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.*;
import java.util.stream.Stream;/*** @author: lyk* @description: Word 添加水印工具类**/
public class WatermarkUtil {private static final Logger LOGGER = LoggerFactory.getLogger(WatermarkUtil.class);/** word字体 */private static final String FONT_NAME = "宋体";/** 字体大小 */private static final String FONT_SIZE = "0.2pt";/** 字体颜色 */private static final String FONT_COLOR = "#d0d0d0";/** 一个字平均长度,单位pt,用于:计算文本占用的长度(文本总个数*单字长度)*/private static final Integer WIDTH_PER_WORD = 10;/** 与顶部的间距 */private static Integer STYLE_TOP = 0;/** 文本旋转角度 */private static final String STYLE_ROTATION = "30";/*** @param inPutPath* @param putPutPath* @param fingerText* @author: lyk* @description: 添加水印入口方法* @date: 2024/1/25 23:42**/public static void waterMarkDocXDocument(String inPutPath, String putPutPath, String fingerText) {long beginTime = System.currentTimeMillis();try (OutputStream out = new FileOutputStream(putPutPath);InputStream in = new FileInputStream(inPutPath);OPCPackage srcPackage = OPCPackage.open(in);XWPFDocument doc = new XWPFDocument(srcPackage)) {// 把整页都打上水印for (int lineIndex = -5; lineIndex < 20; lineIndex++) {STYLE_TOP = 100 * lineIndex;waterMarkDocXDocument(doc, fingerText);}// 输出新文档doc.write(out);LOGGER.info("添加水印成功!,一共耗时" + (System.currentTimeMillis() - beginTime) + "毫秒");} catch (IOException e) {throw new RuntimeException(e);} catch (InvalidFormatException e) {throw new RuntimeException(e);}}/*** 为文档添加水印* @param doc        需要被处理的docx文档对象* @param fingerText 需要添加的水印文字*/public static void waterMarkDocXDocument(XWPFDocument doc, String fingerText) {// 水印文字之间使用8个空格分隔fingerText = fingerText + repeatString(" ", 8);// 一行水印重复水印文字次数fingerText = repeatString(fingerText, 10);// 如果之前已经创建过 DEFAULT 的Header,将会复用XWPFHeader header = doc.createHeader(HeaderFooterType.DEFAULT);int size = header.getParagraphs().size();if (size == 0) {header.createParagraph();}CTP ctp = header.getParagraphArray(0).getCTP();byte[] rsidr = doc.getDocument().getBody().getPArray(0).getRsidR();byte[] rsidrDefault = doc.getDocument().getBody().getPArray(0).getRsidRDefault();ctp.setRsidP(rsidr);ctp.setRsidRDefault(rsidrDefault);CTPPr ppr = ctp.addNewPPr();ppr.addNewPStyle().setVal("Header");// 开始加水印CTR ctr = ctp.addNewR();CTRPr ctrpr = ctr.addNewRPr();ctrpr.addNewNoProof();CTGroup group = CTGroup.Factory.newInstance();CTShapetype shapeType = group.addNewShapetype();CTTextPath shapeTypeTextPath = shapeType.addNewTextpath();shapeTypeTextPath.setOn(STTrueFalse.T);shapeTypeTextPath.setFitshape(STTrueFalse.T);CTLock lock = shapeType.addNewLock();lock.setExt(STExt.VIEW);CTShape shape = group.addNewShape();shape.setId("PowerPlusWaterMarkObject");shape.setSpid("_x0000_s102");shape.setType("#_x0000_t136");// 设置形状样式(旋转,位置,相对路径等参数)shape.setStyle(getShapeStyle(fingerText));shape.setFillcolor(FONT_COLOR);// 字体设置为实心shape.setStroked(STTrueFalse.FALSE);// 绘制文本的路径CTTextPath shapeTextPath = shape.addNewTextpath();// 设置文本字体与大小shapeTextPath.setStyle("font-family:" + FONT_NAME + ";font-size:" + FONT_SIZE);shapeTextPath.setString(fingerText);CTPicture pict = ctr.addNewPict();pict.set(group);}/*** 构建Shape的样式参数** @param fingerText* @return*/private static String getShapeStyle(String fingerText) {StringBuilder sb = new StringBuilder();// 文本path绘制的定位方式sb.append("position: ").append("absolute");// 计算文本占用的长度(文本总个数*单字长度)sb.append(";width: ").append(fingerText.length() * WIDTH_PER_WORD).append("pt");// 字体高度sb.append(";height: ").append("20pt");sb.append(";z-index: ").append("-251654144");sb.append(";mso-wrap-edited: ").append("f");// 设置水印的间隔,这是一个大坑,不能用top,必须要margin-top。sb.append(";margin-top: ").append(STYLE_TOP);sb.append(";mso-position-horizontal-relative: ").append("page");sb.append(";mso-position-vertical-relative: ").append("page");sb.append(";mso-position-vertical: ").append("left");sb.append(";mso-position-horizontal: ").append("center");sb.append(";rotation: ").append(STYLE_ROTATION);return sb.toString();}/*** 将指定的字符串重复repeats次.*/private static String repeatString(String pattern, int repeats) {StringBuilder buffer = new StringBuilder(pattern.length() * repeats);Stream.generate(() -> pattern).limit(repeats).forEach(buffer::append);return new String(buffer);}
}
/*** @author lyk* @version 1.0* @date 2024/1/25 23:16* @description*/
public class Main {public static void main(String[] args) {final String inPath = "src/main/java/com/lyk/finger/doc/aaaa.docx";final String outPath = "src/main/java/com/lyk/finger/doc/out.docx";// 添加水印WatermarkUtil.waterMarkDocXDocument(inPath, outPath, "落魄程序员在线炒粉");}}

4、OK 完事~

拿去好好享用吧~

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

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

相关文章

SpringBoot activemq收发消息、配置及原理

SpringBoot集成消息处理框架 Spring framework提供了对JMS和AMQP消息框架的无缝集成&#xff0c;为Spring项目使用消息处理框架提供了极大的便利。 与Spring framework相比&#xff0c;Spring Boot更近了一步&#xff0c;通过auto-configuration机制实现了对jms及amqp主流框架…

首发:2024全球DAO组织发展研究

作者&#xff0c;张群&#xff08;专注DAO及区块链应用研究&#xff0c;赛联区块链教育首席讲师&#xff0c;工信部赛迪特邀资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09; DAO&#xff08;去中心化自治组织&am…

03-Redis缓存高可用集群

文章目录 1、Redis集群方案比较2、Redis高可用集群搭建redis集群搭建Java操作redis集群 4、Redis集群原理分析槽位定位算法跳转重定位Redis集群节点间的通信机制gossip通信的10000端口网络抖动 Redis集群选举原理分析集群脑裂数据丢失问题集群是否完整才能对外提供服务Redis集群…

LC 2846. 边权重均等查询

2846. 边权重均等查询 难度&#xff1a; 困难 题目大意&#xff1a; 现有一棵由 n 个节点组成的无向树&#xff0c;节点按从 0 到 n - 1 编号。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ui, vi, wi] 表示树中存在一条位于节点 …

Android studio环境搭建过程异常

异常1 e: file:///D:/project/AndroidProject/settings.gradle.kts:5:21: Unexpected tokens (use ; to separate expressions on the same line) dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories { // …

第16章_网络编程(网络通信要素,TCP与UDP协议,网络编程API,TCP网络编程,UDP网络编程,URL编程)

文章目录 第16章_网络编程本章专题与脉络1. 网络编程概述1.1 软件架构1.2 网络基础 2. 网络通信要素2.1 如何实现网络中的主机互相通信2.2 通信要素一&#xff1a;IP地址和域名2.2.1 IP地址2.2.2 域名 2.3 通信要素二&#xff1a;端口号2.4 通信要素三&#xff1a;网络通信协议…

BLIP-2: 基于冻结图像编码器和大型语言模型的语言-图像预训练引导

BLIP-2: 基于冻结图像编码器和大型语言模型的语言-图像预训练引导 项目地址BLIP-2的背景与意义BLIP-2的安装与演示BLIP-2模型库图像到文本生成示例特征提取示例图像-文本匹配示例性能评估与训练引用BLIP-2Hugging Face集成 在语言-图像预训练领域&#xff0c;BLIP-2的出现标志着…

深度学习分类问题之Logistic Regression

逻辑回归模型&#xff0c;虽然名字是回归&#xff0c;但是是解决分类问题。 在线性回归里面&#xff0c;我们根据有效信息&#xff0c;预测下一个由已知信息得到的数值&#xff0c;叫做回归问题&#xff0c;但是在机器学习里面&#xff0c;常见的是分类问题。最常见的就是MNIS…

React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现

commitDeletion 1 &#xff09;概述 在 react commit 阶段的 commitRoot 第二个while循环中调用了 commitAllHostEffects&#xff0c;这个函数不仅仅处理了新增节点&#xff0c;更新节点最后一个操作&#xff0c;就是删除节点&#xff0c;就需要调用 commitDeletion&#xff0…

《动手学深度学习(PyTorch版)》笔记4.5

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过。…

掌握可视化大屏:提升数据分析和决策能力的关键(下)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

(大众金融)SQL server面试题(3)-客户已用额度总和

今天&#xff0c;面试了一家公司&#xff0c;什么也不说先来三道面试题做做&#xff0c;第三题。 那么&#xff0c;我们就开始做题吧&#xff0c;谁叫我们是打工人呢。 题目是这样的&#xff1a; DEALER_INFO经销商授信协议号码经销商名称经销商证件号注册地址员工人数信息维…

three.js 鼠标选中模型弹出标签

效果&#xff1a;请关注抖音 代码&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red;position: relative;"></div><…

202410读书笔记|《半小时漫画青春期》——成为自己世界的星星,这才是最要紧的事儿

202410读书笔记|《半小时漫画青春期&#xff1a;心理篇》——成为自己世界的星星&#xff0c;这才是最要紧的事儿 一、一到考试就焦虑&#xff0c;怎么办&#xff1f;二、以前情绪挺淡定&#xff0c;现在咋动不动就爆发&#xff1f;三、追星那么开心&#xff0c;为啥还要我小心…

ajax点击搜索返回所需数据

html 中body设置&#xff08;css设置跟进自身需求&#xff09; <p idsearch_head>学生信息查询表</p> <div id"div_1"> <div class"search_div"> <div class"search_div_item"> …

数据库设计的一些原则

文章目录 数据库设计原则表之间的关系一对一关系&#xff08;了解&#xff09;一对多&#xff08;多对一&#xff09;多对多联合主键和复合主键 数据库设计准则-范式1、函数依赖2、完全函数依赖3、部分函数依赖4、传递函数依赖5、码 第一范式第二范式第三范式第三范式 数据库设…

【原神游戏开发日志3】登录和注册有何区别?

版权声明&#xff1a; ● 本文为“优梦创客”原创文章&#xff0c;您可以自由转载&#xff0c;但必须加入完整的版权声明 ● 文章内容不得删减、修改、演绎 ● 本文视频版本&#xff1a;见文末 ● 相关学习资源&#xff1a;见文末 前言 ● 这是我们原神游戏开发日记的第三期 ●…

TensorFlow2实战-系列教程1:回归问题预测

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 1、环境测试 import tensorflow as tf import numpy as np tf.__version__打印结果 ‘…

2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024)

2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024) 2024 International Conference on Materials, Control Engineering, and Manufacturing Technology (ICMCEMT 2024) 会议简介&#xff1a; 2024年材料、控制工程与制造技术国际学术会议(ICMCEMT 2024)定于2024年在…

分布式因果推断在美团履约平台的探索与实践

美团履约平台技术部在因果推断领域持续的探索和实践中&#xff0c;自研了一系列分布式的工具。本文重点介绍了分布式因果树算法的实现&#xff0c;并系统地阐述如何设计实现一种分布式因果树算法&#xff0c;以及因果效应评估方面qini_curve/qini_score的不足与应对技巧。希望能…