引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>8.0.5</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcutil-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>bouncy-castle-adapter</artifactId>
<version>9.1.0</version>
</dependency>
生成证书文件
# windows利用java环境执行该命令
keytool -genkey -alias RSAkey -keystore cert.p12 -storetype PKCS12 -keyalg RSA -storepass test@123 -validity 3560 -keysize 2048 -dname "CN=XXX公司, OU=XXX公司, O=XXX公司"
工具类实现
PDFSignUtil
/**
* <p>
* <B>Description: PDF数字签名工具</B>
* </P>
* Revision Trail: (Date/Author/Description)
* 2025/3/26 Ryan Huang CREATE
*
* @author Ryan Huang
* @version 1.0
*/
public class PDFSignUtil {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final Logger log = LoggerFactory.getLogger(PDFSignUtil.class);
/**
* 证书类型
*/
private static final String PKCS12 = "PKCS12";
/**
* 签章名称前缀
*/
private static final String PDF_SEAL_FILE_NAME_PRE = "SEAL";
/**
* 数字签名前缀
*/
private static final String PDF_SIGN_FILE_NAME_PRE = "SIGN";
/**
* 签名类型:数字签名
*/
private static final String SIGN_TYPE_DIGITAL = "DIGITAL_SIGN";
/**
* 签名类型:印章
*/
private static final String SIGN_TYPE_SEAL = "SEAL_SIGN";
/**
* 证书信息
*/
private static final String CERT_SUBJECT_DN = "CN=XXX公司,OU=XXX公司,O=XXX公司";
/**
* PDF加盖图章(固定位置)
*
* @param password 证书密码
* @param certPath 证书路径
* @param pdfPath PDF路径
* @param sealImagePath 图章路径
* @param x 图章X坐标,坐标轴的原点(0,0)位于该页面的左下角。X 轴就是左下角到右下角的那条直线
* @param y 图章Y坐标,坐标轴的原点(0,0)位于该页面的左下角。Y 轴就是左下角到左上角的那条直线
* @param sealImageWidth 图章宽度
* @param sealImageHeight 图章高度
* @param pageNumArray 需要加盖图章的页码数组,为空则选择全部
*
* @return 加盖图章后的PDF字节数组
*/
public static byte[] sealWithImage(String password, String certPath, String pdfPath, String sealImagePath,
float x, float y, int sealImageWidth, int sealImageHeight, int[] pageNumArray) {
try (InputStream certFis = new FileInputStream(certPath);
InputStream pdfFis = new FileInputStream(pdfPath);
InputStream stampFis = new FileInputStream(sealImagePath)) {
return sealWithImage(password, certFis, pdfFis, stampFis, x, y, sealImageWidth, sealImageHeight, pageNumArray);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* PDF加盖图章(固定位置)
*
* @param password 证书密码
* @param certFis 证书输入流
* @param pdfFis PDF输入流
* @param sealImageFis 图章输入流
* @param x 图章X坐标,坐标轴的原点(0,0)位于该页面的左下角。X 轴就是左下角到右下角的那条直线
* @param y 图章Y坐标,坐标轴的原点(0,0)位于该页面的左下角。Y 轴就是左下角到左上角的那条直线
* @param sealImageWidth 图章宽度
* @param sealImageHeight 图章高度
* @param pageNumArray 需要加盖图章的页码数组,为空则选择全部
*
* @return 加盖图章后的PDF字节数组
*/
public static byte[] sealWithImage(String password, InputStream certFis, InputStream pdfFis, InputStream sealImageFis,
float x, float y, int sealImageWidth, int sealImageHeight, int[] pageNumArray) {
byte[] pdfIsByte = inputStreamToByteArray(pdfFis);
pdfFis = new ByteArrayInputStream(pdfIsByte);
byte[] sealImageByte = inputStreamToByteArray(sealImageFis);
// 需要签章的页码数组
int[] processedPageNums = getPageNumArray(pdfIsByte, pageNumArray);
// 每页签章时的临时文件输出流
ByteArrayOutputStream temAos = null;
try (ByteArrayOutputStream resultAos = new ByteArrayOutputStream()){
// 获取PKCS12证书信息
KeyStore keyStore = KeyStore.getInstance(PKCS12, BouncyCastleProvider.PROVIDER_NAME);
keyStore.load(certFis, password.toCharArray());
String alias = keyStore.aliases().nextElement();
PrivateKey key = (PrivateKey) keyStore.getKey(keyStore.aliases().nextElement(), password.toCharArray());
Certificate[] chain = keyStore.getCertificateChain(alias);
certFis.close();
certFis = null;
// 对需要签章的页码进行签章
for (int pageNum : processedPageNums) {
temAos = processSeal(pdfFis, x, y, sealImageWidth, sealImageHeight, pageNum, sealImageByte, key, chain);
// 定义输入流为生成的输出流内容,已完成多次签章
pdfFis = new ByteArrayInputStream(temAos.toByteArray());
resultAos.reset();
resultAos.write(temAos.toByteArray());
}
return resultAos.toByteArray();
}catch (Exception e){
throw new RuntimeException("PDF签章失败!", e);
}finally {
try {
pdfFis.close();
if (temAos != null) {
temAos.close();
}
if (certFis != null) {
certFis.close();
}
} catch (IOException e) {
log.error("PDF签章资源关闭异常: ", e);
}
}
}
/**
* PDF加盖图章(文本相对位置)
*
* @param password 证书密码
* @param certPath 证书路径
* @param pdfPath 待加盖图章的PDF路径
* @param sealImagePath 图章路径
* @param targetText 目标文本
* @param offsetX X偏移量
* @param offsetY Y偏移量
* @param sealImageWidth 图章宽度
* @param sealImageHeight 图章高度
* @param pageNumArray 需要加盖图章的页码数组,为空则选择全部
*
* @return 加盖图章后的PDF字节数组
*/
public static byte[] sealWithImage(String password, String certPath, String pdfPath, String sealImagePath, String targetText,
float offsetX, float offsetY, int sealImageWidth, int sealImageHeight, int[] pageNumArray) {
try (InputStream certFis = new FileInputStream(certPath);
InputStream pdfFis = new FileInputStream(pdfPath);
InputStream stampFis = new FileInputStream(sealImagePath)) {
return sealWithImage(password, certFis, pdfFis, stampFis, targetText, offsetX, offsetY, sealImageWidth, sealImageHeight, pageNumArray);
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* PDF加盖图章(文本相对位置)
*
* @param password 证书密码
* @param certFis 证书输入流
* @param pdfFis 待加盖图章的PDF输入流
* @param sealImageFis 图章输入流
* @param targetText 目标文本
* @param offsetX X轴偏移量,中心点为匹配文本第一个字符最左下角位置
* @param offsetY Y偏移量,中心点为匹配文本第一个字符最左下角位置
* @param sealImageWidth 图章宽度
* @param sealImageHeight 图章高度
* @param pageNumArray 需要加盖图章的页码数组,为空则选择全部
*
* @return 加盖图章后的PDF字节数组
*/
public static byte[] sealWithImage(String password, InputStream certFis, InputStream pdfFis, InputStream sealImageFis, String targetText,
float offsetX, float offsetY, int sealImageWidth, int sealImageHeight, int[] pageNumArray) {
// 获取需要加盖图章的页码数组
byte[] pdfIsByte = inputStreamToByteArray(pdfFis);
byte[] sealImageByte = inputStreamToByteArray(sealImageFis);
pdfFis = new ByteArrayInputStream(pdfIsByte);
int[] processedPageNums = getPageNumArray(pdfIsByte, pageNumArray);
// 每页签章时的临时文件输出流
ByteArrayOutputStream temAos = null;
try (ByteArrayOutputStream resultAos = new ByteArrayOutputStream()) {
// 获取目标文本位置
Map<Integer, List<Rectangle>> textPositions = findTextPositions(new ByteArrayInputStream(pdfIsByte), targetText, processedPageNums);
if(textPositions.isEmpty()) return pdfIsByte;
// 获取PKCS12证书信息
KeyStore keyStore = KeyStore.getInstance(PKCS12, BouncyCastleProvider.PROVIDER_NAME);
keyStore.load(certFis, password.toCharArray());
String alias = keyStore.aliases().nextElement();
PrivateKey key = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
Certificate[] chain = keyStore.getCertificateChain(alias);
certFis.close();
certFis = null;
// 遍历找到的文本位置
for (Map.Entry<Integer, List<Rectangle>> entry : textPositions.entrySet()) {
int pageNum = entry.getKey();
for (Rectangle textRect : entry.getValue()) {
// 计算签章位置
float x = textRect.getX() + offsetX;
float y = textRect.getY() + offsetY;
// 处理签章
temAos = processSeal(pdfFis, x, y, sealImageWidth, sealImageHeight, pageNum, sealImageByte, key, chain);
pdfFis = new ByteArrayInputStream(temAos.toByteArray());
resultAos.reset();
resultAos.write(temAos.toByteArray());
}
}
return resultAos.toByteArray();
} catch (Exception e) {
throw new ServiceException("PDF文字相对位置签章失败: " + e.getMessage());
}finally {
try {
if (certFis != null) {
certFis.close();
}
pdfFis.close();
} catch (IOException e) {
log.error("PDF签章资源关闭异常: ", e);
}
}
}
/**
* pdf数字签名
*
* @param password 证书密码
* @param keyStorePath 证书路径
* @param pdfPath PDF路径
*
* @return 数字签名后的PDF字节数组
*/
public static byte[] digitalSign(String password, String keyStorePath, String pdfPath) {
try(FileInputStream fileInputStream = new FileInputStream(pdfPath);
FileInputStream certFis = new FileInputStream(keyStorePath)) {
return digitalSign(password, certFis, fileInputStream);
}catch (IOException e){
throw new ServiceException("PDF文件异常: " + e.getMessage());
}
}
/**
* pdf数字签名
*
* @param password 证书密码
* @param certFis 证书路径
* @param pdfFis 数字签名PDF输入流
*
* @return 数字签名后的PDF字节数组
*/
public static byte[] digitalSign(String password, InputStream certFis, InputStream pdfFis) {
// 获取PDF页码后输入流会被标记到结尾,所以需通过字节重新创建输入流
byte[] pdfIsByte = inputStreamToByteArray(pdfFis);
int[] pageNumArray = getDigitalPageNumArray(new ByteArrayInputStream(pdfIsByte));
if(pageNumArray.length == 0) return pdfIsByte;
pdfFis = new ByteArrayInputStream(pdfIsByte);
// 结果文件输出流
ByteArrayOutputStream resultAos = new ByteArrayOutputStream();
// 每页签章时的临时文件输出流
ByteArrayOutputStream temAos = null;
try {
// 获取PKCS12证书信息
KeyStore keyStore = KeyStore.getInstance(PKCS12, BouncyCastleProvider.PROVIDER_NAME);
keyStore.load(certFis, password.toCharArray());
String alias = keyStore.aliases().nextElement();
PrivateKey key = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
Certificate[] chain = keyStore.getCertificateChain(alias);
certFis.close();
certFis = null;
// 对需要签章的页码进行签章
for (int pageNum : pageNumArray) {
temAos = processDigitalSign(pdfFis, pageNum, key, chain);
// 定义输入流为生成的输出流内容,已完成多次签章
pdfFis = new ByteArrayInputStream(temAos.toByteArray());
resultAos = temAos;
}
return resultAos.toByteArray();
}catch (Exception e){
throw new RuntimeException("PDF数字签名失败!", e);
}finally {
try {
if (temAos != null) {
temAos.close();
}
if (certFis != null) {
certFis.close();
}
resultAos.close();
pdfFis.close();
} catch (IOException e) {
log.error("PDF数字签名资源关闭异常: ", e);
}
}
}
/**
* 验证PDF数字签名
* 签发规则:
* 1. 每个页面需有一个数字签名
* 2. 数字签名名称必须以 SIGN 开头, 且签章名称需要需与页码一致,如签章名称为 SIGN1, 签章页码为1
* 3. 数字签名的证书信息必须一致
* 4. 签章原因字段 Reason -> 解密后的字符串并序列化后为Map {"curPage": 签章页码, "signType": "DIGITAL_SIGN"}
*
* @param pdfPath PDF文件路径
*/
public static boolean verifyDigitalSignature(String pdfPath) {
try(FileInputStream pdfFis = new FileInputStream(pdfPath)){
return verifyDigitalSignature(pdfFis);
}catch (IOException e){
throw new RuntimeException(e);
}
}
/**
* 验证PDF数字签名
* 签发规则:
* 1. 每个页面需有一个数字签名
* 2. 数字签名名称必须以 SIGN 开头, 且签章名称需要需与页码一致,如签章名称为 SIGN1, 签章页码为1
* 3. 数字签名的证书信息必须一致
* 4. 签章原因字段 Reason -> 解密后的字符串并序列化后为Map {"curPage": 签章页码, "signType": "DIGITAL_SIGN"}
*
* @param pdfInputStream PDF文件输入流
*/
public static boolean verifyDigitalSignature(InputStream pdfInputStream) {
try (PdfReader pdfReader = new PdfReader(pdfInputStream);
PdfDocument pdfDoc = new PdfDocument(pdfReader);){
SignatureUtil signUtil = new SignatureUtil(pdfDoc);
List<String> names = signUtil.getSignatureNames();
if (names.isEmpty()) {
log.info("PDF未签名");
return false;
};
// 验证签名有效性
for (String name : names) {
PdfPKCS7 pkcs7 = signUtil.readSignatureData(name);
if (!pkcs7.verifySignatureIntegrityAndAuthenticity()) {
log.info("PDF签名有效性验证失败");
return false;
}
}
// 验证PDF是否为本公司签发
long signNumber = names.stream()
.filter(name -> name.contains(PDF_SIGN_FILE_NAME_PRE))
.count();
if (signNumber != pdfDoc.getNumberOfPages()) {
log.info("数字签名数与页码数不一致");
return false;
}
for (String name : names) {
if (!name.contains(PDF_SIGN_FILE_NAME_PRE)) continue;
PdfPKCS7 pkcs7 = signUtil.readSignatureData(name);
Certificate[] chain = pkcs7.getSignCertificateChain();
//验证数字签名证书
if (chain != null && chain.length > 0) {
X509Certificate cert = (X509Certificate) chain[0];
String subjectDN = cert.getSubjectX500Principal().getName();
if (!subjectDN.trim().equals(CERT_SUBJECT_DN)) {
log.info("数字签名证书不匹配,当前数字签名证书:{}", subjectDN);
return false;
}
}
// 验证数字签名原因
int pageNum = Integer.parseInt(name.substring(PDF_SIGN_FILE_NAME_PRE.length()));
String reason = pkcs7.getReason();
String decrypt = RSAUtil.decrypt(reason);
HashMap<String, Object> reasonMap = JsonUtil.parse(decrypt, new TypeReference<HashMap<String, Object>>() {});
if (!(reasonMap.get("curPage") != null
&& pageNum == Integer.parseInt(reasonMap.get("curPage").toString())
&& SIGN_TYPE_DIGITAL.equals(reasonMap.get("signType")))) {
log.info("数字签名原因不匹配!");
return false;
}
}
} catch (IOException e) {
throw new ServiceException("PDF解析异常:" + e);
} catch (GeneralSecurityException e) {
log.error("PDF签名验证有效性失败:{}", e.getMessage());
} catch (Exception e) {
log.error("PDF签名验证失败:{}", e.getMessage());
}finally {
if(pdfInputStream != null){
try {
pdfInputStream.close();
} catch (IOException e) {
log.error("PDF签名验证资源关闭异常:{}", e.getMessage());
}
}
}
return true;
}
/**
* 查找指定文本在PDF中的位置
*
* @param pdfInputStream PDF文件输入流
* @param targetText 目标文本
* @param pageNumbers 需要查找的页码
* @return Map<Integer, List < Rectangle>>
*/
public static Map<Integer, List<Rectangle>> findTextPositions(InputStream pdfInputStream, String targetText, int[] pageNumbers) {
Map<Integer, List<Rectangle>> positions = new HashMap<>();
try (PdfReader pdfReader = new PdfReader(pdfInputStream);
PdfDocument pdfDoc = new PdfDocument(pdfReader)) {
for (int pageNumber : pageNumbers) {
// 使用自定义文本定位策略
TextPositionStrategy strategy = new TextPositionStrategy(targetText);
PdfCanvasProcessor processor = new PdfCanvasProcessor(strategy);
processor.processPageContent(pdfDoc.getPage(pageNumber));
List<Rectangle> found = strategy.getFoundPositions();
if (!found.isEmpty()) {
positions.put(pageNumber, found);
}
}
return positions;
} catch (Exception e) {
throw new ServiceException(targetText + "文本查找失败:" + e.getMessage());
}
}
/**
* 获取签名页码数组
*
* @param pdfInputStream 待签名PDF输入流
* @param pageNumArray 需要签名页码数组,为空,则代表都需签名
* @return int[]
*/
public static int[] getPageNumArray(InputStream pdfInputStream, int[] pageNumArray) {
return getPageNumArray(inputStreamToByteArray(pdfInputStream), pageNumArray);
}
/**
* 获取签名页码数组
*
* @param pdfByte 待签名PDF字节数组
* @param pageNumArray 需要签名页码数组,为空,则代表都需签名
* @return int[]
*/
public static int[] getPageNumArray(byte[] pdfByte, int[] pageNumArray) {
int totalPage = 0;
//去重
pageNumArray = Arrays.stream(pageNumArray).distinct().toArray();
try (PdfReader pdfReader = new PdfReader(new ByteArrayInputStream(pdfByte));
PdfDocument pdfDoc = new PdfDocument(pdfReader)) {
totalPage = pdfDoc.getNumberOfPages();
}catch (Exception e){
throw new ServiceException("获取PDF总页数失败: " + e.getMessage());
}
// 验证页码合法性
if (pageNumArray != null) {
for (int pageNum : pageNumArray) {
if (pageNum < 1 || pageNum > totalPage) {
throw new IllegalArgumentException("无效的页码: " + pageNum);
}
}
}
// 是否全部签名
if (pageNumArray == null || pageNumArray.length == 0) {
pageNumArray = new int[totalPage];
for (int i = 0; i < totalPage; i++) {
pageNumArray[i] = i + 1;
}
}
return pageNumArray;
}
/**
* 获取需要数字签名的页码数组
* 1. pageNumArray为空代表全部需要数字签名
* 2. 已经进行数字签名的不再进行数字签名
*
* @param pdfInputStream 待签名PDF输入流
* @return int[]
*/
public static int[] getDigitalPageNumArray(InputStream pdfInputStream) {
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfInputStream))) {
SignatureUtil signUtil = new SignatureUtil(pdfDoc);
List<String> names = signUtil.getSignatureNames();
// 获取所有需要签名的页码(1~总页数)
int totalPages = pdfDoc.getNumberOfPages();
Set<Integer> unsignedPages = new HashSet<>();
for (int i = 1; i <= totalPages; i++) {
unsignedPages.add(i);
}
// 排除已签名页码
for (String name : names) {
if (name.startsWith(PDF_SIGN_FILE_NAME_PRE)) {
try {
int signedPage = Integer.parseInt(name.substring(PDF_SIGN_FILE_NAME_PRE.length()));
unsignedPages.remove(signedPage);
} catch (NumberFormatException e) {
log.warn("无效的签名名称格式: {}", name);
}
}
}
// 转换为数组返回
return unsignedPages.stream()
.mapToInt(Integer::intValue)
.toArray();
} catch (Exception e) {
throw new RuntimeException("获取需要数字签名的PDF页码失败! ", e);
}
}
/**
* 获取PDF总页数
*
* @param pdfInputStream PDF文件输入流
* @return int 总页数
*/
public static int getPageTotal(InputStream pdfInputStream) {
try (PdfReader pdfReader = new PdfReader(pdfInputStream);
PdfDocument pdfDoc = new PdfDocument(pdfReader)) {
return pdfDoc.getNumberOfPages();
}catch (Exception e){
throw new ServiceException("获取PDF总页数失败: " + e);
}
}
/**
* 处理签章内容
*
* @param pdfFis PDF输入流
* @param x 签章X坐标
* @param y 图章Y坐标
* @param sealImageWidth 图章宽度
* @param sealImageHeight 图章高度
* @param pageNum 签章页码
* @param sealImageByte 图章字节
* @param key 私钥
* @param chain 证书链
*
* @return 签章完的输出流
*/
private static ByteArrayOutputStream processSeal(InputStream pdfFis, float x, float y, int sealImageWidth, int sealImageHeight,
int pageNum, byte[] sealImageByte, PrivateKey key, Certificate[] chain) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try(PdfReader reader = new PdfReader(pdfFis)) {
PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties().useAppendMode());
// 配置签章外观
signer.setPageRect(new Rectangle(x, y, sealImageWidth, sealImageHeight));
signer.setPageNumber(pageNum);
signer.getSignatureAppearance()
.setSignatureGraphic(ImageDataFactory.create(sealImageByte))
.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
// 设置签章算法
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA256, BouncyCastleProvider.PROVIDER_NAME);
// 执行签章
signer.signDetached(digest, signature, chain,
null, null, null, 0, PdfSigner.CryptoStandard.CADES);
} catch (Exception e) {
throw new ServiceException("PDF签章失败: " + e.getMessage());
}
return outputStream;
}
/**
* 处理数字签章
*
* @param pdfFis pdf输入流
* @param pageNum 页码
* @param key 私钥
* @param chain 证书链
* @return 签章完的输出流
*/
private static ByteArrayOutputStream processDigitalSign(InputStream pdfFis, int pageNum, PrivateKey key, Certificate[] chain) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try(PdfReader reader = new PdfReader(pdfFis);) {
PdfSigner signer = new PdfSigner(reader, outputStream, new StampingProperties().useAppendMode());
signer.setPageNumber(pageNum);
// 设置每个签章的名称和签章验证内容
signer.setFieldName(PDF_SIGN_FILE_NAME_PRE + pageNum);
signer.setReason(getEncryptReason(pageNum, SIGN_TYPE_DIGITAL));
// 设置签章算法
IExternalDigest digest = new BouncyCastleDigest();
IExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA256, BouncyCastleProvider.PROVIDER_NAME);
// 执行签章
signer.signDetached(digest, signature, chain,
null, null, null, 0, PdfSigner.CryptoStandard.CADES);
} catch (Exception e) {
throw new ServiceException("PDF数字签章失败: " + e.getMessage());
}
return outputStream;
}
/**
* 获取加密原因
*
* @param pageNum 页码
* @param signType NUMBER_SIGN:数字
* @return 加密后的字符串
* @throws Exception 异常
*/
private static String getEncryptReason(int pageNum, String signType) throws Exception {
HashMap<String, Object> reasonMap = new HashMap<>();
// 当前页
reasonMap.put("curPage", pageNum);
// 签名类型 认证
reasonMap.put("signType", signType);
String reasonJson = JsonUtil.toJson(reasonMap);
return RSAUtil.encrypt(reasonJson);
}
/**
* InputStream 转 ByteArray
*
* @param inputStream 输入流
* @return byte[]
*/
private static byte[] inputStreamToByteArray(InputStream inputStream) {
try (BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("流转换失败", e);
}
}
}
文本定位策略实现
/**
* <P>
* <B>Description: IText7 文本定位策略</B>
* </P>
* Revision Trail: (Date/Author/Description)
* 2025/3/28 Ryan Huang CREATE
* @author Ryan Huang
* @version 1.0
*/
public class TextPositionStrategy extends LocationTextExtractionStrategy {
/**
* 目标文本
*/
private final String targetText;
/**
* 目标文本位置
*/
private final List<Rectangle> positions = new ArrayList<>();
/**
* 记录字符及对应坐标
*/
private final List<Pair<Character, Rectangle>> characterList = new ArrayList<>();
public TextPositionStrategy(String targetText) {
this.targetText = targetText.toUpperCase(); // 根据需求调整大小写策略
}
@Override
public void eventOccurred(IEventData data, EventType type) {
super.eventOccurred(data, type);
if (type != EventType.RENDER_TEXT) return;
TextRenderInfo renderInfo = (TextRenderInfo) data;
for (TextRenderInfo charInfo : renderInfo.getCharacterRenderInfos()) {
String charStr = charInfo.getText();
if (charStr.isEmpty()) continue;
// 记录字符及对应坐标
Rectangle rect = charInfo.getBaseline().getBoundingRectangle();
Vector startPoint = charInfo.getBaseline().getStartPoint();
characterList.add(new Pair<>(
charStr.charAt(0),
new Rectangle(
startPoint.get(Vector.I1),
startPoint.get(Vector.I2),
rect.getWidth(),
rect.getHeight()
)
));
}
}
public List<Rectangle> getFoundPositions() {
// 构建完整文本
StringBuilder fullText = new StringBuilder();
for (Pair<Character, Rectangle> pair : characterList) {
fullText.append(pair.getKey());
}
// 查找所有匹配位置
int index = 0;
while ((index = fullText.indexOf(targetText, index)) != -1) {
int endPos = index + targetText.length();
if (endPos > characterList.size()) break;
// 合并目标区域的坐标
List<Rectangle> targetRects = characterList.subList(index, endPos)
.stream()
.map(Pair::getValue)
.collect(Collectors.toList());
positions.add(mergeRectangles(targetRects));
index = endPos; // 继续查找后续匹配
}
return positions;
}
// 合并矩形区域
private Rectangle mergeRectangles(List<Rectangle> rects) {
float minX = Float.MAX_VALUE;
float minY = Float.MAX_VALUE;
float maxX = Float.MIN_VALUE;
float maxY = Float.MIN_VALUE;
for (Rectangle r : rects) {
minX = Math.min(minX, r.getX());
minY = Math.min(minY, r.getY());
maxX = Math.max(maxX, r.getX() + r.getWidth());
maxY = Math.max(maxY, r.getY() + r.getHeight());
}
return new Rectangle(
minX,
minY,
maxX - minX,
maxY - minY
);
}
}
加密解密工具实现
/**
* <p>
* <B>Description: RSA加密工具</B>
* </P>
* Revision Trail: (Date/Author/Description)
* 2025/3/28 Ryan Huang CREATE
*
* @author Ryan Huang
* @version 1.0
*/
public class RSAUtil {
/**
* 加密算法
*/
private static final String ALGORITHM = "RSA";
/**
* 密钥长度
*/
private static final int KEY_SIZE = 2048;
/**
* 公钥
*/
private static final String PUBLIC_KEY = "";
/**
* 私钥
*/
private static final String PRIVATE_KEY = "";
/**
* 生成密钥对
*
* @return 结果
* @throws NoSuchAlgorithmException 异常
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
/**
* 公钥加密
*
* @param plainText 明文
* @param publicKey 公钥
*
* @return 加密后的字符串
* @throws Exception 异常
*/
public static String encrypt(String plainText, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
/**
* 公钥加密
*
* @param plainText 明文
*
* @return 加密后的字符串
* @throws Exception 异常
*/
public static String encrypt(String plainText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
Key publicKey = getPublicKeyFromString(PUBLIC_KEY);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
/**
* 私钥解密
*
* @param encryptedText 密文
* @param privateKey 私钥
*
* @return 解密后的字符串
* @throws Exception 异常
*/
public static String decrypt(String encryptedText, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes);
}
/**
* 私钥解密
*
* @param encryptedText 密文
*
* @return 解密后的字符串
* @throws Exception 异常
*/
public static String decrypt(String encryptedText) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
Key privateKey = getPrivateKeyFromString(PRIVATE_KEY);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes);
}
/**
* 将公钥转换为字符串
*
* @param publicKey 公钥
* @return 字符串
*/
public static String getPublicKeyString(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
/**
* 从字符串中获取私钥
*
* @param privateKey 私钥
* @return 私钥字符串
*/
public static String getPrivateKeyString(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
/**
* 从字符串中获取公钥
*
* @param publicKeyString 公钥
* @return 公钥字符串
* @throws Exception 异常
*/
public static PublicKey getPublicKeyFromString(String publicKeyString) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyString);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(spec);
}
/**
* 从字符串中获取私钥
*
* @param privateKeyString 私钥
* @return 私钥字符串
* @throws Exception 异常
*/
public static PrivateKey getPrivateKeyFromString(String privateKeyString) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(spec);
}
}