# 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案 **Repository Path**: dingjunjun_codeSpace/pdf ## Basic Information - **Project Name**: 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案 - **Description**: 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 129 - **Created**: 2025-03-26 - **Last Updated**: 2025-03-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 历经一个月总结使用java实现pdf文件的电子签字+盖章+防伪二维码+水印+PDF文件加密的全套解决方案 # 一、前言 九月中旬到十月底,我和同事参加了某个系统的开发,涉及到对PDF的电子签字+盖章+防伪二维码+水印等,我最开始选择使用pageoffice实现PDF的盖章和签字,并且也写了一篇博客来进行详细的介绍(pageoffice实现签名盖章:[http://t.csdn.cn/nNxpe](http://t.csdn.cn/nNxpe "http://t.csdn.cn/nNxpe")),但是出现一个问题,PageOffice支持JAVA、[ASP.NET](http://ASP.NET "ASP.NET")、PHP多种编程开发语言,使开发集成简单高效,事半功倍。让集成PAGEOFFICE的协同办公系统更具价值,但是他是卓正软件公司的一个项目,需要收费,并且安全系数不一定能得到保证,而这个项目需要上生产,所以经过多方的研究,学习,总结,今天终于将一套PDF集成线上签字+盖章+防伪二维码+水印的一系列解决方法总结出来,今天写这篇博客进行开源 # 二、使用itextPDF实现PDF电子公章工具类 ### 1、电子公章的制作 我们需要实现电子公章盖章,但是不能使用公司的章,我们这次推荐使用的线上做章工具来模拟电子印章 * 做章网站:[http://seal.biaozhiku.com/](http://seal.biaozhiku.com/ "http://seal.biaozhiku.com/") * 我们选择圆形印章 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_1ySdjiVqeM.png) * 然后输入公司名,输入章名输入编码然后点击395生成,最后点击保存图片,我们的个人专业章就实现了 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_lr-LPzZxXi.png) * **电子公章效果如图:** ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/%E7%A8%8B%E5%BA%8F%E5%91%98%E5%B0%8F%E7%8E%8B%E5%B7%A5%E4%BD%9C%E5%AE%A4_seal.biaozhiku.com_alpha_hJzMkdI_mm.png) * PDF模板图 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/%E7%A8%8B%E5%BA%8F%E5%91%98%E5%B0%8F%E7%8E%8B_00_s58T3b8wlh.jpg) * 生成PDF效果图 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/demo3_00_255Cxa_eGz.jpg) ### 2、itextPDF的相关依赖 ```xml com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 ``` * 使用的是boot项目,所以完整依赖是 ```xml org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 ``` ### 3、相关配置及数字签名的配置 #### (1)摘要算法 * 我们项目启动之后报错 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_23PdD597Lv.png) * 需要加这个配置文件就不报错了,这个主要原因是摘要算法没有,需要引入相关依赖 ```xml org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 ``` * 涉及到加密算法就需要数字签名了(数字签名格式,CMS,CADE),我们就需要一个文件(我的命名是:server.p12)这个东西需要我们自己电脑生成数字签名 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_qzl-QVWTIi.png) ### (2)java工具keytool生成p12数字证书文件 Keytool是用于管理\*\*和证书的工具,位于%JAVA\_HOME%/bin目录。 使用JDK的keytool工具 * keytool在jdk的bin目录下 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_A5L2e9Fqso.png) 2\. 打开keytool所在的bin目录,然后在上面的路径显示框中输入CMD,然后回车,即可在当前文件夹下打开命令提示符,并且路径是当前文件夹。 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_KgdNXg9mE6.png) * 生成数字文件,在命令行输入 ```java 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 ``` ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_cynvPl3LoN.png) 参数解释: ```bash storepass keystore 文件存储密码 keypass 私钥加解密密码 alias 实体别名(包括证书私钥) dname 证书个人信息 keyalt 采用公钥算法,默认是DSA keysize **长度(DSA算法对应的默认算法是sha1withDSA,不支持2048长度,此时需指定RSA) validity 有效期 keystore 指定keystore文件 ``` ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_ZWjJHgBwtp.png) * 转换为p12格式 在命令行输入 ```java 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 ``` ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image__ik2dGnUma.png) * 生成的最终文件 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_PQAR_RUAi-.png) ### 4、项目结构及源码 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_t3aI1iEX1r.png) * 工具类(可以直接复制) ```java 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,图章右上角y appearance.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,CADE MakeSignature.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(); } } } } ``` * 实体类 ```java 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-1 private String imagePath;//图章路径 private String fieldName;//表单域名称 private Certificate[] chain;//证书链 private PrivateKey pk;//签名私钥 private int certificationLevel = 0; //批准签章 private PdfSignatureAppearance.RenderingMode renderingMode;//表现形式:仅描述,仅图片,图片和描述,签章者和描述 //图章属性 private float rectllx;//图章左下角x private float rectlly;//图章左下角y private float recturx;//图章右上角x private float rectury;//图章右上角y public 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; } } ``` * 测试 ```java 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); //需要进行签章的pdf String 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、结果展示 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_UnvRIku7C9.png) # 三、thymeleaf+itext签字功能+PDF文件加密实现 * 所以需依赖 * 需要手工导入一个jar包(jar包我放在了源码里面,需要自行下载) ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_DLEtxY_wX1.png) ```xml org.springframework.boot spring-boot-starter-parent 2.2.5.RELEASE org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.itextpdf itextpdf 5.5.10 com.itextpdf itext-asian 5.2.0 org.bouncycastle bcprov-jdk15on 1.49 org.bouncycastle bcpkix-jdk15on 1.49 com.google.guava guava 25.0-jre ``` 因为这个的相关类太多,只展示了核心代码,全部代码请到博客底部下载源码 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_twmNnIa3Ms.png) ### 1、签字图片上传和签字实现的Controller ```java 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 { @Autowired private SignService signService; @PostMapping(value = "/uploadSign") @ResponseBody public String uploadSign(String img) { return signService.uploadSign(img); } @PostMapping(value = "/sign") @ResponseBody public String sign() { return signService.sign(id); } } ``` ### 2、签字上传,签字,PDF文件加密业务层实现 ```java /** * @author 王恒杰 * @date 2022/10/24 14:00 * @Description: */ @Service public class SignServiceImpl implements SignService{ @Override public 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"; } } @Override public 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"; } } ``` * 封装签字的方法 ```java 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> map = KeywordPDFUtils.getPDFText(requestBean.getSrcPDFPath()); // 2.获取关键字坐标 List 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加密文件的方法 ```java 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实现) * 配置文件 ```.properties server.port=8081 spring.thymeleaf.prefix=classpath:/templates/ ``` * 前端源码 ```html 手写签名



