From cf3ba9b4fc145ced8c62365afd1ae2a5cf5f7bfd Mon Sep 17 00:00:00 2001
From: "15881625488@163.com" <15881625488@163.com>
Date: Thu, 8 Jan 2026 19:56:47 +0800
Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9Apdf=E5=AF=BC=E5=87=BA=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
hxhq-modules/hxhq-system/pom.xml | 22 ++
.../main/java/com/hxhq/business/utils/PdfUtil.java | 388 +++++++++++++++++++++
2 files changed, 410 insertions(+)
create mode 100644 hxhq-modules/hxhq-system/src/main/java/com/hxhq/business/utils/PdfUtil.java
diff --git a/hxhq-modules/hxhq-system/pom.xml b/hxhq-modules/hxhq-system/pom.xml
index 24218da..47780a3 100644
--- a/hxhq-modules/hxhq-system/pom.xml
+++ b/hxhq-modules/hxhq-system/pom.xml
@@ -77,6 +77,28 @@
spring-boot-starter-test
test
+
+
+
+ com.itextpdf
+ itextpdf
+ 5.5.11
+
+
+ com.itextpdf.tool
+ xmlworker
+ 5.5.11
+
+
+
+ com.itextpdf
+ itext-asian
+ 5.2.0
+
+
+ org.projectlombok
+ lombok
+
diff --git a/hxhq-modules/hxhq-system/src/main/java/com/hxhq/business/utils/PdfUtil.java b/hxhq-modules/hxhq-system/src/main/java/com/hxhq/business/utils/PdfUtil.java
new file mode 100644
index 0000000..19fab9a
--- /dev/null
+++ b/hxhq-modules/hxhq-system/src/main/java/com/hxhq/business/utils/PdfUtil.java
@@ -0,0 +1,388 @@
+package com.hxhq.business.utils;
+
+import com.hxhq.common.core.utils.DateUtils;
+import com.itextpdf.text.*;
+import com.itextpdf.text.Font;
+import com.itextpdf.text.pdf.*;
+import com.itextpdf.text.pdf.draw.LineSeparator;
+import lombok.SneakyThrows;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.List;
+
+/**
+ * OpenPDF工具类 - 支持保存到本地文件
+ */
+@Component
+public class PdfUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(PdfUtil.class);
+
+ public static void main(String[] args) {
+ String fileDir="d:/";
+ exportForm("张三 2025-12-12 14:52:52",fileDir);
+ exportTable("张三 2025-12-12 14:52:52",fileDir);
+ }
+
+ /**
+ * 导出form
+ * @param qm
+ * @param fileDir
+ * @return
+ */
+ public static String exportForm(String qm,String fileDir) {
+ Document document = null;
+ FileOutputStream fos = null;
+ String filePath = "";
+ try {
+ // 1. 生成文件名
+ String fileName = generateFileName();
+
+ // 2. 确保目录存在
+ File dir = new File(fileDir);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+ // 3. 完整文件路径
+ filePath = Paths.get(fileDir, fileName).toString();
+
+ // 4. 创建PDF文档
+ document = new Document(PageSize.A4);
+ fos = new FileOutputStream(filePath);
+ PdfWriter writer = PdfWriter.getInstance(document, fos);
+
+ // 使用完整的页脚处理器
+ writer.setPageEvent(new CompletePdfFooter());
+
+ // 5. 设置PDF属性
+ document.addTitle("华西海圻");
+ document.addAuthor("华西海圻");
+ document.addCreator("华西海圻");
+ document.addCreationDate();
+ document.open();
+
+ // 6. 设置中文字体
+ BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ Font titleFont = new Font(bfChinese, 16, Font.BOLD);
+
+ // 7. 添加签名
+ Paragraph title = new Paragraph(qm, titleFont);
+ title.setAlignment(Element.ALIGN_CENTER);
+ title.setSpacingAfter(20);
+ document.add(title);
+
+ //横线
+ LineSeparator line = new LineSeparator();
+ line.setLineWidth(0.5f);
+ // 设置100%宽度
+ line.setPercentage(100f); // 百分比宽度
+ // 设置对齐方式
+ line.setAlignment(Element.ALIGN_CENTER); // Element.ALIGN_CENTER, Element.ALIGN_LEFT等
+ // 设置颜色
+ line.setLineColor(BaseColor.BLACK);
+ // 添加到文档
+ document.add(line);
+
+ Map formData = new LinkedHashMap<>();
+ formData.put("姓名","张三");
+ formData.put("性别", "男");
+ formData.put("手机","15882062878");
+
+ // 生成table
+ addFormTableColumns(document, formData,3);
+ log.info("生成成功:{}", filePath);
+ } catch (Exception e) {
+ log.error("生成失败", e);
+ throw new RuntimeException("生成失败: " + e.getMessage());
+ } finally {
+ if (document != null) {
+ document.close();
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ log.error("关闭文件流失败", e);
+ }
+ }
+ }
+ return filePath;
+ }
+
+ /**
+ * 导出table
+ * @param qm
+ * @param fileDir
+ * @return
+ */
+ public static String exportTable( String qm,String fileDir) {
+ Document document = null;
+ FileOutputStream fos = null;
+ String filePath = "";
+ try {
+ // 1. 生成文件名
+ String fileName = generateFileName();
+
+ // 2. 确保目录存在
+ File dir = new File(fileDir);
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ // 3. 完整文件路径
+ filePath = Paths.get(fileDir, fileName).toString();
+
+ // 4. 创建PDF文档
+ document = new Document(PageSize.A4);
+ fos = new FileOutputStream(filePath);
+ PdfWriter writer = PdfWriter.getInstance(document, fos);
+
+ // 使用完整的页脚处理器
+ writer.setPageEvent(new CompletePdfFooter());
+ // 5. 设置PDF属性
+ document.addTitle("华西海圻");
+ document.addAuthor("华西海圻");
+ document.addCreator("华西海圻");
+ document.addCreationDate();
+
+ document.open();
+
+ // 6. 设置中文字体
+ BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ Font titleFont = new Font(bfChinese, 18, Font.BOLD);
+ Font headerFont = new Font(bfChinese, 12, Font.BOLD);
+ Font contentFont = new Font(bfChinese, 10, Font.NORMAL);
+
+ // 7. 添加签名
+ Paragraph title = new Paragraph(qm, titleFont);
+ title.setAlignment(Element.ALIGN_CENTER);
+ title.setSpacingAfter(20);
+ document.add(title);
+
+ //横线
+ LineSeparator line = new LineSeparator();
+ line.setLineWidth(0.5f);
+ // 设置100%宽度
+ line.setPercentage(100f); // 百分比宽度
+ // 设置对齐方式
+ line.setAlignment(Element.ALIGN_CENTER); // Element.ALIGN_CENTER, Element.ALIGN_LEFT等
+ // 设置颜色
+ line.setLineColor(BaseColor.BLACK);
+ // 添加到文档
+ document.add(line);
+
+ // 9. 创建表格
+ PdfPTable table = new PdfPTable(3);
+ table.setWidthPercentage(100);
+ table.setSpacingBefore(10);
+
+ // 10. 表头
+ String[] headers = {"签名人", "签名意义", "签名时间"};
+ for (String header : headers) {
+ PdfPCell cell = new PdfPCell(new Phrase(header, headerFont));
+ cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ cell.setBackgroundColor(BaseColor.WHITE);
+ cell.setPadding(8);
+ cell.setBorderWidth(1);
+ table.addCell(cell);
+ }
+
+ // 11. 表格数据
+ int rowNum = 0;
+ for (int i = 0; i < 3; i ++) {
+ // 交替行颜色
+ if (rowNum % 2 == 0) {
+ table.getDefaultCell().setBackgroundColor(BaseColor.WHITE);
+ } else {
+ table.getDefaultCell().setBackgroundColor(BaseColor.WHITE);
+ }
+ table.addCell(createCell("1", contentFont));
+ table.addCell(createCell("2", contentFont));
+ table.addCell(createCell("3", contentFont));
+ rowNum++;
+ }
+ document.add(table);
+
+ log.info("生成成功:{}", filePath);
+ } catch (Exception e) {
+ log.error("生成失败", e);
+ throw new RuntimeException("生成失败: " + e.getMessage());
+ } finally {
+ if (document != null) {
+ document.close();
+ }
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (IOException e) {
+ log.error("关闭文件流失败", e);
+ }
+ }
+ }
+ return filePath;
+ }
+
+
+ /**
+ * 创建表单
+ */
+ public static void addFormTableColumns(Document document, Map data, Integer count)
+ throws DocumentException, IOException {
+
+ // 创建3列表格
+ PdfPTable table = new PdfPTable(count);
+ table.setWidthPercentage(100);
+ table.setSpacingBefore(10);
+ table.setSpacingAfter(10);
+
+ // 设置等宽列
+ table.setWidths(new float[]{1, 1, 1});
+
+ // 创建字体
+ BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ Font labelFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);
+ Font valueFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK);
+
+ // 将数据转换为List
+ List> entries = new ArrayList<>(data.entrySet());
+
+ // 每3个字段一组,每组一行
+ for (int i = 0; i < entries.size(); i += count) {
+ // 创建一行
+ for (int j = 0; j < count; j++) {
+ int index = i + j;
+
+ if (index < entries.size()) {
+ Map.Entry entry = entries.get(index);
+
+ // 创建单元格内容
+ Phrase cellContent = new Phrase();
+ cellContent.add(new Chunk(entry.getKey() + ":", labelFont));
+ cellContent.add(new Chunk(com.hxhq.common.core.utils.StringUtils.isBlank(entry.getValue()) ? "" : entry.getValue(), valueFont));
+
+ PdfPCell cell = new PdfPCell(cellContent);
+ cell.setBorder(Rectangle.NO_BORDER); // 无边框
+ cell.setPadding(4);
+ cell.setHorizontalAlignment(Element.ALIGN_LEFT);
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell.setMinimumHeight(20); // 最小高度
+
+ table.addCell(cell);
+ } else {
+ // 添加空单元格保持布局
+ PdfPCell emptyCell = new PdfPCell();
+ emptyCell.setBorder(Rectangle.NO_BORDER);
+ emptyCell.setMinimumHeight(20);
+ table.addCell(emptyCell);
+ }
+ }
+ }
+
+ document.add(table);
+ }
+
+ /**
+ * 创建表格
+ */
+ private static PdfPCell createCell(String content, Font font) {
+ PdfPCell cell = new PdfPCell(new Phrase(content, font));
+ cell.setHorizontalAlignment(Element.ALIGN_CENTER);
+ cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
+ cell.setPadding(6);
+ cell.setMinimumHeight(25);
+ return cell;
+ }
+
+
+ /**
+ * 生成文件名
+ */
+ private static String generateFileName() {
+ Random random= new Random();
+ String timestamp = DateUtils.dateTimeNow("yyyyMMddHHmmss");
+ return String.format("%s_%s.pdf", timestamp,random.nextInt(1000));
+ }
+
+
+ /**
+ * 页脚
+ */
+ public static class CompletePdfFooter extends PdfPageEventHelper {
+
+ private Font footerFont;
+ private PdfTemplate total;
+ private int totalPages = 0;
+
+ public CompletePdfFooter() {
+ try {
+ BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ footerFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.GRAY);
+ } catch (Exception e) {
+ footerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL, BaseColor.GRAY);
+ }
+ }
+
+ @Override
+ public void onOpenDocument(PdfWriter writer, Document document) {
+ total = writer.getDirectContent().createTemplate(100, 16);
+ }
+
+ @SneakyThrows
+ @Override
+ public void onEndPage(PdfWriter writer, Document document) {
+ PdfContentByte cb = writer.getDirectContent();
+
+ int currentPage = writer.getPageNumber();
+ float y = document.bottom() - 20;
+
+ // 在每一页都重新计算,确保位置准确
+ String pageText = "第 " + currentPage + " 页 / 共 ";
+ BaseFont baseFont = footerFont.getBaseFont();
+
+ // 计算页面文本宽度
+ float pageTextWidth = baseFont.getWidthPoint(pageText, footerFont.getSize());
+
+ // 临时用估计的总页数计算位置(等文档关闭后会被替换)
+ String tempTotal = String.valueOf(writer.getPageNumber() + 5); // 估计值
+ float totalWidth = baseFont.getWidthPoint(tempTotal, footerFont.getSize());
+
+ // 计算起始位置
+ float totalTextWidth = pageTextWidth + totalWidth +
+ baseFont.getWidthPoint(" 页", footerFont.getSize());
+ float startX = (document.getPageSize().getWidth() - totalTextWidth) / 2;
+
+ // 写入当前页信息
+ ColumnText.showTextAligned(cb, Element.ALIGN_LEFT,
+ new Phrase(pageText, footerFont), startX, y, 0);
+
+ // 添加总页数模板
+ cb.addTemplate(total, startX + pageTextWidth, y);
+
+ // 写入"页"字
+ ColumnText.showTextAligned(cb, Element.ALIGN_LEFT,
+ new Phrase(" 页", footerFont), startX + pageTextWidth + totalWidth, y, 0);
+
+ }
+
+ @Override
+ public void onCloseDocument(PdfWriter writer, Document document) {
+ totalPages = writer.getPageNumber();
+ String totalPagesStr = String.valueOf(totalPages);
+
+ // 重新创建合适宽度的模板
+ BaseFont baseFont = footerFont.getBaseFont();
+ total.beginText();
+ total.setFontAndSize(baseFont, footerFont.getSize());
+ total.setTextMatrix(0, 0);
+ total.showText(totalPagesStr);
+ total.endText();
+ }
+ }
+
+}
\ No newline at end of file