一、准备工作
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());
}
}
}