当前位置: 首页 > news >正文

生成PDF文件(基于 iText PDF )

一、准备工作

pom依赖

        <dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.1.17</version><type>pom</type></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>kernel</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>io</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>layout</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>forms</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>pdfa</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>sign</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>barcodes</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>hyph</artifactId><version>7.1.17</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>font-asian</artifactId><version>7.1.17</version></dependency>

二、代码处理

业务实体(测试实体,举例一个)

package com.hawkcloud.flow.vo;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** * 历史记录**/
@Data
public class History implements Serializable {private static final long serialVersionUID = 1L;/*** id*/private Long id;@ApiModelProperty(value = "处理人")private String operators;@ApiModelProperty(value = "抄送人")private String targetUsers;@ApiModelProperty(value = "处理意见")private String content;@ApiModelProperty(value = "节点名称")private String node;@ApiModelProperty(value = "审批状态")private String approvalStatus;@ApiModelProperty(value = "处理时间")private Date handleTime;} 

生成代码:

package com.test.service.impl;import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import com.test.core.jackson.JsonUtil;
import com.test.core.log.exception.ServiceException;
import com.test.core.utils.CollectionUtil;
import com.test.core.utils.DateUtil;
import com.test.core.utils.StringUtil;
import com.test.flow.feign.IProcessClient;
import com.test.flow.vo.History;
import com.test.hcd.api.constant.MessageKeyConstant;
import com.test.hcd.service.IReconBillQueryService;
import com.test.hdl.clearance.entity.ClsSettlementBill;
import com.test.message.MessageStruct;
import com.test.resource.feign.IAttachClient;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.UnitValue;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;/*** @Author R* @Description 生成PDF* @Date 2025/6/11 09:56**/
@Slf4j
@Service
public class generationPdfServiceImpl {@Value("${tempFilePath:/Users/files/temp}")private String tempFilePath;@Resourceprivate IReconBillQueryService reconBillQueryService;@Resourceprivate IProcessClient processClient;@Resourceprivate IAttachClient attachClient;@RabbitListener(queues = {MessageKeyConstant.SETTLEBILL_REVIEW_SUCCESS_QUE})@RabbitHandlerpublic void startSignature(MessageStruct messageStruct, Message message, Channel channel) {String filePath = null;try {long start = System.currentTimeMillis();// TODO 1,获取业务单据信息ClsSettlementBill bill = JsonUtil.parse(messageStruct.getMessage(), ClsSettlementBill.class);ReconBill reconBill = reconBillQueryService.getBill(bill.getBillId());// TODO 2,生成并获取pdf路径filePath = generationPdf(bill, reconBill);// TODO 3,业务处理(文件上传,写入数据库)Long fileId = uploadFile(filePath);updateBill(reconBill.getId(), fileId);// 成功处理,发送 ACKchannel.basicAck(message.getMessageProperties().getDeliveryTag(), false);long end = System.currentTimeMillis();log.info("pdf文件生成成功,累计耗时" + (end - start) / 1000 + "s");} catch (Exception e) {log.error(e.getMessage());try {channel.basicNack(message.getMessageProperties().getDeliveryTag(),// 仅拒绝当前消息false,// 重新放回队列(或设为 false 进入死信队列)true);} catch (IOException e1) {e1.printStackTrace();}} finally {// TODO 4,删除临时文件,避免占用磁盘空间if (StringUtil.isNotBlank(filePath)) {FileUtil.del(filePath);}}}private String generationPdf(ClsSettlementBill bill, ReconBill reconBill) throws IOException {try {String title = reconBill.getOrgName() + "与" + reconBill.getReconOrgName() + "账单";StringBuilder subtitle = new StringBuilder("日期:").append(reconBill.getBusiBeginDate()).append("-").append(reconBill.getBusiEndDate());String fileName = "测试单据";String filePath = Paths.get(tempFilePath, fileName).toString();File file = new File(filePath);// 创建父目录(若不存在)File parentDir = file.getParentFile();if (parentDir != null && !parentDir.exists()) {parentDir.mkdirs();  // 自动创建多级目录}List<History> histories = processClient.history(bill.getProcessInstanceId());createPdf(file, title, subtitle.toString(), reconBill, histories);log.error("对账单PDF已成功保存到: {}", new File(filePath).getAbsolutePath());return filePath;} catch (Exception e) {throw new ServiceException("生成PDF文件异常:" + e.getMessage());}}/*** 创建PDF** @param file* @param title* @param subtitle* @throws IOException*/private void createPdf(File file, String title, String subtitle, ReconBill reconBill,List<History> histories) throws IOException {// 1. 初始化文档PdfDocument pdf = new PdfDocument(new PdfWriter(file));Document doc = new Document(pdf);// 2. 添加标题和副标题addTitleAndSubtitle(doc, title, subtitle);// 3. 添加单据信息表格addReconBillTable(doc, reconBill);// 4. 添加审批流程表格addApprovalHistoryTable(doc, histories);// 5. 关闭文档doc.close();}private void addTitleAndSubtitle(Document doc, String title, String subtitle) throws IOException {doc.add(new Paragraph(title).setTextAlignment(TextAlignment.CENTER)//设置字体.setFont(getChineseFont()).setFontSize(15)//行高.setFixedLeading(30)//字体加粗.setBold());doc.add(new Paragraph(subtitle).setTextAlignment(TextAlignment.CENTER)//设置字体.setFont(getChineseFont()).setFontSize(8).setFontColor(new DeviceRgb(128, 128, 128))//行高.setFixedLeading(15));}private void addReconBillTable(Document doc, ReconBill reconBill) throws IOException {Table customerTable = new Table(UnitValue.createPercentArray(new float[]{1.5F, 3, 3, 3, 3, 3})).setWidth(500).setFontSize(9)//设置字体.setFont(getChineseFont()).setKeepTogether(true).setMarginBottom(30);// 单据表头addTableRowHeader(customerTable);// 单据表头addTableRow(customerTable, reconBill.getDetailList());doc.add(customerTable);}private void addApprovalHistoryTable(Document doc, List<History> histories) throws IOException {// 审批流程标题doc.add(new Paragraph("审批流程").setTextAlignment(TextAlignment.LEFT)//设置字体.setFont(getChineseFont()).setFontSize(11)//行高.setFixedLeading(30)//字体加粗.setBold());// 添加表格(审批流程)Table orderTable = new Table(UnitValue.createPercentArray(new float[]{3, 3, 3, 3, 3})).setWidth(500).setFontSize(8)// 单元格级别也设置.setKeepTogether(true)//设置字体.setFont(getChineseFont());// 审批流程表头addOrderHeader(orderTable);// 审批流程内容addOrderRow(orderTable, histories);doc.add(orderTable);}private PdfFont getChineseFont() throws IOException {return PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", true);}private void addTableRowHeader(Table customerTable) throws IOException {List<String> headers = new ArrayList<>();headers.add("序号");headers.add("类别");headers.add("项目");headers.add("金额");headers.add("结果");headers.add("备注");addTableHeader(customerTable, headers);}private void addTableRow(Table customerTable, List<ReconBillDetail> detailList) {if (CollectionUtil.isNotEmpty(detailList)) {int index = 1;for (ReconBillDetail detail : detailList) {addTableRow(customerTable,Integer.toString(index++),detail.getAccountCategoryName() == null ? "" : detail.getAccountCategoryName(),detail.getAccountItemName() == null ? "" : detail.getAccountItemName(),formatValue(detail.getCalAmount()),detail.getCheckResult() == null ? "" : detail.getCheckResult(),detail.getRemark() == null ? "" : detail.getRemark());}}}private static String formatValue(Number value) {return value == null ? "--" : value.toString();}private void addOrderHeader(Table orderTable) throws IOException {List<String> orderHeader = new ArrayList<>();orderHeader.add("名称");orderHeader.add("审批人");orderHeader.add("审批结果");orderHeader.add("审批意见");orderHeader.add("审批时间");addTableHeader(orderTable, orderHeader);}private void addOrderRow(Table orderTable, List<History> histories) {if (CollectionUtil.isNotEmpty(histories)) {histories.forEach(history -> {history.forEach(singleHistory -> {String operator = singleHistory.getOperators() + (StringUtil.isNotBlank(singleHistory.getTargetUsers())? "\n抄送:" + singleHistory.getTargetUsers() : "");String content = StringUtil.isNotBlank(singleHistory.getContent()) ? singleHistory.getContent() : "--";addTableRow(orderTable, history.getNode() == null ? "" : history.getNode(), operator,singleHistory.getApprovalStatus() == null ? "" : singleHistory.getApprovalStatus(),content, DateUtil.format(singleHistory.getHandleTime(), "yyyy-MM-dd HH:mm:ss"));});});}}/*** 添加表头*/private void addTableHeader(Table table, List<String> orderHeader) throws IOException {// 设置表头行的每个单元格for (String header : orderHeader) {Cell headerCell = new Cell().add(new Paragraph(header).setFont(PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", true)).setFontSize(10).setBold()).setBackgroundColor(new DeviceRgb(248, 248, 249)).setTextAlignment(TextAlignment.CENTER)// 单元格内边距.setPadding(5);table.addHeaderCell(headerCell);}}/*** 添加表格行*/private void addTableRow(Table table, String... cells) {for (String cell : cells) {table.addCell(new Cell().add(new Paragraph(cell)));}}/*** 上传文件** @param filePath 文件路径* @return 文件ID*/private Long uploadFile(String filePath) {try {File file = new File(filePath);String fileName = file.getName();byte[] content = IoUtil.readBytes(new FileInputStream(file));MultipartFile multipartFile = new MockMultipartFile(fileName, fileName, "multipart/form-data;", content);Attach attach = attachClient.uploadFile(multipartFile);return attach.getId();} catch (Exception e) {log.error("上传文件{}异常,错误信息{}", filePath, e.getMessage(), e);throw new ServiceException("上传文件异常:" + e.getMessage());}}}

生成的pdf文件中的表格内容需要根据具体的文字内容大小适配:

Table customerTable = new Table(UnitValue.createPercentArray(new float[]{1.5F, 3, 3, 3, 3, 3}))// 表格宽度.setWidth(500)// 字体大小.setFontSize(9)//设置字体.setFont(getChineseFont())// 确保PDF页面整体性,避免被分页符打断.setKeepTogether(true)// 下方添加30单位空白区域.setMarginBottom(30);
new float[]{1.5F, 3, 3, 3, 3, 3}

此处定义的大小即为表格的占比大小,数量要与列数相同,本举例中为6列:

private void addTableRowHeader(Table customerTable) throws IOException {List<String> headers = new ArrayList<>();headers.add("序号");headers.add("类别");headers.add("项目");headers.add("金额");headers.add("结果");headers.add("备注");addTableHeader(customerTable, headers);}private void addTableRow(Table customerTable, List<ReconBillDetail> detailList) {if (CollectionUtil.isNotEmpty(detailList)) {int index = 1;for (ReconBillDetail detail : detailList) {addTableRow(customerTable,Integer.toString(index++),detail.getAccountCategoryName() == null ? "" : detail.getAccountCategoryName(),detail.getAccountItemName() == null ? "" : detail.getAccountItemName(),formatValue(detail.getCalAmount()),detail.getCheckResult() == null ? "" : detail.getCheckResult(),detail.getRemark() == null ? "" : detail.getRemark());}}}

http://www.lryc.cn/news/582487.html

相关文章:

