Pdf常用工具类及签章异常常见问题

获取首页缩略图

场景:文件列表,代码见PdfUtils.java的getPdfThumb方法

缩略图示例:

PDF用户空间边界和内容边界是否一致

场景:用户空间边界和内容边界不一致,会导致签章(签名)签到页面外面,代码见PdfUtils.java的preHandlePdf方法

示例:

已加密文件解密


    /**
     * 解密
     *
     * @param content
     * @param fileName
     * @return
     */
    public static byte[] decrypt(byte[] content, String fileName) {
        long start = System.currentTimeMillis();
        try (PDDocument document = PDDocument.load(content,
                "", null, null, MemoryUsageSetting.setupTempFileOnly());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            document.setAllSecurityToBeRemoved(true);
            document.save(out);
            log.error("【PDF解密】成功{},耗时{}", fileName, System.currentTimeMillis() - start);
            return out.toByteArray();
        } catch (Exception e) {
            log.error("【PDF解密】异常{}", fileName, e);
            throw new ServerErrorException("PDF解密异常");
        }
    }

移除无效签名

 签名无效


    public static void main(String[] args) {
        // 是否需要移除签名
        if (isNeedRemoveSign(fileContent)) {
            fileContent = PdfUtils.removeSign(fileContent, fileName);
        }
    }

    private boolean isNeedRemoveSign(byte[] fileContent) {
        try {
            PdfReader reader = new PdfReader(fileContent);
            AcroFields acroFields = reader.getAcroFields();
            List<String> names = acroFields.getSignatureNames();
            // 没有签名返回需要,因为有些无效的获取不到
            if (names.isEmpty()) {
                return true;
            }
            for (String name : names) {
                PdfPKCS7 pk = acroFields.verifySignature(name);
                if (!pk.verify()) {
                    return true;
                }
            }
        } catch (Exception e) {
            log.error("PDF签名校验异常");
            return true;
        }
        return false;
    }


    /**
     * 移除所有签名(印模保留)
     * @param src
     * @return
     */
    public static byte[] removeSign(byte[] src, String fileName){
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            PdfReader reader = new PdfReader(src);
            PdfStamper stamper = new PdfStamper(reader, baos);
            stamper.setFormFlattening(true);
            stamper.close();
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("【移除无效签名异常】异常{}", fileName, e);
            throw new ServerErrorException("移除无效签名异常");
        }
    }

原始PDF需要转换为图片格式PDF

场景:有些格式异常,签章会报错,因此需要转换为图片格式PDF,代码见PdfUtils.java的convertImagePdf方法

签章会报错文件示例:

签章异常报错:cfca.com.itextpdf.text.DocumentException:No message found for append.mode.requires.a.document.without.errors.even.if.recovery.was.possible

签章报错:cfca.com.itextpdf.text.ExceptionConverter:cfca.sadk.org.bouncycastle.asn1.DLSequence cannot be cast to cfca.sadk.org.bouncycastle.asn1.ASN10bjectIdentifier

签章报错:java.lang.ClassCastException:cfca.com.itextpdf.text.pdf.PdfDictionary cannot be cast to cfca.com.itextpdf.text.pdf.PRIndirectReference

PdfUtils.java

import cn.hutool.core.util.NumberUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;

@Slf4j
public class PdfUtils {

    /**
     * 经过测试,dpi为96,100,105,120,150,200中,105显示效果较为清晰,体积稳定,dpi越高图片体积越大,一般电脑显示分辨率为96
     */
    private static final float DEFAULT_DPI = 120;
    /**
     * pdf标准宽
     */
    public static final float PDF_WIDTH = 595.3F;
    /**
     * pdf标准高
     */
    public static final float PDF_HEIGHT = 841.9F;

    /**
     * 首页缩略图
     * @param content
     * @param fileName
     * @return
     */
    public static byte[] getPdfThumb(byte[] content, String fileName) {
        //Loading an existing PDF document
        // 防止内存溢出——在进行渲染时会进行二次采样(跳过像素行/行),这样可以节省包含大量图像文件的PDF文件的空间。
        try (PDDocument document = PDDocument.load(content,
                "", null, null, MemoryUsageSetting.setupTempFileOnly());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            //Instantiating the PDFRenderer class
            PDFRenderer renderer = new PDFRenderer(document);
            //Rendering an image from the PDF document
            BufferedImage image = renderer.renderImage(0, 0.2f);
            //Writing the image to a file
            ImageIO.write(image, "JPEG", out);
            log.info("【PDF首页缩略图】pdfbox生成{}首页缩略图成功", fileName);
            return out.toByteArray();
        } catch (Exception e) {
            log.error("【PDF首页缩略图】pdfbox生成{}生成首页缩略图异常", fileName, e);
            return null;
        }
    }


    /**
     * 解密
     *
     * @param content
     * @param fileName
     * @return
     */
    public static byte[] decrypt(byte[] content, String fileName) {
        long start = System.currentTimeMillis();
        try (PDDocument document = PDDocument.load(content,
                "", null, null, MemoryUsageSetting.setupTempFileOnly());
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            document.setAllSecurityToBeRemoved(true);
            document.save(out);
            log.error("【PDF解密】成功{},耗时{}", fileName, System.currentTimeMillis() - start);
            return out.toByteArray();
        } catch (Exception e) {
            log.error("【PDF解密】异常{}", fileName, e);
            throw new ServerErrorException("PDF解密异常");
        }
    }