``` ### 4、实现签字功能演示 * 启动springboot项目 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_Ow5A-xr0tg.png) * 输入[http://localhost:8080/](http://localhost:8080/ "http://localhost:8080/") 打开签字页面,输入姓名点击提交 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_a4El9oQzlW.png) * 提交成功会显示提交成功 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_0wGCDh86HW.png) * 服务器上可以看到图片已经保存好了 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_qL1bDU-MF9.png) ### 5、实现PDF的签字和加密 #### (1)PDF加密实现思路讲解(详细代码看工具类) * PDF加密的方法 ```java //加密 EncryptPDFUtil.encryptPDF(outPath, "EncryptPDF"); ``` * 然后我们分装了两个权限,如果是需要对这个PDF文件修改一个密码,只能查看PDF一个密码 ```java //加密PDF文件 PdfEncryptionKeySize keySize = PdfEncryptionKeySize.Key_128_Bit; String openPassword = "123456";//打开文档时,仅用于查看文档 String permissionPassword = "test";//打开文档时,可编辑文档 ``` #### (2)实现签字的方法的讲解 ```java signimg("src/main/resources/img/sign.png", srcPath, "批准人", outPath); ``` * 其中第一个参数代表的是签字的图片,第二个参数源文件,第三个参数是“批准人”这个关键字的地方进行签字,第四个参数代表的是输出路径 #### (3)使用postman测试(因为是post接口,所有只能使用postman进行简单的测试) ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_Ulgf55F_7H.png) #### (4)最终生成了两个文件 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_co23ZeHqo_.png) * 一个是没有加密的signOut.pdf,他是签字后生成的文件 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_BBIogUAKhq.png) * 加密后的文件EncryptPDF.pdf 这个需要输入密码才能打开文件 * 输入只读密码 123456 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_sdhPjH-DRo.png) * 打开文件后,里面的所有文字不能复制 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_EzXS88Odsq.png) * 使用可以修改权限打开文件 密码:test 文件是可以复制可以修改的 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_OaN7WYGeLG.png) # 四、PDF实现生成水印并删除源文件 ### 1、生成水印的工具类 ```java 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水印添加完成!"); } } ``` 删除源文件 ```java //删除没有用的文件 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)加水印之前的源文件 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_6OTD5ffc1N.png) #### (2)测试代码 ```java 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(); } } } ``` ![](image/image_XDeFaw_Svq.png) * 删除源文件的代码 ```java boolean b = deleteFile(srcPdfPath); ``` #### (3)加水印之后的文件 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_Hi0RG6gya7.png) ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_4XTCsTLyBr.png) # 五、PDF使用工具Adobe Acrobat DC+PDF+Itext模板实现二维码功能 ### 1、打开电脑中的Adobe Acrobat pro DC(这个应该win10 都有,搜索一下就出来了),点击 文件→创建→创建表单 * 如果没有自己安装下载一个**下载地址:**[adobe acrobat pro dc2018破解版](https://pan.baidu.com/s/12oAFq3rPD0S91CdEPHgZQA "adobe acrobat pro dc2018破解版") * acrobat pro dc 2018序列号 1118-1629-0753-5166-7814-8217 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_iq_TamIWmF.png) ### 2、然后导入刚刚生成的pdf(注意签名后的文件不能再进行添加二维码或者文字了) ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_gQcutbLKdS.png) ### 3、点击图片 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_XaU_hRRv_s.png) ### 4、固定好二维码固定位置然后编辑他为img() * 击这个阴影部分,将名称改成你要设置的名称,后面要根据这个名称来给他赋值 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_ggaey5_KFm.png) ### 5、全部设置好以后就可以另存为了,打开后是这个样子 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_3lE9BsMnag.png) ### 6、实现二维码生成的工具类(仅供测试,详细代码看7) ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_j6VVwkUG5Z.png) * jar包 ```xml com.google.zxing core 3.3.0 com.google.zxing javase 3.3.0 ``` * 代码 ```java 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 block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } ``` * 测试二维码生成 ```java 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 block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } ``` * 生成后的二维码 ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_DIVOxqryr7.png) ### 7、生成二维码之后将二维码嵌入进PDF综合版本 * 生成二维码并且嵌入PDF工具 ```java 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 { // 利用模板生成pdf public static void pdfout(Map 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 qrcodeFields=(Map) map.get("qrcodeFields"); //遍历二维码字段 for (Map.Entry 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(); } } } ``` * 测试 ```java public static void main(String[] args) { //文本内容map Map map = new HashMap(); //二维码map Map qrcodeFields = new HashMap(); qrcodeFields.put("img","https://www.wolai.com/6MjBCdAq3mmGXBcb62V4DE"); //组装map传过去 Map o=new HashMap(); o.put("qrcodeFields",qrcodeFields); //执行 pdfout(o); } ``` ![](https://csdn-image.oss-cn-beijing.aliyuncs.com/img/image_xRPHg-uIQE.png)