生成PDF文件(基于 iText PDF )

发布于:2025-07-10 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、准备工作

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;
    @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文件中的表格内容需要根据具体的文字内容大小适配:

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());
            }
        }
    }


网站公告

今日签到

点亮在社区的每一天
去签到