1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

java电子签章实现

项目源码路径:https://github.com/Syske/learning-dome-code.git

前言

最近应客户需求,需要实现电子签章功能,公章部分用的时金格科技的接口,个人人签字需要自己实现,公章部分我们就不说了,商业接口做的都比较成熟,也有示例代码,所以今天着重说的就是个人签字部分。

参照公章部分的实现方式,同时也参考了很多博客1,查了很多资料,也搞清楚了电子签章的基本流程:

因为我要实现的功能很简单就是个人签章,而且我的签名是手写的,所以创建签名部分就省略了,核心部分就是确定签名坐标和签名,确定坐标部分我根据自己查找的资料,实现了根据关键字确定坐标,因为确定坐标很麻烦,也不够灵活。

对于创建签名我有一个思路,可以将创建签名作为一个远程服务部署,然后远程调用,然后检验,返回签名;当然你也可以通过这种方式生成公章,但是因为没有经过公正机构认证,这种方式生成的公章其实是不具法律效应的,好了下面直接上代码吧。

创建签名密钥

这个密钥在pdf签名的时候会校验

package io.github.syske.common.util;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.security.cert.Certificate;
/**
 * @program: dpf-sign-test
 * @description: 签名工具类
 * @author: syske
 * @create: 2019-12-04 09:02
 */

public class Pkcs {