  • Android framework修改解决偶发开机时有两个launcher入口的情况
  • Prompt Injection Attack to Tool Selection in LLM Agents
  • 论文略读:Prefix-Tuning: Optimizing Continuous Prompts for Generation
  • C++11标准库算法:深入理解std::find, std::find_if与std::find_if_not
  • Python中os.path和pathlib模块路径操作函数汇总
  • react的条件渲染【简约风5min】
  • C#使用Semantic Kernel实现Embedding功能
  • 【知足常乐ai笔记】机器人强化学习
  • TVS管工作原理是什么?主要的应用场景都有哪些?
  • MySQL数据库访问(C/C++)
  • 赛博威破解快消品渠道营销三重困局,助力企业实现“活动即战力”
  • 小米YU7预售现象深度解析:智能电动汽车的下一个范式革命
  • 内容页模板表格显示不全的问题处理
  • IP 能ping通,服务器是否开机?
  • 第8章:应用层协议HTTP、SDN软件定义网络、组播技术、QoS
  • 【快手】数据挖掘面试题0002:求某地铁站每日客流量,乘地铁经过、进出站人都包括在内
  • Tourism Management and Technology Economy,旅游管理与技术经济知网期刊
  • Oracle 存储过程、函数与触发器
  • 【OceanBase诊断调优】—— 执行计划显示分区 PARTITIONS[P0SP9] 如何查询是哪个分区?
  • 数据结构与算法:博弈类问题
  • 服务器经常出现蓝屏是什么原因导致的?如何排查和修复?
  • node.js中yarn、npm、cnpm详解
  • npm : 无法加载文件 D:\Node\npm.ps1,因为在此系统上禁止运行脚本。
  • 【QT】-隐式转换 explicit用法
  • React18+TypeScript状态管理最佳实践
  • 说说SpringBoot常用的注解?
  • 【Nginx】Nginx代理WebSocket
  • Ollama+OpenWebUI 0.42+0.3.35 最新版一键安装教程,解决手动更新失败问题
  • kafka如何让消息均匀的写入到每个partition
  • OpenWebUI(5)源码学习-后端socket通信模块