Java开发 PDF文件生成方案
业务需求背景
业务端需要能够将考试答卷内容按指定格式呈现并导出为pdf格式进行存档,作为紧急需求插入。导出内容存在样式复杂性,包括特定的字体(中文)、字号、颜色,页面得有页眉、页码,数据需要进行表格聚合处理,并且需要动态处理边框、单元格、数据文本格式化等,整体功能上线时间紧迫。

第一版方案实现:前端显示 + 后端 Selenium 调用浏览器打印
为能够尽快上线此功能,团队经讨论确定第一版方案以满足需求。
实现原理 该方案核心在于借助浏览器的渲染能力,通过 Selenium 库搭配 Chrome Headless 无头浏览器模拟用户操作,具体步骤如下:
-
前端页面设计:前端开发人员根据业务需求,构建一个完整的网页模板,确保所有样式和布局都符合预期。
-
后端调用与打印:后端服务器通过Selenium库启动Chrome Headless浏览器,加载前端生成的页面链接。然后调用浏览器的打印功能,将页面转换为PDF格式并保存到指定路径供用户下载。
优点
-
快速实现:由于前端页面已经具备完善的样式和布局,后端只需负责调用和转换,因此可以较快上线。
-
充分利用现有资源:借助浏览器本身的渲染引擎,避免了额外的开发工作量。
缺点
-
性能瓶颈:每次导出都需要启动浏览器实例,消耗较多系统资源,尤其在高并发场景下容易出现性能问题。
-
潜在风险:集成第三方浏览器服务会引入额外的依赖项,从而增加系统的复杂性和不可靠性。这种外部依赖可能导致系统在面对第三方服务的故障、维护或更新时出现不稳定的情况,进而影响整体的服务质量和用户体验。
第二版方案实现:后端生成 Excel 再转成 PDF
由于存在潜在风险和性能瓶颈,需要将现有方案优化为后端生成。
具体实现
Java Excel转PDF POI+Itext5-CSDN博客
转换方案
当前市面上Excel转Pdf方案分为两类:
一:成熟的商业产品,可以直接调转换方法一键生成PDF
二:开源方案,可以写入PDF,但是不支持直接转换,也不提供转换方案,可行的方案通常为第三方自行编写的Util类开源