    private static KeyPair getKey() throws NoSuchAlgorithmException {
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",
                new BouncyCastleProvider());
        generator.initialize(1024);
        // 证书中的密钥 公钥和私钥
        KeyPair keyPair = generator.generateKeyPair();
        return keyPair;
    }

    /**
     * @param password
     *            密码
     * @param issuerStr 颁发机构信息
     *
     * @param subjectStr 使用者信息
     *
     * @param certificateCRL 颁发地址
     *
     * @return
     */
    public static Map<String, byte[]> createCert(String password,
                                                 String issuerStr, String subjectStr, String certificateCRL) {
        Map<String, byte[]> result = new HashMap<String, byte[]>();
        ByteArrayOutputStream out = null;
        try {
            // 生成JKS证书
            // KeyStore keyStore = KeyStore.getInstance("JKS");
            // 标志生成PKCS12证书
            KeyStore keyStore = KeyStore.getInstance("PKCS12",
                    new BouncyCastleProvider());
            keyStore.load(null, null);
            KeyPair keyPair = getKey();
            // issuer与 subject相同的证书就是CA证书
            Certificate cert = generateCertificateV3(issuerStr, subjectStr,
                    keyPair, result, certificateCRL, null);
            // cretkey随便写,标识别名
            keyStore.setKeyEntry("cretkey", keyPair.getPrivate(),
                    password.toCharArray(), new Certificate[] { cert });
            out = new ByteArrayOutputStream();
            cert.verify(keyPair.getPublic());
            keyStore.store(out, password.toCharArray());
            byte[] keyStoreData = out.toByteArray();
            result.put("keyStoreData", keyStoreData);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
        return result;
    }

    /**
     * @param issuerStr
     * @param subjectStr
     * @param keyPair
     * @param result
     * @param certificateCRL
     * @param extensions
     * @return
     */
    public static Certificate generateCertificateV3(String issuerStr,
                                                    String subjectStr, KeyPair keyPair, Map<String, byte[]> result,
                                                    String certificateCRL, List<Extension> extensions) {
        ByteArrayInputStream bout = null;
        X509Certificate cert = null;
        try {
            PublicKey publicKey = keyPair.getPublic();
            PrivateKey privateKey = keyPair.getPrivate();
            Date notBefore = new Date();
            Calendar rightNow = Calendar.getInstance();
            rightNow.setTime(notBefore);
            // 日期加1年
            rightNow.add(Calendar.YEAR, 1);
            Date notAfter = rightNow.getTime();
            // 证书序列号
            BigInteger serial = BigInteger.probablePrime(256, new Random());
            X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                    new X500Name(issuerStr), serial, notBefore, notAfter,
                    new X500Name(subjectStr), publicKey);
            JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder(
                    "SHA1withRSA");
            SecureRandom secureRandom = new SecureRandom();
            jBuilder.setSecureRandom(secureRandom);
            ContentSigner singer = jBuilder.setProvider(
                    new BouncyCastleProvider()).build(privateKey);
            // 分发点
            ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier(
                    "2.5.29.31");
            GeneralName generalName = new GeneralName(
                    GeneralName.uniformResourceIdentifier, certificateCRL);
            GeneralNames seneralNames = new GeneralNames(generalName);
            DistributionPointName distributionPoint = new DistributionPointName(
                    seneralNames);
            DistributionPoint[] points = new DistributionPoint[1];
            points[0] = new DistributionPoint(distributionPoint, null, null);
            CRLDistPoint cRLDistPoint = new CRLDistPoint(points);
            builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);
            // 用途
            ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier(
                    "2.5.29.15");
            // | KeyUsage.nonRepudiation | KeyUsage.keyCertSign
            builder.addExtension(keyUsage, true, new KeyUsage(
                    KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
            // 基本限制 X509Extension.java
            ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier(
                    "2.5.29.19");
            builder.addExtension(basicConstraints, true, new BasicConstraints(
                    true));
            // privKey:使用自己的私钥进行签名,CA证书
            if (extensions != null)
                for (Extension ext : extensions) {
                    builder.addExtension(
                            new ASN1ObjectIdentifier(ext.getOid()),
                            ext.isCritical(),
                            ASN1Primitive.fromByteArray(ext.getValue()));
                }
            X509CertificateHolder holder = builder.build(singer);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            bout = new ByteArrayInputStream(holder.toASN1Structure()
                    .getEncoded());
            cert = (X509Certificate) cf.generateCertificate(bout);
            byte[] certBuf = holder.getEncoded();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            // 证书数据
            result.put("certificateData", certBuf);
            //公钥
            result.put("publicKey", publicKey.getEncoded());
            //私钥
            result.put("privateKey", privateKey.getEncoded());
            //证书有效开始时间
            result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
            //证书有效结束时间
            result.put("notAfter", format.format(notAfter).getBytes("utf-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bout != null) {
                try {
                    bout.close();
                } catch (IOException e) {
                }
            }
        }
        return cert;
    }

    public static void main(String[] args) throws Exception{
        // CN: 名字与姓氏    OU : 组织单位名称
        // O :组织名称  L : 城市或区域名称  E : 电子邮件
        // ST: 州或省份名称  C: 单位的两字母国家代码
        String issuerStr = "CN=电子签名,OU=github,O=github,C=CN,L=西安,ST=陕西";
        String subjectStr = "CN=电子签名,OU=github,O=github,C=CN,L=西安,ST=陕西";
        String certificateCRL  = "https://syske.github.io/";
        Map<String, byte[]> result = createCert("123456", issuerStr, subjectStr, certificateCRL);

        FileOutputStream outPutStream = new FileOutputStream("D:/keystore.p12"); // ca.jks
        outPutStream.write(result.get("keyStoreData"));
        outPutStream.close();
        FileOutputStream fos = new FileOutputStream(new File("D:/keystore.cer"));
        fos.write(result.get("certificateData"));
        fos.flush();
        fos.close();
    }
}
package io.github.syske.common.util;

/**
 * @program: dpf-sign-test
 * @description:
 * @author: syske
 * @create: 2019-12-04 09:00
 */
public class Extension {
    private String oid;

    private boolean critical;

    private byte[] value;

    public String getOid() {
        return oid;
    }

    public byte[] getValue() {
        return value;
    }
    public boolean isCritical() {
        return critical;
    }
}

生成签名

生成的签名是png图片

package io.github.syske.common.util;

/**
 * @program: dpf-sign-test
 * @description:
 * @author: syske
 * @create: 2019-12-03 22:50
 */
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import sun.font.FontDesignMetrics;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class SignImage {

    /**
     * @param doctorName   String 医生名字
     * @param hospitalName String 医生名称
     * @param date         String 签名日期
     *                     图片高度
     * @param jpgname      String jpg图片名
     * @return
     */
    public static boolean createSignTextImg(
            String doctorName, //
            String hospitalName, //
            String date,
            String jpgname) {
        int width = 255;
        int height = 100;
        FileOutputStream out = null;
        //背景色
        Color bgcolor = Color.WHITE;
        //字色
        Color fontcolor = Color.RED;
        Font doctorNameFont = new Font(null, Font.BOLD, 20);
        Font othorTextFont = new Font(null, Font.BOLD, 18);
        try { // 宽度 高度
            BufferedImage bimage = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g = bimage.createGraphics();
            g.setColor(bgcolor); // 背景色
            g.fillRect(0, 0, width, height); // 画一个矩形
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON); // 去除锯齿(当设置的字体过大的时候,会出现锯齿)

            g.setColor(Color.RED);
            g.fillRect(0, 0, 8, height);
            g.fillRect(0, 0, width, 8);
            g.fillRect(0, height - 8, width, height);
            g.fillRect(width - 8, 0, width, height);

            g.setColor(fontcolor); // 字的颜色
            g.setFont(doctorNameFont); // 字体字形字号
            FontMetrics fm = FontDesignMetrics.getMetrics(doctorNameFont);
            int font1_Hight = fm.getHeight();
            int strWidth = fm.stringWidth(doctorName);
            int y = 35;
            int x = (width - strWidth) / 2;
            g.drawString(doctorName, x, y); // 在指定坐标除添加文字

            g.setFont(othorTextFont); // 字体字形字号

            fm = FontDesignMetrics.getMetrics(othorTextFont);
            int font2_Hight = fm.getHeight();
            strWidth = fm.stringWidth(hospitalName);
            x = (width - strWidth) / 2;
            g.drawString(hospitalName, x, y + font1_Hight); // 在指定坐标除添加文字

            strWidth = fm.stringWidth(date);
            x = (width - strWidth) / 2;
            g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐标除添加文字

            g.dispose();
            out = new FileOutputStream(jpgname); // 指定输出文件
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bimage);
            param.setQuality(50f, true);
            encoder.encode(bimage, param); // 存盘
            out.flush();
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
    }

    public static void main(String[] args) {
        createSignTextImg("华佗", "在线医院", "2018.01.01", "sign.jpg");
    }
}

