获取首页缩略图
场景:文件列表,代码见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("解析签署文件证书信息异常");
}
}