生成PDF文件(基于 iText PDF )

一、准备工作

pom依赖

XML 复制代码
        <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>

二、代码处理

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

java 复制代码
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;

} 

生成代码:

java 复制代码
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;
    @Resource
    private IReconBillQueryService reconBillQueryService;
    @Resource
    private IProcessClient processClient;
    @Resource
    private IAttachClient attachClient;

    @RabbitListener(queues = {MessageKeyConstant.SETTLEBILL_REVIEW_SUCCESS_QUE})
    @RabbitHandler
    public 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);
            // 成功处理,发送 ACK
            channel.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文件中的表格内容需要根据具体的文字内容大小适配:

java 复制代码
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列:

java 复制代码
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());
            }
        }
    }
相关推荐
C.果栗子6 小时前
Blob格式的PDF文件调用打印,浏览器文件打印(兼容)
前端·javascript·pdf
ruleslol9 小时前
SpringCloud03-Eureka02-搭建Eureka服务
spring cloud·eureka
Highcharts.js11 小时前
Highcharts常见问题解析(5):如何将多个图表导出到同一张图片或 PDF?
pdf·highcharts
脸大是真的好~14 小时前
尚硅谷 SpringCloud 01 分布式概念-工程创建-nacos安装-nacos服务注册与发现-远程调用-负载均衡注解版-配置中心-动态刷新-环境隔离
分布式·spring·spring cloud
麦烤楽鸡翅15 小时前
pdf(攻防世界)
网络安全·pdf·ctf·misc·杂项·攻防世界·信息竞赛
小坏讲微服务15 小时前
Spring Cloud Alibaba 2025.0.0 整合 ELK 实现日志
运维·后端·elk·spring cloud·jenkins
Less is moree16 小时前
PDF无法打印怎么解决?
pdf
小坏讲微服务19 小时前
整合Spring Cloud Alibaba与Gateway实现跨域的解决方案
java·开发语言·后端·spring cloud·云原生·gateway
lijun_xiao200921 小时前
Python-将身份证正反面图片-生成PDF
pdf
A尘埃1 天前
项目七:PDF智能公式与计算(金融机构信贷报告自动解析与风险评估)
pdf