1024程序员节|历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案

news/2024/11/7 10:47:49/

🍅程序员小王的博客:程序员小王的博客
🍅CSDN地址:程序员小王java
🍅 欢迎点赞 👍 收藏 ⭐留言 📝
🍅 如有编辑错误联系作者,如果有比较好的文章欢迎分享给我,我会取其精华去其糟粕
🍅java自学的学习路线:java自学的学习路线

一、前言

今天是1024程序员节,必须得写一篇博客庆祝一下了!九月中旬到十月底,我和同事参加了某个系统的开发,涉及到对PDF的电子签字+盖章+防伪二维码+水印等,我最开始选择使用pageoffice实现PDF的盖章和签字,并且也写了一篇博客来进行详细的介绍(pageoffice实现签名盖章:http://t.csdn.cn/nNxpe),但是出现一个问题,PageOffice支持JAVA、ASP.NET、PHP多种编程开发语言,使开发集成简单高效,事半功倍。让集成PAGEOFFICE的协同办公系统更具价值,但是他是卓正软件公司的一个项目,需要收费,并且安全系数不一定能得到保证,而这个项目需要上生产,所以经过多方的研究,学习,总结,今天终于将一套PDF集成线上签字+盖章+防伪二维码+水印的一系列解决方法总结出来,今天写这篇博客进行开源

项目开源地址:https://gitee.com/wanghengjie563135/pdf.git
csdn下载地址:https://download.csdn.net/download/weixin_44385486/86813947

二、使用itextPDF实现PDF电子公章工具类

1、电子公章的制作

我们需要实现电子公章盖章,但是不能使用公司的章,我们这次推荐使用的线上做章工具来模拟电子印章

  • 做章网站:http://seal.biaozhiku.com/

  • 我们选择圆形印章

  • 然后输入公司名,输入章名输入编码然后点击395生成,最后点击保存图片,我们的个人专业章就实现了

  • 电子公章效果如图:
    在这里插入图片描述

  • PDF模板图

在这里插入图片描述

  • 生成PDF效果图

2、itextPDF的相关依赖

      <!-- itextpdf依赖 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.10</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><dependency><!-- 摘要算法 --><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.49</version></dependency>
  • 使用的是boot项目,所以完整依赖是
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version>
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- itextpdf依赖 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.10</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.49</version></dependency></dependencies>

3、相关配置及数字签名的配置

(1)摘要算法

  • 我们项目启动之后报错

  • 需要加这个配置文件就不报错了,这个主要原因是摘要算法没有,需要引入相关依赖
   <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.49</version></dependency>
  • 涉及到加密算法就需要数字签名了(数字签名格式,CMS,CADE),我们就需要一个文件(我的命名是:server.p12)这个东西需要我们自己电脑生成数字签名

(2)java工具keytool生成p12数字证书文件

Keytool是用于管理**和证书的工具,位于%JAVA_HOME%/bin目录。
使用JDK的keytool工具

  • keytool在jdk的bin目录下

2. 打开keytool所在的bin目录,然后在上面的路径显示框中输入CMD,然后回车,即可在当前文件夹下打开命令提示符,并且路径是当前文件夹。

  • 生成数字文件,在命令行输入
keytool -genkeypair -alias whj -keypass 111111 -storepass 111111 -dname “C=CN,ST=SD,L=QD,O=haier,OU=dev,CN=haier.com” -keyalg RSA -keysize 2048 -validity 3650 -keystore D:\keystore\server.keystore

参数解释:

storepass keystore 文件存储密码
keypass 私钥加解密密码
alias 实体别名(包括证书私钥)
dname 证书个人信息
keyalt 采用公钥算法,默认是DSA keysize **长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA)
validity 有效期
keystore 指定keystore文件

  • 转换为p12格式

在命令行输入

keytool -importkeystore -srckeystore D:\keystore\server.keystore -destkeystore D:\keystore\whj.p12 -srcalias whj -destalias serverkey -srcstoretype jks -deststoretype pkcs12 -srcstorepass 111111 -deststorepass 111111 -noprompt

  • 生成的最终文件

4、项目结构及源码

  • 工具类(可以直接复制)
