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