PDF入参以及模板对应签章图踩坑点

news/2025/9/25 13:55:57/文章来源:https://www.cnblogs.com/wangliangyu/p/19111067
模板PDF推荐使用万兴PDF工具破解版调整表单域和表单域名称,入参后的PDF需要设置入参字体和扁平化来保证PDF可以直接显示入参参数。可以防止出现打开PDF显示
文本域

// OSS上的PDF模板文件URL
private static final String OSS_PDF_TEMPLATE_URL = "";


@PostMapping("/oss/uploadSignedPdf")
public Result<String> uploadSignedPdfToOSS(@RequestBody JSONObject requestBody) {
try {
// 参数校验
String idCard = CommonUtils.processNullableValue(requestBody.getString("idCard"), "");
String email = CommonUtils.processNullableValue(requestBody.getString("email"), "");
String phone = CommonUtils.processNullableValue(requestBody.getString("phone"), "");
String signatureBase64 = CommonUtils.processNullableValue(requestBody.getString("signature"), "");

if (signatureBase64.isEmpty()) {
return Result.error("签名图片不能为空");
}

// 移除Base64字符串中的前缀(如果存在)
if (signatureBase64.startsWith("data:image/png;base64,")) {
signatureBase64 = signatureBase64.substring("data:image/png;base64,".length());
}
// Base64解码签名图片
byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64);
File signatureFile = File.createTempFile("signature_", ".png");
Files.write(signatureFile.toPath(), signatureBytes);

// 创建带签名的PDF(使用PdfReader)
File tempPdf = generateSignedPdf(idCard, email, phone, signatureFile);

// 上传OSS
String relativePath = "contracts/" + phone+tempPdf.getName();
String ossUrl = OssBootUtil.upload(
new FileInputStream(tempPdf),
relativePath
);

// 清理临时文件
signatureFile.delete();
tempPdf.delete();

return Result.OK(ossUrl);
} catch (IOException | IllegalArgumentException | DocumentException e) {
return Result.error("文件处理失败: " + e.getMessage());
}
}

// 签名图片缩放比例常量
private static final float SIGNATURE_IMAGE_SCALE = 0.1f; // 将图片缩小到原来的10%
private static final String SIGNATURE_FORM_FIELD = "signature"; // 签名表单域名称

private File generateSignedPdf(String idCard, String email, String phone, File signature) throws IOException, DocumentException {
// 使用PdfReader读取模板
PdfReader reader = new PdfReader(OSS_PDF_TEMPLATE_URL);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream("temp_contract.pdf"));
// 设置中文字体,使用更通用的字体配置以确保在各种设备上都能正常显示
BaseFont baseFont = null;
try {
// 尝试使用iText内置的中文字体
baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e1) {
try {
// 备选字体方案
baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e2) {
log.warn("无法加载中文字体,使用默认字体");
}
}

Font signatureFont = baseFont != null ? new Font(baseFont, 12, Font.NORMAL) : new Font();

Image signatureImage = Image.getInstance(signature.getAbsolutePath());

// 填充表单字段
AcroFields form = stamper.getAcroFields();

// 添加字体替代,确保中文正常显示
if (baseFont != null) {
form.addSubstitutionFont(baseFont);
}

// 填充表单字段,使用setValueAsString替代setField,可能在某些情况下更可靠
if (idCard != null && !idCard.isEmpty()) {
form.setField("idCard", idCard);
}
if (email != null && !email.isEmpty()) {
form.setField("email", email);
}
if (phone != null && !phone.isEmpty()) {
form.setField("phone", phone);
}


// 添加签名图片到PDF
List<AcroFields.FieldPosition> fieldPositions = form.getFieldPositions(SIGNATURE_FORM_FIELD);
if (fieldPositions != null && !fieldPositions.isEmpty()) {
for (AcroFields.FieldPosition fieldPosition : fieldPositions) {
int page = fieldPosition.page;
PdfContentByte canvas = stamper.getOverContent(page);

Rectangle signRect = fieldPosition.position;
float x = signRect.getLeft();
float y = signRect.getBottom();

// 调整签名图片大小 - 将图片缩小到原来的10%
float width = signatureImage.getWidth() * SIGNATURE_IMAGE_SCALE;
float height = signatureImage.getHeight() * SIGNATURE_IMAGE_SCALE;
signatureImage.scaleToFit(width, height);

// 设置图片位置
signatureImage.setAbsolutePosition(x, y);
canvas.addImage(signatureImage);
}
log.info("成功在{}个页面位置添加签名图片", fieldPositions.size());
} else {
log.warn("未找到签名表单域:{}", SIGNATURE_FORM_FIELD);
}


// 添加当前日期
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String currentDate = dateFormat.format(new Date());
if (currentDate != null && !currentDate.isEmpty()) {
form.setField("signDate", currentDate);
}

// 设置表单扁平化,但不启用完全压缩,可能有助于移动设备兼容性
stamper.setFormFlattening(true);

stamper.close();
reader.close();
return new File("temp_contract.pdf");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站分页导航廊坊百度快照优化

一、Android抓包方式 对Https降级进行抓包&#xff0c;降级成Http使用抓包工具对Https进行抓包 二、常用的抓包工具 wireshark&#xff1a;侧重于TCP、UDP传输层&#xff0c;HTTP/HTTPS也能抓包&#xff0c;但不能解密HTTPS报文。比较复杂fiddler&#xff1a;支持HTTP/HTTPS…