签名实现

这里需要注意的是,签名入参中的页码,如果是最后一页,页码传null

package io.github.syske.common.util;

import com.itextpdf.awt.geom.Rectangle2D;
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.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.security.*;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;

import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.UUID;

/**
 * @program: SignPdf
 * @description:
 * @author: syske
 * @create: 2019-12-03 22:54
 */

public class SignPdf {
    private static Logger logger = Logger.getLogger(SignPdf.class);
    private static final String PASSWORD = "123456"; // 秘钥密码
    private static final String KEY_STORE_PATH = "d:\\keystore.p12"; // 秘钥文件路径

    private SignPdf() {
    }

    /**
     * 图片签章,指定签名坐标位置
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfOutFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param x
     *            以左下角为原点x坐标值
     * @param y
     *            以左下角为原点Y坐标值
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     * @throws Exception
     */
    public static void sign(String signPdfSrc, String signedPdfOutFile,
                            String signImage, Float x, Float y, Integer numberOfPages,
                            PageStyle pageStyle) throws Exception {
        sign(signPdfSrc, signedPdfOutFile, signImage, x, y, null,
                numberOfPages, pageStyle);
    }

    /**
     * 图片签章,指定关键字
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param keyWords
     *            关键字
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     */
    public static void sign(String signPdfSrc, String signedPdfFile,
                            String signImage, String keyWords, Integer numberOfPages,
                            PageStyle pageStyle) throws Exception {
        sign(signPdfSrc, signedPdfFile, signImage, null, null, keyWords,
                numberOfPages, pageStyle);
    }