package com.whj.pdf;import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.*;
import com.whj.entity.SignatureInfo;import java.io.*;
import java.security.GeneralSecurityException;/*** @author 王恒杰* @date 2022/10/13 22:52* @Description:盖章功能工具类*/
public class ItextUtil {public static final char[] PASSWORD = "111111".toCharArray();// keystory密码/*** 单多次签章通用** @param src* @param target* @param signatureInfo* @throws GeneralSecurityException* @throws IOException* @throws DocumentException*/@SuppressWarnings("resource")public void sign(String src, String target, SignatureInfo signatureInfo) {InputStream inputStream = null;FileOutputStream outputStream = null;ByteArrayOutputStream result = new ByteArrayOutputStream();try {inputStream = new FileInputStream(src);ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream();PdfReader reader = new PdfReader(inputStream);// 创建签章工具PdfStamper ,最后一个boolean参数是否允许被追加签名// false的话,pdf文件只允许被签名一次,多次签名,最后一次有效// true的话,pdf可以被追加签名,验签工具可以识别出每次签名之后文档是否被修改PdfStamper stamper = PdfStamper.createSignature(reader,tempArrayOutputStream, '\0', null, true);// 获取数字签章属性对象PdfSignatureAppearance appearance = stamper.getSignatureAppearance();appearance.setReason(signatureInfo.getReason());appearance.setLocation(signatureInfo.getLocation());// 设置签名的位置,页码,签名域名称,多次追加签名的时候,签名预名称不能一样 图片大小受表单域大小影响(过小导致压缩)// 签名的位置,是图章相对于pdf页面的位置坐标,原点为pdf页面左下角// 四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角y//四个参数的分别是,图章左下角x,图章左下角y,图章右上角x,图章右上角yappearance.setVisibleSignature(new Rectangle(280, 220, 140, 600), 1, "sig1");// 读取图章图片Image image = Image.getInstance(signatureInfo.getImagePath());appearance.setSignatureGraphic(image);appearance.setCertificationLevel(signatureInfo.getCertificationLevel());// 设置图章的显示方式,如下选择的是只显示图章(还有其他的模式,可以图章和签名描述一同显示)appearance.setRenderingMode(signatureInfo.getRenderingMode());// 这里的itext提供了2个用于签名的接口,可以自己实现,后边着重说这个实现// 摘要算法ExternalDigest digest = new BouncyCastleDigest();// 签名算法ExternalSignature signature = new PrivateKeySignature(signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(),null);// 调用itext签名方法完成pdf签章 //数字签名格式,CMS,CADEMakeSignature.signDetached(appearance, digest, signature,signatureInfo.getChain(), null, null, null, 0,MakeSignature.CryptoStandard.CADES);inputStream = new ByteArrayInputStream(tempArrayOutputStream.toByteArray());// 定义输入流为生成的输出流内容,以完成多次签章的过程result = tempArrayOutputStream;outputStream = new FileOutputStream(new File(target));outputStream.write(result.toByteArray());outputStream.flush();} catch (Exception e) {e.printStackTrace();} finally {try {if (null != outputStream) {outputStream.close();}if (null != inputStream) {inputStream.close();}if (null != result) {result.close();}} catch (IOException e) {e.printStackTrace();}}}}
  • 实体类
package com.whj.entity;import com.itextpdf.text.pdf.PdfSignatureAppearance;import java.security.PrivateKey;
import java.security.cert.Certificate;/*** @author 王恒杰* @date 2022/10/13 22:52* @Description:*/
public class SignatureInfo {private String reason; //签名的原因,显示在pdf签名属性中private String location;//签名的地点,显示在pdf签名属性中private String digestAlgorithm;//摘要算法名称,例如SHA-1private String imagePath;//图章路径private String fieldName;//表单域名称private Certificate[] chain;//证书链private PrivateKey pk;//签名私钥private int certificationLevel = 0; //批准签章private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式:仅描述,仅图片,图片和描述,签章者和描述//图章属性private float rectllx;//图章左下角xprivate float rectlly;//图章左下角yprivate float recturx;//图章右上角xprivate float rectury;//图章右上角ypublic float getRectllx() {return rectllx;}public void setRectllx(float rectllx) {this.rectllx = rectllx;}public float getRectlly() {return rectlly;}public void setRectlly(float rectlly) {this.rectlly = rectlly;}public float getRecturx() {return recturx;}public void setRecturx(float recturx) {this.recturx = recturx;}public float getRectury() {return rectury;}public void setRectury(float rectury) {this.rectury = rectury;}public String getReason() {return reason;}public void setReason(String reason) {this.reason = reason;}public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public String getDigestAlgorithm() {return digestAlgorithm;}public void setDigestAlgorithm(String digestAlgorithm) {this.digestAlgorithm = digestAlgorithm;}public String getImagePath() {return imagePath;}public void setImagePath(String imagePath) {this.imagePath = imagePath;}public String getFieldName() {return fieldName;}public void setFieldName(String fieldName) {this.fieldName = fieldName;}public Certificate[] getChain() {return chain;}public void setChain(Certificate[] chain) {this.chain = chain;}public PrivateKey getPk() {return pk;}public void setPk(PrivateKey pk) {this.pk = pk;}public int getCertificationLevel() {return certificationLevel;}public void setCertificationLevel(int certificationLevel) {this.certificationLevel = certificationLevel;}public PdfSignatureAppearance.RenderingMode getRenderingMode() {return renderingMode;}public void setRenderingMode(PdfSignatureAppearance.RenderingMode renderingMode) {this.renderingMode = renderingMode;}}
  • 测试
package com.whj.pdf;import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.whj.entity.SignatureInfo;import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;import static com.whj.pdf.ItextUtil.PASSWORD;/*** @author 王恒杰* @date 2022/10/24 10:42* @Description:  盖章功能实现*/
public class PdfStamp {public static void main(String[] args) {try {ItextUtil app = new ItextUtil();// 将证书文件放入指定路径,并读取keystore ,获得私钥和证书链String pkPath = "src/main/resources/whj.p12";KeyStore ks = KeyStore.getInstance("PKCS12");ks.load(new FileInputStream(pkPath), PASSWORD);String alias = ks.aliases().nextElement();PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);// 得到证书链Certificate[] chain = ks.getCertificateChain(alias);//需要进行签章的pdfString path = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\程序员小王.pdf";// 封装签章信息SignatureInfo signInfo = new SignatureInfo();signInfo.setReason("理由");signInfo.setLocation("位置");signInfo.setPk(pk);signInfo.setChain(chain);signInfo.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);signInfo.setDigestAlgorithm(DigestAlgorithms.SHA1);signInfo.setFieldName("demo");// 签章图片signInfo.setImagePath("D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\chapter.png");signInfo.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC); 值越大,代表向x轴坐标平移 缩小 (反之,值越小,印章会放大)signInfo.setRectllx(100); 值越大,代表向y轴坐标向上平移(大小不变)signInfo.setRectlly(200);// 值越大   代表向x轴坐标向右平移  (大小不变)signInfo.setRecturx(150);// 值越大,代表向y轴坐标向上平移(大小不变)signInfo.setRectury(150);//签章后的pdf路径app.sign(path, "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\out.pdf", signInfo);} catch (Exception e) {e.printStackTrace();}}}

5、结果展示

三、thymeleaf+itext签字功能+PDF文件加密实现