高性能PCIe 3.0软核,x1~x16,支持EP/RC,AXI4接口,内置DMA控制器,适用ASIC和FPGA

PCIe-AXI-Controller兼容PCI Express Base Specification Revision 3.1,实现PCIe PHY Layer,Data Link Layer以及Transaction Layer的所有功能特性,不仅内置DMA控制器,而且具备AXI4用户接口,提供一个高性能,易于…

使用git clone 批量下载huggingface模型文件

1.选定要下载的模型 以下载moka-ai/m3e-base为例,切换到Files and versions。2.更改下载网页的url 如上图所示,当前要下载模型网页的url为: https://huggingface.co/moka-ai/m3e-base/tree/mainAI写代…

深入 Spring Boot 异常处理底层机制 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Python 换进安装GDAL

Hello World本文来自博客园,作者:南宫影,转载请注明原文链接:https://www.cnblogs.com/nangongying/p/19111056

sync(同步本地文件到OSS)

注意事项要将本地文件同步到OSS,您必须具有oss:PutObject、oss:ListObjects和oss:DeleteObject权限。具体操作,请参见为RAM用户授权自定义的权限策略。Binary名称 从ossutil 1.6.16版本开始,命令行中Binary名称支持…

云南装饰公司做网站湖北城乡住房建设厅网站怎查证件

操作系统习题习题一一、选择习题二一、选择二、综合题习题三一、选择题&#xff1f;二、简答题进程互斥遵循的四个原则&#xff1a;空闲让进、忙则等待、有限等待、让权等待重点习题四一、选择&#xff1f;&#xff1f;二、综合题死锁产生的 4 个必要条件是&#xff1a; &#…

淘宝客网站一定要备案网易企业邮箱怎么申请

Xsens动作捕捉技术助力于中国戏曲演员运动分析 搜维尔科技&#xff1a;Movella Xsens动作捕捉技术助力于中国戏曲演员运动分析

番禺网站建设培训班友情链接交换方式有哪些

一、什么是数据结构 数据结构是一组用来保存一种或多种特定关系的数据的集合。其主要目的是组织和存储数据&#xff0c;以便于高效的访问和修改。在程序设计中&#xff0c;将大量而复杂的问题以特定的数据类型和特定的存储结构存储在内存中&#xff0c;并在此基础上实现某个特定…

网站关键词搜不到了百度建网站

做过很多winform项目&#xff0c;都为winform控件头疼不已。想实现一些漂亮的样子总是很难。我这里列举几个缺点&#xff1a; 1.winform控件大多是 绝对布局 &#xff0c;你需要给出准确的坐标。那么在实现居中效果就会很难。 2.学习成本&#xff0c;也了解各个控件的结构&…

MyBatisPlus 会默认设置 mybatis 的 scanPackages 为当前 BeanFactory 的 auto-configuration 的 base packages

MyBatisPlus 会默认设置 mybatis 的 scanPackages 为当前 BeanFactory 的 auto-configuration 的 base packagesMybatisPlus 在自动配置时,会自动注册 MapperScannerConfigurer,并设置 自动扫描 Mapper 的 basePacka…

工程实践 使用本地包开发python项目

工程实践 使用本地包开发python项目 当python项目比较庞大, 把共用的公共函数和方法封装成一个本地包不仅便于在不同项目间复用,还能通过统一的方式读取资源文件, 是一个很好的工程实践. 下面来介绍一下这种开发方式.…

详细介绍:Python + Flask + API Gateway + Lambda + EKS 实战

详细介绍:Python + Flask + API Gateway + Lambda + EKS 实战pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Con…

实用指南:【设计模式】适配器模式 在java中的应用

实用指南:【设计模式】适配器模式 在java中的应用2025-09-25 13:45 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displ…

日记4

今天接着学Java,直接从类和对象入手。理解类是模板,对象是实例,像设计图纸和盖好的房子。试着写简单类定义对象,一开始老把属性和方法弄混,改了几次代码,终于成功创建对象并调用方法,感觉对面向对象的思路更清晰…

P2042 [NOI2005] 维护数列 题解

QwQP2042 [NOI2005] 维护数列 题解 平衡树 因为操作里面有翻转,严格强于文艺平衡树,所以考虑平衡树维护数列。直接暴力插入即可 分裂出删除的子段,然后合并其两端的平衡树 分裂出修改的子段,然后打推平的懒标记, …

达梦数据库查询字段类型为Date 修改为DateTime

SELECT ALTER TABLE || OWNER || . || TABLE_NAME || MODIFY || COLUMN_NAME || DATETIME; AS alter_sql FROM ALL_TAB_COLUMNS WHERE DATA_TYPE = DATE and OWNER=PS_EXAMPLEDBUSER order by COLUMN_NAME asc

详细介绍:PyTorch 神经网络工具箱

详细介绍:PyTorch 神经网络工具箱pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

C++ new 操作符在操作系统层执行了什么操作?

C++ new 操作符在操作系统层执行了什么操作?在C++中,new操作符的执行涉及操作系统层面的内存分配和对象构造过程,具体可分为以下几个关键步骤: 1. 调用内存分配函数(operator new) new操作符首先通过operator ne…

[ABC422F-G] 题解

QwQ[ABC422F-G] 题解 F - Eat and Ride 考虑 DP,DP 状态要么压和要么压长度,如果压和就很直接,但是显然复杂度会爆炸,如果压长度的话,可以发现每到一个新点都要算:这条路径中在它后面的点的个数乘上它的点权,所…