    /**
     * 私人签章
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfOutFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param x
     *            以左下角为原点x坐标
     * @param y
     *            以左下角为原点y坐标
     * @param keyWords
     *            关键字
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     * @return
     */
    public static void sign(String signPdfSrc, String signedPdfOutFile,
                            String signImage, Float x, Float y, String keyWords,
                            Integer numberOfPages, PageStyle pageStyle) throws Exception {
        File signPdfSrcFile = new File(signPdfSrc);
        PdfReader reader = null;
        ByteArrayOutputStream signPDFData = null;
        PdfStamper stp = null;
        FileInputStream fos = null;
        FileOutputStream pdfOutputStream = null;
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();
            Security.addProvider(provider);
            KeyStore ks = KeyStore.getInstance("PKCS12",
                    new BouncyCastleProvider());
            fos = new FileInputStream(KEY_STORE_PATH);
            // 私钥密码 为Pkcs生成证书是的私钥密码 123456
            ks.load(fos, PASSWORD.toCharArray());
            String alias = ks.aliases().nextElement();
            PrivateKey key = (PrivateKey) ks.getKey(alias,
                    PASSWORD.toCharArray());
            Certificate[] chain = ks.getCertificateChain(alias);
            reader = new PdfReader(signPdfSrc); // 也可以输入流的方式构建
            signPDFData = new ByteArrayOutputStream();
            numberOfPages = numberOfPages == null ? reader.getNumberOfPages()
                    : 0;

            // 临时pdf文件
            File temp = new File(signPdfSrcFile.getParent(),
                    System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp,
                    true);
            stp.setFullCompression();
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("数字签名,不可改变");
            // 使用png格式透明图片
            Image image = Image.getInstance(signImage);

            sap.setImageScale(0);
            sap.setSignatureGraphic(image);
            sap.setRenderingMode(RenderingMode.GRAPHIC);
            float llx = 0f;
            float lly = 0f;

            float signImageWidth = image.getWidth();
            float signImageHeight = image.getHeight();

            float signImageHeightSocale = 85 / signImageWidth * signImageHeight;
            if (keyWords != null && !keyWords.isEmpty()) {
                KeyWordInfo keyWordInfo = getKeyWordLocation(numberOfPages,
                        keyWords, reader);
                Rectangle pageSize = reader.getPageSize(numberOfPages);
                float width = pageSize.getWidth();
                if (PageStyle.PAGE_STYLE_LANDSCAPE.equals(pageStyle)) {
                    llx = keyWordInfo.getY() + (float) keyWordInfo.getHeight();
                    lly = width - keyWordInfo.getX() - signImageHeightSocale
                            / 2;
                } else if (PageStyle.PAGE_STYLE_PORTRAIT.equals(pageStyle)) {
                    llx = keyWordInfo.getX() + (float) keyWordInfo.getWidth()*keyWords.length();
                    lly = keyWordInfo.getY() - signImageHeightSocale / 2;
                }

            } else if (x != null && y != null) {
                llx = x;
                lly = y;
            } else {
                throw new Exception("坐标和关键字不能同时为空!");
            }

            float urx = llx + 85;
            float ury = lly + signImageHeightSocale;
            // 是对应x轴和y轴坐标
            sap.setVisibleSignature(new Rectangle(llx, lly, urx, ury),
                    numberOfPages,
                    UUID.randomUUID().toString().replaceAll("-", ""));
            stp.getWriter().setCompressionLevel(5);
            ExternalDigest digest = new BouncyCastleDigest();
            ExternalSignature signature = new PrivateKeySignature(key,
                    DigestAlgorithms.SHA512, provider.getName());
            MakeSignature.signDetached(sap, digest, signature, chain, null,
                    null, null, 0, CryptoStandard.CADES);
            stp.close();
            reader.close();
            pdfOutputStream = new FileOutputStream(signedPdfOutFile);
            pdfOutputStream.write(signPDFData.toByteArray());
            pdfOutputStream.close();
        } catch (KeyStoreException e) {
            logger.error("签名验证失败", e);
            throw new Exception("签名验证失败", e);
        } catch (FileNotFoundException e) {
            logger.error("文件未找到", e);
            throw new Exception("文件未找到", e);
        } catch (IOException e) {
            logger.error("IO异常", e);
            throw new Exception("IO异常", e);
        } catch (Exception e) {
            logger.error("签章失败", e);
            throw new Exception("签章失败", e);
        } finally {
            if (signPDFData != null) {
                try {
                    signPDFData.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }

            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }
            if (pdfOutputStream != null) {
                try {
                    pdfOutputStream.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }

        }
    }