    /**
     * 是否需要转图片格式PDF
     * 如果用户空间边界和内容边界不一致,则需要转换成图片格式PDF
     * @param content
     * @param fileName
     * @return
     * @throws Exception
     */
    public static PreHandlePdfVo preHandlePdf(byte[] content, String fileName) {
        try (PDDocument document = PDDocument.load(content,
                "", null, null, MemoryUsageSetting.setupTempFileOnly())) {
            PDPage page = document.getPage(0);
            // 以默认用户空间单位表示的矩形,用于定义要在其上显示或打印页面的物理介质的边界。
            PDRectangle mediaBox = page.getMediaBox();
            // 返回内容的边界框
            PDRectangle bBox = page.getBBox();
            PreHandlePdfVo result = PreHandlePdfVo.builder()
                    .width(bBox.getWidth())
                    .height(bBox.getHeight())
                    .build();
            // 用户空间矩形跟内容边界不一致,则需要转换
            if (mediaBox.getWidth() != bBox.getWidth()) {
                result.setNeedConvertImagePdf(true);
            }
            if (mediaBox.getHeight() != bBox.getHeight()) {
                result.setNeedConvertImagePdf(true);
            }
            return result;
        } catch (Exception e) {
            log.error("【解析PDF异常】异常{}", fileName, e);
            throw new ServerErrorException("解析PDF异常");
        }
    }

    /**
     * 原始PDF转换为图片格式PDF
     * @param content
     * @param fileName
     * @return
     * @throws Exception
     */
    public static byte[] convertImagePdf(byte[] content, String fileName){
        long start = System.currentTimeMillis();
        try (PDDocument document = PDDocument.load(content,
                "", null, null, MemoryUsageSetting.setupTempFileOnly());
             PDDocument newDoc = new PDDocument();
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            //Instantiating the PDFRenderer class
            PDFRenderer renderer = new PDFRenderer(document);
            for (int i = 0; i < document.getNumberOfPages(); i++) {
                // 页面转图片,默认dpi是72,如果大于72,转换出来的图片会放大
                //Rendering an image from the PDF document
                BufferedImage image = renderer.renderImageWithDPI(i, DEFAULT_DPI, ImageType.RGB);
                try (ByteArrayOutputStream imageContent = new ByteArrayOutputStream()) {
                    ImageIO.write(image, "png", imageContent);
                    // 图片插入到新页面
                    PDPage page = new PDPage(new PDRectangle(PDF_WIDTH, PDF_HEIGHT));
                    float width = image.getWidth();
                    float height = image.getHeight();
                    // 超出最大值,则缩放
                    if (width > PDF_WIDTH) {
                        double scale = NumberUtil.div(width, PDF_WIDTH);
                        width = (float)NumberUtil.div(width, scale);
                        height = (float)NumberUtil.div(height, scale);
                    }
                    if (height > PDF_HEIGHT) {
                        double scale = NumberUtil.div(height, PDF_HEIGHT);
                        width = (int)NumberUtil.div(width, scale);
                        height = (int)NumberUtil.div(height, scale);
                    }
                    //Creating PDImageXObject object
                    PDImageXObject pdImage = PDImageXObject.createFromByteArray(newDoc, imageContent.toByteArray(), i + ".jpg");
                    //creating the PDPageContentStream object
                    try (PDPageContentStream contents = new PDPageContentStream(newDoc, page)) {
                        //Drawing the image in the PDF document
                        contents.drawImage(pdImage, 0, PDF_HEIGHT-height, width, height);
                        newDoc.addPage(page);
                    }
                }
            }
            newDoc.save(out);
            log.error("【转图片格式PDF】成功{},耗时{}", fileName, System.currentTimeMillis()-start);
            return out.toByteArray();
        } catch (Exception e) {
            log.error("【转图片格式PDF】异常{}", fileName, e);
            throw new ServerErrorException("文件转图片格式PDF异常");
        }
    }

}

解析PDF中签名证书信息


    /**
     * 解析签署文件证书信息
     *
     * @param pdfIn
     * @return
     */
    public static ArrayList<PdfCertInfo> parseCertInfo(byte[] pdfIn) {
        ArrayList<PdfCertInfo> list = new ArrayList<>();
        try {
            //pdf reader 读取流内容
            PdfReader reader = new PdfReader(pdfIn);
            // 获取Acro字段
            AcroFields fields = reader.getAcroFields();
            // 获取Acro字段里的签名信息
            ArrayList<String> names = fields.getSignatureNames();
            for (String name : names) {
                // 获取签名的pkcs7数据,指定提供者
                PdfPKCS7 pkcs7 = fields.verifySignature(name, "BC");
                X509Certificate certificate = pkcs7.getSigningCertificate();
                String[] subjectDNName = certificate.getSubjectDN().getName().split("@");
                if (subjectDNName.length < 3) {
                    continue;
                }
                String issuerDNName = certificate.getIssuerDN().getName();
                PdfCertInfo certInfo = PdfCertInfo.builder()
                        .signTime(CommonUtils.dateToLocalDateTime(pkcs7.getSignDate().getTime()))
                        .startTime(CommonUtils.dateToLocalDateTime(certificate.getNotBefore()))
                        .endTime(CommonUtils.dateToLocalDateTime(certificate.getNotAfter()))
                        .certName(subjectDNName[1])
                        .custIdNumber(subjectDNName[2].substring(1))
                        // 序列号要转16进制
                        .serialNo(certificate.getSerialNumber().toString(16))
                        .format(certificate.getPublicKey().getFormat())
                        .issuer(issuerDNName.substring(issuerDNName.indexOf("CN=") + 3))
                        .build();
                list.add(certInfo);
            }
            return list;
        } catch (Exception e) {
            log.error("解析签署文件证书信息异常", e);
            throw new ServerErrorException("解析签署文件证书信息异常");
        }
    }

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值