由于商业产品收费很高,故使用开源组件。
商业产品:aspose、spire
开源组件:itextpdf
参考文档:
Java开发中Word转PDF文件5种方案横向评测_java word转pdf-CSDN博客
Java Excel转PDF(免费) - 天航星 - 博客园
实现原理 此方案分为三个主要步骤:
-
填充 Excel 模版:将已有的Excel模版进行数据填充,写入Excel中
-
写入 Excel 文件:由于表格内容格式过于复杂,且需要根据不同数据动态合并单元格等情况,无法使用模版填充,使用Apache POI库,按照规定的格式写入Excel文件。在此过程中,需对每个单元格进行格式设置,如数据类型、对齐方式、边框、合并等,以确保数据展示规整有序。
-
转换为 PDF 文件:使用iText库将生成的Excel文件转换为PDF格式。转换时需要调整PDF页面布局,包括页面大小、边距、字体、字号、颜色等样式属性,确保最终输出符合项目要求。
优点
-
格式一致性:Excel本身具有强大的表格处理能力,能够很好地保证数据格式的一致性和准确性。
-
易于调试:在Excel中更容易发现和修正问题,可以使用Offic等软件直观查看。
-
数据模版:可以使用模版的方式改变样式布局,减少代码改动。
缺点
-
效率低下:涉及两次转换过程,增加了处理时间和资源消耗。
异常
用itext转换pdf时,如果单元格内容过多,会出现该bug
com.itextpdf.text.DocumentException: java.lang.NullPointerException: Cannot read field \"llx\" because \"cell\" is null
在互联网中未出现的bug,经过研究后无法修复,但是目前市场上的成熟转换方案都是商业产品,免费或使用版本限制太多,无法满足需求,改用直接写入pdf方案。
解决方案
com.itextpdf.text.DocumentException: java.lang.NullPointerException: Cannot read field \“llx\“ becau-CSDN博客
第三版方案实现:纯后端 PDF 生成
由于上述 bug 经多人研究解决及替换方案均无果,只能改用直接写入 PDF 的方案。
实现步骤:
代码替换:由于原本方案实现的布局代码已经完善,数据构造和布局填充是分离的,使用新方案只需要修改poi处代码,改用itext的方式重新写入即可
避坑
-
单元格合并时机:使用 POI 方式时,代码逻辑为先填充表格全部单元格内容,最后判断单元格进行合并。在 iText 方案中,此逻辑会导致合并单元格跨页时,下一页合并单元格丢失效果。经研究发现,需在创建合并单元格的第一个单元格时就指定合并区域,余下被合并单元格不再写入 PdfPTable。
-
分批次写入document:当一次写入内容过多时,依然会抛出关于 “llx” 的 bug。需减少一次写入 document 的单元格数量,目前方案是每道题作为一个新的 PdfPTable,处理完成就写入一次 document,而非整张试卷一次性写入。
相关文章:
Java开发 PDF文件生成方案
业务需求背景 业务端需要能够将考试答卷内容按指定格式呈现并导出为pdf格式进行存档,作为紧急需求插入。导出内容存在样式复杂性,包括特定的字体(中文)、字号、颜色,页面得有页眉、页码,数据需要进行表格聚…...
数学期望和方差
数学期望(Mathematical Expectation)和方差(Variance)是概率论和统计学中两个非常重要的概念。下面将分别对这两个概念进行解释。 数学期望 数学期望是随机变量的平均值,它描述了随机变量的中心位置。对于离散随机变…...
【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据...本篇介绍Tensor RT 的优化流程。
【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 【面试AI算法题中的知识点】方向涉及:ML/DL/CV/NLP/大数据…本篇介绍Tensor RT 的优化流程。 文章目录 【面试AI算法题中的知识点】方向涉及:ML/D…...
BLDC无感控制的驱动逻辑
如何知道转子已经到达预定位置,因为我们只有知道了转子到达了预定位置之后才能进行换相,这样电机才能顺滑的运转。转子位置检测常用的有三种方式。 方式一:通过过零检测,三相相电压与电机中性点电压进行比较。过零检测的优点在于…...
BP神经网络的反向传播算法
BP神经网络(Backpropagation Neural Network)是一种常用的多层前馈神经网络,通过反向传播算法进行训练。反向传播算法的核心思想是通过计算损失函数对每个权重的偏导数,从而调整权重,使得网络的预测输出与真实输出之间…...
[实用指南]如何将视频从iPhone传输到iPad
概括 将视频从 iPhone 传输到 iPad 时遇到问题?您可能知道一种方法,但不知道如何操作。此外,您要传输的视频越大,完成任务就越困难。那么如何将视频从 iPhone 传输到 iPad,特别是当您需要发送大视频文件时?…...
Linux Snipaste 截图闪屏/闪烁
防 csdn 不能看,Go to juejin Linux Snipaste 截图时窗口元素一闪一闪的无法正常使用。 解决此问题时系统环境为 Manjaro KDE6,不过我在其他发行版与 gnome 上也碰到了。 先放解决办法: # 启动 Snipaste 时去掉缩放参数 env -u QT_SCREEN_…...
【YOLOv5】源码(common.py)
该文件位于/models/common.py,提供了构建YOLOv5模型的各种基础模块,其中包含了常用的功能模块,如自动填充autopad函数、标准卷积层Conv、瓶颈层Bottleneck、C3、SPPF、Concat层等 参考笔记:【YOLOv3】 源码(common.py…...
Node 如何生成 RSA 公钥私钥对
一、引入crypto模块 crypto 为node 自带模块,无需安装 const crypto require(crypto);二、封装生成方法 async function generateRSAKeyPair() {return new Promise((resolve, reject) > {crypto.generateKeyPair(rsa, {modulusLength: 2048, // 密钥长度为 …...
瑞_Linux中部署配置Java服务并设置开机自启动
文章目录 背景Linux服务配置步骤并设置开机自启动附-Linux服务常用指令 🙊 前言:由于博主在工作时,需要将服务部署到 Linux 服务器上运行,每次通过指令启动服务非常麻烦,所以将 jar 包部署的服务设置开机自启动&#x…...
javaEE-多线程进阶-JUC的常见类
juc:指的是java.util.concurrent包,该包中加载了一些有关的多线程有关的类。 目录 一、Callable接口 FutureTask类 参考代码: 二、ReentrantLock 可重入锁 ReentrantLock和synchronized的区别: 1.ReentantLock还有一个方法:…...
Flume拦截器的实现
Flume conf文件编写 vim file_to_kafka.conf#定义组件 a1.sources r1 a1.channels c1#配置source a1.sources.r1.type TAILDIR a1.sources.r1.filegroups f1 a1.sources.r1.filegroups.f1 /Users/zhangjin/model/project/realtime-flink/applog/log/app.* # 设置断点续传…...
Swift Combine 学习(四):操作符 Operator
Swift Combine 学习(一):Combine 初印象Swift Combine 学习(二):发布者 PublisherSwift Combine 学习(三):Subscription和 SubscriberSwift Combine 学习(四&…...
leetcode 173.二叉搜索树迭代器栈绝妙思路
以上算法题中一个比较好的实现思路就是利用栈来进行实现,以下方法三就是利用栈来进行实现的,思路很好,很简练。进行next的时候,先是一直拿到左边的子树,直到null为止,这一步比较好思考一点,下一…...
df.groupby([pd.Grouper(freq=‘1M‘, key=‘Date‘), ‘Buyer‘]).sum()
df.groupby([pd.Grouper(freq1M, keyDate), Buyer]).sum() 用于根据特定的时间频率和买家(Buyer)对 DataFrame 进行分组,然后计算每个分组的总和。下面是对这行代码的逐步解释: df.groupby([...]):这个操作会根据传入的…...
LLM - 使用 LLaMA-Factory 部署大模型 HTTP 多模态服务 (4)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144881432 大模型的 HTTP 服务,通过网络接口,提供 AI 模型功能的服务,允许通过发送 HTTP 请求,交互…...
icp备案网站个人备案与企业备案的区别
个人备案和企业备案是在进行ICP备案时需要考虑的两种不同情况。个人备案是指个人拥有的网站进行备案,而企业备案则是指企业或组织名下的网站进行备案。这两者在备案过程中有一些明显的区别。 首先,个人备案相对来说流程较为简单。个人备案只需要提供个人…...
如何不修改模型参数来强化大语言模型 (LLM) 能力?
前言 如果你对这篇文章感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。 大语言模型 (Large Language Model, LLM, e.g. ChatGPT) 的参数量少则几十亿,多则上千亿,对其的训…...
AF3 AtomAttentionEncoder类的init_pair_repr方法解读
AlphaFold3 的 AtomAttentionEncoder 类中,init_pair_repr 方法方法负责为原子之间的关系计算成对表示(pair representation),这是原子转变器(atom transformer)模型的关键组成部分,直接影响对蛋白质/分子相互作用的建模。 init_pair_repr源代码: def init_pair_repr(…...
DDoS攻击防御方案大全
1. 引言 随着互联网的迅猛发展,DDoS(分布式拒绝服务)攻击成为了网络安全领域中最常见且危害严重的攻击方式之一。DDoS攻击通过向目标网络或服务发送大量流量,导致服务器过载,最终使其无法响应合法用户的请求。本文将深…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
测试微信模版消息推送
进入“开发接口管理”--“公众平台测试账号”,无需申请公众账号、可在测试账号中体验并测试微信公众平台所有高级接口。 获取access_token: 自定义模版消息: 关注测试号:扫二维码关注测试号。 发送模版消息: import requests da…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