  • 所以需依赖

  • 需要手工导入一个jar包(jar包我放在了源码里面,需要自行下载)

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.5.RELEASE</version>
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- itextpdf依赖 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.10</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.49</version></dependency><!--guava是来自Google的Java核心类库。包含了新的集合类型(例如:复合map、复合set)、不可变集合,以及一些对于并发、I/O、hashing、缓存、原型、字符串等的通用功能。guava被广泛使用在Google的项目中,也被广泛的使用在其他公司里。--><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>25.0-jre</version></dependency></dependencies>

因为这个的相关类太多,只展示了核心代码,全部代码请到博客底部下载源码

1、签字图片上传和签字实现的Controller

package com.whj.controller;import com.whj.service.SignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;/*** @author 王恒杰* @date 2022/10/24 13:57* @Description: 实现签字上传和签字功能*/
@RestController
@RequestMapping("/sign")
public class SignController {@Autowiredprivate SignService signService;@PostMapping(value = "/uploadSign")@ResponseBodypublic String uploadSign(String img) {return signService.uploadSign(img);}@PostMapping(value = "/sign")@ResponseBodypublic String sign() {return signService.sign(id);}
}

2、签字上传,签字,PDF文件加密业务层实现

/*** @author 王恒杰* @date 2022/10/24 14:00* @Description:*/
@Service
public class SignServiceImpl implements  SignService{@Overridepublic String uploadSign(String img) {//String idCard="3000";// 生成jpeg图片 idCard.asString()String url = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\img\\sign.png";try {if (img == null) // 图像数据为空{return "no";}int i = img.indexOf("base64,") + 7;//获取前缀data:image/gif;base64,的坐标String newImage = img.substring(i, img.length());//去除前缀BASE64Decoder decoder = new BASE64Decoder();// Base64解码byte[] bytes = decoder.decodeBuffer(newImage);for (int j = 0; j < bytes.length; ++j) {if (bytes[j] < 0) {// 调整异常数据bytes[j] += 256;}}OutputStream out = new FileOutputStream(url);out.write(bytes);out.flush();out.close();return "yes";} catch (Exception e) {return "no";}}@Overridepublic String sign() {try {//初始化文件String srcPath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\out.pdf";//输出文件String outPath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\signOut.pdf";signimg("D:\\Idea\\stamp\\Itext\\src\\main\\resources\\img\\sign.png", srcPath, "批准人", outPath);//加密EncryptPDFUtil.encryptPDF(outPath, "EncryptPDF");return "yes";} catch (Exception e) {return "no";}}
  • 封装签字的方法
public static void signimg(String imgurl, String pdfurl, String keywords, String outPDFPath) throws IOException {List list = new ArrayList();SignPDFBean bean1 = new SignPDFBean();bean1.setKeyStorePass("111111");bean1.setKeyStorePath("src/main/resources/server.p12");bean1.setKeyWord(keywords);bean1.setSealPath(imgurl);bean1.setSignLocation(keywords);bean1.setSignReason("计量检定证书签字");list.add(bean1);SignPDFRequestBean requestBean = new SignPDFRequestBean();requestBean.setSrcPDFPath(pdfurl);requestBean.setOutPDFPath(outPDFPath);requestBean.setSignPDFBeans(list);long startTime = System.currentTimeMillis();// 1.解析pdf文件Map<Integer, List<KeyWordBean>> map = KeywordPDFUtils.getPDFText(requestBean.getSrcPDFPath());// 2.获取关键字坐标List<SignPDFBean> beans = requestBean.getSignPDFBeans();byte[] fileData = null;InputStream in = null;for (int i = 0; i < beans.size(); i++) {SignPDFBean pdfBean = beans.get(i);KeyWordBean bean = KeywordPDFUtils.getKeyWordXY1(map, pdfBean.getKeyWord());if (null == bean) {System.out.println("未查询到关键字。。。");}System.out.println("111" + bean.toString());long keyTime = System.currentTimeMillis();if (i == 0) {in = new FileInputStream(requestBean.getSrcPDFPath());} else {in = new ByteArrayInputStream(fileData);}// 3.进行盖章fileData = SignPDFUtils.sign(pdfBean.getKeyStorePass(), pdfBean.getKeyStorePath(), in, pdfBean.getSealPath(), bean.getX(), bean.getY(), bean.getPage(), pdfBean.getSignReason(), pdfBean.getSignLocation());long signTime = System.currentTimeMillis();}// 4.输出盖章后pdf文件FileOutputStream f = new FileOutputStream(new File(requestBean.getOutPDFPath()));f.write(fileData);f.close();in.close();long endTime = System.currentTimeMillis();System.out.println("总时间:" + (endTime - startTime));}}

3、封装的实现PDF加密文件的方法

package com.whj.util;import com.spire.pdf.PdfDocument;
import com.spire.pdf.security.PdfEncryptionKeySize;
import com.spire.pdf.security.PdfPermissionsFlags;import java.util.EnumSet;/*** @author 王恒杰* @date 2022/10/18 9:26* @Description:   将PDF加密的工具类*/
public  class EncryptPDFUtil {public static void encryptPDF(String startFileName,String EncryptPDF) {//创建PdfDocument实例PdfDocument doc = new PdfDocument();//加载PDF文件doc.loadFromFile(startFileName);//添加一个空白页,目的为了删除jar包添加的水印,后面再移除这一页doc.getPages().add();//加密PDF文件PdfEncryptionKeySize keySize = PdfEncryptionKeySize.Key_128_Bit;String openPassword = "123456";//打开文档时,仅用于查看文档String permissionPassword = "test";//打开文档时,可编辑文档EnumSet flags = EnumSet.of(PdfPermissionsFlags.Print, PdfPermissionsFlags.Fill_Fields);doc.getSecurity().encrypt(openPassword, permissionPassword, flags, keySize);//移除第一个页doc.getPages().remove(doc.getPages().get(doc.getPages().getCount()-1));//保存文件doc.saveToFile("src/main/resources/pdf/EncryptPDF/"+EncryptPDF+".pdf");doc.close();}
}

4、签字前端(thymeleaf实现)

  • 配置文件
server.port=8081
spring.thymeleaf.prefix=classpath:/templates/
  • 前端源码
<!DOCTYPE html>
<html><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><!--<meta name="viewport" content="initial-scale=1,width=device-width, height=device-height,user-scalable=no,maximum-scale=1, minimum-scale=1" /><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="format-detection" content="telephone=no">--><!--<script src="http://www.jq22.com/jquery/2.1.1/jquery.min.js"></script>--><title>手写签名</title><script type="text/javascript" src="../jquery-3.1.1.min.js" ></script><script type="text/javascript" src="../ajaxfileupload.js" ></script><style type="text/css">#canvas {}</style>
</head><body id="bb"><div id="canvasDiv"></div><br><br><br><button id="btn_clear">清除</button><button id="btn_submit">提交</button><script language="javascript">try{function onDocumentTouchStart(event) {if(event.touches.length == 1) {event.preventDefault();// Faking double click for touch devicesvar now = new Date().getTime();if(now - timeOfLastTouch < 250) {reset();return;}timeOfLastTouch = now;mouseX = event.touches[0].pageX;mouseY = event.touches[0].pageY;isMouseDown = true;}}function onDocumentTouchMove(event) {if(event.touches.length == 1) {event.preventDefault();mouseX = event.touches[0].pageX;mouseY = event.touches[0].pageY;}}function onDocumentTouchEnd(event) {if(event.touches.length == 0) {event.preventDefault();isMouseDown = false;}}var canvasDiv = document.getElementById('canvasDiv');var canvas = document.createElement('canvas');var canvasWidth = 1191;var canvasHeight = 670;document.addEventListener('touchmove', onDocumentTouchMove, false);/*document.addEventListener('touchstart', onDocumentTouchStart, true);document.addEventListener('touchend', onDocumentTouchEnd, true);*/var point = {};point.notFirst = false;canvas.setAttribute('width', canvasWidth);canvas.setAttribute('height', canvasHeight);canvas.setAttribute('id', 'canvas');canvasDiv.appendChild(canvas);if(typeof G_vmlCanvasManager != 'undefined') {canvas = G_vmlCanvasManager.initElement(canvas);}var context = canvas.getContext("2d");var img = new Image();img.src = "./write.jpg";img.onload = function() {var ptrn = context.createPattern(img, 'no-repeat');context.fillStyle = ptrn;context.fillRect(0, 0, canvas.width, canvas.height);}canvas.addEventListener("touchstart", function(e) {console.log("touchstart");var mouseX = e.touches[0].pageX - this.offsetLeft;var mouseY = e.touches[0].pageY - this.offsetTop;paint = true;addClick(e.touches[0].pageX - this.offsetLeft, e.touches[0].pageY - this.offsetTop);redraw();});canvas.addEventListener("touchend", function(e) {console.log("touchend");paint = false;});canvas.addEventListener("touchmove", function(e) {console.log("touchmove");console.log(e);console.log("触摸坐标:"+(e.touches[0].clientX - this.offsetLeft)+","+(e.touches[0].clientY - this.offsetTop));if(paint) {addClick(e.touches[0].pageX - this.offsetLeft, e.touches[0].pageY - this.offsetTop, true);redraw();}});canvas.addEventListener("mousedown", function(e) {console.log("mousedown");var mouseX = e.pageX - this.offsetLeft;var mouseY = e.pageY - this.offsetTop;paint = true;addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);redraw();});canvas.addEventListener("mousemove", function(e) {console.log(e);/*console.log(this.offsetLeft);console.log(this.offsetTop);*/console.log("鼠标坐标:"+(e.pageX - this.offsetLeft)+","+(e.pageY - this.offsetTop));console.log("mousemove");if(paint) {addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);redraw();}});canvas.addEventListener("mouseup", function(e) {console.log("mouseup");paint = false;});canvas.addEventListener("mouseleave", function(e) {console.log("mouseleave");paint = false;});var clickX = new Array();var clickY = new Array();var clickDrag = new Array();var paint;function addClick(x, y, dragging){clickX.push(x);clickY.push(y);clickDrag.push(dragging);console.debug(clickDrag);}function redraw() {//canvas.width = canvas.width; // Clears the canvascontext.strokeStyle = "black";context.lineJoin = "round";context.lineWidth = 5;while(clickX.length > 0) {point.bx = point.x;point.by = point.y;point.x = clickX.pop();point.y = clickY.pop();console.log(point.x);console.log(point.y);/*alert(point.x);alert(point.y);*/point.drag = clickDrag.pop();context.beginPath();if(point.drag && point.notFirst) {context.moveTo(point.bx, point.by);} else {point.notFirst = true;context.moveTo(point.x - 1, point.y);}context.lineTo(point.x, point.y);context.closePath();context.stroke();}for(var i=0; i < clickX.length; i++){    context.beginPath();if(clickDrag[i] && i){context.moveTo(clickX[i-1], clickY[i-1]);}else{context.moveTo(clickX[i]-1, clickY[i]);}context.lineTo(clickX[i], clickY[i]);context.closePath();context.stroke();}}var clear = document.getElementById("btn_clear");var submit = document.getElementById("btn_submit");var i =0;clear.addEventListener("click", function() {canvas.width = canvas.width;});submit.addEventListener("click", function() {$("#file").attr("src", canvas.toDataURL("image/png"));/*imgData = canvas.toDataURL("image/png").replace("image/png",'image/octet-stream');var saveFile = function(data, filename) {var save_link = document.createElementNS('a');save_link.href = data;save_link.download = filename;var event = document.createEvent('MouseEvents');event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);save_link.dispatchEvent(event);}i=i+1;var filename = "测试签名" +i+ '.' + "png";saveFile(imgData,filename);*/$.ajax({url: '/sign/uploadSign',type: 'POST',data: {img:canvas.toDataURL("image/png")},success: function (data){if(data=="yes"){alert("上传图片成功")}},error:function(data,status,e){alert(e);}});});}
catch(err){txt="本页中存在错误。\n\n"txt+="错误描述:" + err.description + "\n\n"txt+="点击“确定”继续。\n\n"alert(txt)}</script><img id="file" /></body>
</html>

4、实现签字功能演示

  • 启动springboot项目

  • 输入http://localhost:8080/ 打开签字页面,输入姓名点击提交

  • 提交成功会显示提交成功

  • 服务器上可以看到图片已经保存好了

5、实现PDF的签字和加密

(1)PDF加密实现思路讲解(详细代码看工具类)

  • PDF加密的方法
    //加密EncryptPDFUtil.encryptPDF(outPath, "EncryptPDF");
  • 然后我们分装了两个权限,如果是需要对这个PDF文件修改一个密码,只能查看PDF一个密码
        //加密PDF文件PdfEncryptionKeySize keySize = PdfEncryptionKeySize.Key_128_Bit;String openPassword = "123456";//打开文档时,仅用于查看文档String permissionPassword = "test";//打开文档时,可编辑文档

(2)实现签字的方法的讲解

 signimg("src/main/resources/img/sign.png", srcPath, "批准人", outPath);
  • 其中第一个参数代表的是签字的图片,第二个参数源文件,第三个参数是“批准人”这个关键字的地方进行签字,第四个参数代表的是输出路径

(3)使用postman测试(因为是post接口,所有只能使用postman进行简单的测试)

(4)最终生成了两个文件

  • 一个是没有加密的signOut.pdf,他是签字后生成的文件

  • 加密后的文件EncryptPDF.pdf 这个需要输入密码才能打开文件

  • 输入只读密码 123456

  • 打开文件后,里面的所有文字不能复制

  • 使用可以修改权限打开文件 密码:test 文件是可以复制可以修改的

四、PDF实现生成水印并删除源文件

1、生成水印的工具类

package com.whj.util;import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.pdf.*;import java.io.File;
import java.io.FileOutputStream;/*** @author 王恒杰* @date 2022/10/21 14:53* @Description:*/
public class WaterMark {/*** pdf生成水印** @param srcPdfPath       插入前的文件路径* @param tarPdfPath       插入后的文件路径* @param WaterMarkContent 水印文案* @param numberOfPage     每页需要插入的条数* @throws Exception*/public static void addWaterMark(String srcPdfPath, String tarPdfPath, String WaterMarkContent, int numberOfPage) throws Exception {PdfReader reader = new PdfReader(srcPdfPath);PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(tarPdfPath));PdfGState gs = new PdfGState();System.out.println("adksjskalfklsdk");//设置字体BaseFont font = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);// 设置透明度gs.setFillOpacity(0.4f);int total = reader.getNumberOfPages() + 1;PdfContentByte content;for (int i = 1; i < total; i++) {content = stamper.getOverContent(i);content.beginText();content.setGState(gs);//水印颜色content.setColorFill(BaseColor.DARK_GRAY);//水印字体样式和大小content.setFontAndSize(font, 35);//插入水印  循环每页插入的条数for (int j = 0; j < numberOfPage; j++) {content.showTextAligned(Element.ALIGN_CENTER, WaterMarkContent, 300, 200 * (j + 1), 30);}content.endText();}stamper.close();reader.close();boolean b = deleteFile(srcPdfPath);System.out.println("PDF水印添加完成!");}}

删除源文件

   //删除没有用的文件public static boolean deleteFile(String path) {boolean result = false;File file = new File(path);if (file.isFile() && file.exists()) {int tryCount = 0;while (!result && tryCount++ < 10) {System.gc();result = file.delete();}}return result;}

2、测试

(1)加水印之前的源文件

(2)测试代码

public class TestWaterMark {public static void main(String[] args) {/*** pdf生成水印** @param srcPdfPath       插入前的文件路径* @param tarPdfPath       插入后的文件路径* @param WaterMarkContent 水印文案* @param numberOfPage     每页需要插入的条数* @throws Exception*/String srcPdfPath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\signOut.pdf";String tarPdfPath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\pdf\\TestWaterMark.pdf";String WaterMarkContent = "程序员小王";Integer numberOfPage = 3;try {WaterMark.addWaterMark(srcPdfPath, tarPdfPath, WaterMarkContent, numberOfPage);} catch (Exception e) {e.printStackTrace();}}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u39T5Cpx-1666600264552)(image/image_XDeFaw_Svq.png)]

  • 删除源文件的代码
   boolean b = deleteFile(srcPdfPath);

(3)加水印之后的文件

五、PDF使用工具Adobe Acrobat DC+PDF+Itext模板实现二维码功能

1、打开电脑中的Adobe Acrobat pro DC(这个应该win10 都有,搜索一下就出来了),点击 文件→创建→创建表单

  • 如果没有自己安装下载一个下载地址:adobe acrobat pro dc2018破解版

  • acrobat pro dc 2018序列号

    1118-1629-0753-5166-7814-8217

2、然后导入刚刚生成的pdf(注意签名后的文件不能再进行添加二维码或者文字了)

3、点击图片

4、固定好二维码固定位置然后编辑他为img()

  • 击这个阴影部分,将名称改成你要设置的名称,后面要根据这个名称来给他赋值

5、全部设置好以后就可以另存为了,打开后是这个样子

6、实现二维码生成的工具类(仅供测试,详细代码看7)

  • jar包
  <!-- 条形码、二维码生成 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.0</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.0</version></dependency>
  • 代码
package com.whj.util.QR;import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Hashtable;/*** @author 王恒杰* @date 2022/10/21 16:25* 二维码生成工具类** 通过Google开源的zxing库来事项生成二维码图片*/
public class QrCodeUtils {public static final String QR_CODE_IMAGE_PATH = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\QR\\MyQRCode.png";public static void generateQRCodeImage(String text, int width, int height, String filePath) throws WriterException, IOException {QRCodeWriter qrCodeWriter = new QRCodeWriter();BitMatrix bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, width, height);Path path = FileSystems.getDefault().getPath(filePath);MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path);}/*** 生成二维码* @param contents 二维码的内容* @param width 二维码图片宽度* @param height 二维码图片高度*/public static BufferedImage createQrCodeBufferdImage(String contents, int width, int height){Hashtable hints= new Hashtable();hints.put(EncodeHintType.CHARACTER_SET, "utf-8");BufferedImage image = null;try {BitMatrix bitMatrix = new MultiFormatWriter().encode(contents,BarcodeFormat.QR_CODE, width, height, hints);image = toBufferedImage(bitMatrix);} catch (WriterException e) {e.printStackTrace();}return image;}public static BufferedImage toBufferedImage(BitMatrix matrix) {int width = matrix.getWidth();int height = matrix.getHeight();BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);for (int x = 0; x < width; x++) {for (int y = 0; y < height; y++) {image.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);}}return image;}public static void main(String[] args) {try {generateQRCodeImage ("https://www.baidu.com/", 350, 350,QR_CODE_IMAGE_PATH);} catch (WriterException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
  • 测试二维码生成
package com.whj.util.QR;import com.google.zxing.WriterException;import java.io.IOException;import static com.whj.util.QR.QrCodeUtils.QR_CODE_IMAGE_PATH;/*** @author 王恒杰* @date 2022/10/24 15:42* @Description:*/
public class TestQR {public static void main(String[] args) {try {/*** 第一个参数:内容可以是二维码 也可以是内容* 第二第三参数:二维码的宽高* 第四个参数:二维码生成后的地址*/QrCodeUtils.generateQRCodeImage ("https://www.wolai.com/6MjBCdAq3mmGXBcb62V4DE", 350, 350,QR_CODE_IMAGE_PATH);} catch (WriterException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
  • 生成后的二维码

在这里插入图片描述

7、生成二维码之后将二维码嵌入进PDF综合版本

  • 生成二维码并且嵌入PDF工具
package com.whj;import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.*;import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** @author 王恒杰* @date 2022/10/24 16:06* @Description:*/
public class QR {// 利用模板生成pdfpublic static void pdfout(Map<String,Object> map) {// 模板路径String templatePath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\QR\\QR.pdf";// 生成的新文件路径String newPDFPath = "D:\\Idea\\stamp\\Itext\\src\\main\\resources\\QR\\testout.pdf";PdfReader reader;FileOutputStream out;ByteArrayOutputStream bos;PdfStamper stamper;try {//给表单添加中文字体 这里采用系统字体。不设置的话,中文可能无法显示BaseFont bf = BaseFont.createFont("D:\\Idea\\stamp\\Itext\\src\\main\\resources\\Font\\SIMYOU.TTF" , BaseFont.IDENTITY_H, BaseFont.EMBEDDED);Font FontChinese = new Font(bf, 5, Font.NORMAL);// 输出流out = new FileOutputStream(newPDFPath);// 读取pdf模板reader = new PdfReader(templatePath);bos = new ByteArrayOutputStream();stamper = new PdfStamper(reader, bos);AcroFields form = stamper.getAcroFields();Map<String,Object> qrcodeFields=(Map<String, Object>) map.get("qrcodeFields");//遍历二维码字段for (Map.Entry<String, Object> entry : qrcodeFields.entrySet()) {String key = entry.getKey();Object value = entry.getValue();// 获取属性的类型if(value != null && form.getField(key) != null){//获取位置(左上右下)AcroFields.FieldPosition fieldPosition = form.getFieldPositions(key).get(0);//绘制二维码float width = fieldPosition.position.getRight() - fieldPosition.position.getLeft();BarcodeQRCode pdf417 = new BarcodeQRCode(value.toString(), (int)width, (int)width, null);//生成二维码图像Image image128 = pdf417.getImage();//绘制在第一页PdfContentByte cb = stamper.getOverContent(1);//左边距(居中处理)float marginLeft = (fieldPosition.position.getRight() - fieldPosition.position.getLeft() - image128.getWidth()) / 2;//条码位置image128.setAbsolutePosition(fieldPosition.position.getLeft() + marginLeft, fieldPosition.position.getBottom());//加入条码cb.addImage(image128);}}// 如果为false,生成的PDF文件可以编辑,如果为true,生成的PDF文件不可以编辑stamper.setFormFlattening(true);stamper.close();Document doc = new Document();Font font = new Font(bf, 20);PdfCopy copy = new PdfCopy(doc, out);doc.open();PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);copy.addPage(importPage);doc.close();} catch (IOException e) {e.printStackTrace();} catch (DocumentException e) {e.printStackTrace();}}}
  • 测试
 public static void main(String[] args) {//文本内容mapMap<String,Object> map = new HashMap<String, Object>();//二维码mapMap<String,Object> qrcodeFields = new HashMap<String, Object>();qrcodeFields.put("img","https://www.wolai.com/6MjBCdAq3mmGXBcb62V4DE");//组装map传过去Map<String,Object> o=new HashMap<String, Object>();o.put("qrcodeFields",qrcodeFields);//执行pdfout(o);}


项目开源地址:https://gitee.com/wanghengjie563135/pdf.git
csdn下载地址:https://download.csdn.net/download/weixin_44385486/86813947
在这里插入图片描述


http://www.ppmy.cn/news/356736.html

相关文章

怎样在PDF文件上添加印章

1、首先将需要添加制公司的印章的文件转换成.pdf文件 2、从Adobe 官网上下载Adobe Acrobat XI Pro 这款软件并安装到我们的电脑上&#xff0c;安装完后打开软件&#xff0c;界面如下&#xff1a; 3、准备好我们要添加水印的pdf文档&#xff0c;用Adobe Acrobat XI打开&#xf…

Word盖章和PDF盖章

一、电子签章的作用 对文档进行数字签名与签署纸质文档的原因大致相同&#xff0c;数字签名通过使用计算机加密来验证 &#xff08;身份验证:验证人员和产品所声明的身份是否属实的过程。例如&#xff0c;通过验证用于签名代码的数字签名来确认软件发行商的代码来源和完整性。…

css移动端

目录 谷歌模拟器 屏幕分辨率 视口 二倍图 适配方案 rem 简介 问题 媒体查询 移动端 设备宽度不同&#xff0c;HTML标签字号设置多少合适 flexible.js rem-移动端适配 less 注释 运算 嵌套 变量 导入 导出 禁止导出 谷歌模拟器 模拟移动设备&#xff0c;方…

诚迈科技智能汽车软件产业峰会落幕,智达诚远峰昇操作系统FusionOS发布!

4月6日&#xff0c;由诚迈科技、智达诚远共同主办&#xff0c;苏州工业园区投资促进委员会协办&#xff0c;苏州工业园区智能网联产业促进会大力支持的中国&#xff08;苏州&#xff09;智能汽车软件产业峰会在苏州盛大举行。本次活动以“创新融合 聚力赋能”为主题&#xff0c…

Antv/L7中使用高德地图插件

L7可以使用高德地图作为底图&#xff0c;也可以使用高德地图提供的插件&#xff0c;如工具栏&#xff0c;缩放条等。 官方文档中的引入方式如下const sc new Scene({id: xxxx-map,logoVisible: false,map: new GaodeMap({style: default,pitch: 0,zoom: 13.056,plugin: [AMap…

高德地图——货车导航

高德地图——货车导航 插件&#xff1a;pluginAMap.TruckDriving 第一种方法&#xff1a;使用坐标&#xff0c;比驾驶和骑行多了city和size属性&#xff0c;且需要放入的是json数据 new AMap.TruckDriving({map:map,panel:panel,city:beijing,//城市size:1 //大小}).search(…

ECE8.1认证之路

ECE8.1认证之路 现在考试基本不需要发邮件说明要用中国 身份证考试和用deepl翻译软件&#xff0c;如果不放心也可以问&#xff0c;官方也会回复 1、购买考试资格(需要vxn) 点击 My Account 这个时候会让你登录&#xff0c;我用的是微软的账号登录 然后会让你填入一些基本信…

长沙与华为联合“打样”,湖南智能网联汽车加速跑出新局面

文丨智能相对论 作者丨陈选滨 智能网联汽车产业发展至今&#xff0c;在落地的过程中越来越考验一个地区的产业发展思路以及与市场企业的协同能力。 以长沙来说&#xff0c;目前其智能网联汽车产业在全国保持领跑态势。据赛迪顾问发布的《2020年中国智能网联汽车产业投资潜力…