    /**
     * 查找关键字定位
     *
     * @param numberOfPages
     * @param keyWords
     *            关键字
     * @param reader
     * @return
     * @throws IOException
     */
    private static KeyWordInfo getKeyWordLocation(Integer numberOfPages,
                                                  final String keyWords, PdfReader reader) throws IOException {
        PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(
                reader);

        final KeyWordInfo keyWordInfo = new KeyWordInfo();

        pdfReaderContentParser.processContent(numberOfPages,
                new RenderListener() {
                    @Override
                    public void renderText(TextRenderInfo textRenderInfo) {
                        String text = textRenderInfo.getText(); // 整页内容

                        if (null != text && text.contains(keyWords)) {
                            Rectangle2D.Float boundingRectange = textRenderInfo
                                    .getBaseline().getBoundingRectange();
                            float leftY = (float) boundingRectange.getMinY() - 1;
                            float rightY = (float) boundingRectange.getMaxY() + 1;

                            logger.debug(boundingRectange.x + "--"
                                    + boundingRectange.y + "---");

                            keyWordInfo.setHeight(rightY - leftY);
                            keyWordInfo.setWidth((rightY - leftY)
                                    * keyWords.length());
                            keyWordInfo.setX(boundingRectange.x);
                            keyWordInfo.setY(boundingRectange.y);
                        }

                    }

                    @Override
                    public void renderImage(ImageRenderInfo arg0) {}

                    @Override
                    public void endTextBlock() {}

                    @Override
                    public void beginTextBlock() {}
                });
        return keyWordInfo;
    }

    private static class KeyWordInfo {
        private float x;
        private float y;
        private double width;
        private double height;

        public float getX() {
            return x;
        }

        public void setX(float x) {
            this.x = x;
        }

        public float getY() {
            return y;
        }

        public void setY(float y) {
            this.y = y;
        }

        public double getWidth() {
            return width;
        }

        public void setWidth(double width) {
            this.width = width;
        }

        public double getHeight() {
            return height;
        }

        public void setHeight(double height) {
            this.height = height;
        }
    }

    enum PageStyle {
        PAGE_STYLE_LANDSCAPE, // 横向
        PAGE_STYLE_PORTRAIT // 纵向
    }

    public static PageStyle getPageStyle_LANDSCAPE() {
        return PageStyle.PAGE_STYLE_LANDSCAPE;
    }

    public static PageStyle getPageStyle_PORTRAIT() {
        return PageStyle.PAGE_STYLE_PORTRAIT;
    }

    public static void main(String[] args) throws Exception {
        sign("E:\\test2_sgin.pdf",//
                "D:\\signed-35.pdf",
                "E:\\workSpeace\\pansky\\other_files\\电子签章\\png\\sign_0000_1_yxz.png",
                null, null, "负责人", null, PageStyle.PAGE_STYLE_LANDSCAPE);
        // read();

        // test();
    }



}

项目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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.github.syske</groupId>
    <artifactId>dpf-sign-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.64</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>


    </dependencies>


</project>

签名预览:

总结

因为本身业务不复杂,所以也没有过多的说明,但以上代码实现部分还有很多需要改进的地方,比如:

1、查找关键字的时候,只获取了一次坐标,但是同一页关键字可能存在多个,如果想在相同关键字的地方都盖章,是不能实现的,如果你正好有这样的需求,你可以动手自己改造;

2、和第一个类似,没有实现全文档签名

项目源码路径:https://github.com/Syske/learning-dome-code.git
posted @ 2020-01-04 09:39  云中志  阅读(16878)  评论(2编辑  收藏  举报