diff --git a/ruoyi-bussiness/pom.xml b/ruoyi-bussiness/pom.xml
index fb4b41d..1d774d4 100644
--- a/ruoyi-bussiness/pom.xml
+++ b/ruoyi-bussiness/pom.xml
@@ -116,6 +116,12 @@
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
+
+
+ com.github.librepdf
+ openpdf
+ 1.3.30
+
\ No newline at end of file
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyInfoController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyInfoController.java
index d9d9315..001a361 100644
--- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyInfoController.java
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/cms/CmsPolicyInfoController.java
@@ -3,6 +3,7 @@ package com.ruoyi.cms.controller.cms;
import com.ruoyi.cms.domain.policy.PolicyInfo;
import com.ruoyi.cms.domain.policy.PolicyInfoQuery;
import com.ruoyi.cms.service.policy.IPolicyInfoService;
+import com.ruoyi.cms.util.pdf.OpenPdfPolicyUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
@@ -17,6 +18,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
@@ -168,4 +170,13 @@ public class CmsPolicyInfoController extends BaseController {
return error("文件上传失败:" + e.getMessage());
}
}
+
+ @GetMapping("/exportUtil")
+ public void pdfByUtil(@RequestParam Long id, HttpServletResponse response) throws Exception {
+ PolicyInfo policyInfo=policyInfoService.selectById(id);
+ if (policyInfo == null) {
+ throw new RuntimeException("未查询到对应政策信息");
+ }
+ OpenPdfPolicyUtil.exportPolicyPdf(policyInfo, response);
+ }
}
diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/pdf/OpenPdfPolicyUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/pdf/OpenPdfPolicyUtil.java
new file mode 100644
index 0000000..99a501f
--- /dev/null
+++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/pdf/OpenPdfPolicyUtil.java
@@ -0,0 +1,154 @@
+package com.ruoyi.cms.util.pdf;
+
+import com.lowagie.text.*;
+import com.lowagie.text.pdf.BaseFont;
+import com.lowagie.text.pdf.PdfWriter;
+import com.ruoyi.cms.domain.policy.PolicyInfo;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+
+/**
+ * OpenPDF 政策PDF导出工具
+ * 优化:表格列宽、文本清洗、自动换行、去除多余空行、增加边框区分
+ */
+public class OpenPdfPolicyUtil {
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+ private static BaseFont baseFont;
+
+ static {
+ try {
+ // OpenPDF内置中文字体,无需外部ttf,Linux/Windows通用
+ baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
+ } catch (Exception e) {
+ throw new RuntimeException("初始化PDF宋体字体失败", e);
+ }
+ }
+
+ public static void exportPolicyPdf(PolicyInfo policy, HttpServletResponse response) throws Exception {
+ String rawName = getCleanText(policy.getZcmc());
+ String fileName = URLEncoder.encode(rawName + ".pdf", "UTF-8");
+ response.setContentType("application/pdf");
+ response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+ response.setHeader("Cache-Control", "no-cache");
+
+ try (OutputStream out = response.getOutputStream();
+ Document document = new Document(PageSize.A4, 40, 40, 60, 60)) {
+
+ PdfWriter writer = PdfWriter.getInstance(document, out);
+ document.open();
+
+ // ========== 1、文档主标题 ==========
+ Font titleFont = new Font(baseFont, 20, Font.BOLD);
+ Paragraph titlePara = new Paragraph(getCleanText(policy.getZcmc()), titleFont);
+ titlePara.setAlignment(Element.ALIGN_CENTER);
+ titlePara.setSpacingAfter(25);
+ document.add(titlePara);
+
+ // ========== 2、基础信息表格(调整列宽20/30/20/30,加宽标题列) ==========
+ Font tableHeadFont = new Font(baseFont, 12, Font.BOLD);
+ Font tableValFont = new Font(baseFont, 12);
+ Table infoTable = new Table(4);
+ float[] colWidth = new float[]{20, 30, 20, 30};
+ infoTable.setWidths(colWidth);
+ infoTable.setWidth(100);
+ infoTable.setBorderWidth(0.6f); // 增加边框,区分单元格
+
+ // 第一行
+ infoTable.addCell(buildCell(getCleanText("政策类型"), tableHeadFont));
+ infoTable.addCell(buildCell(getCleanText(policy.getZclx()), tableValFont));
+ infoTable.addCell(buildCell(getCleanText("政策级别"), tableHeadFont));
+ infoTable.addCell(buildCell(getCleanText(policy.getZcLevel()), tableValFont));
+
+ // 第二行
+ infoTable.addCell(buildCell(getCleanText("发文单位"), tableHeadFont));
+ infoTable.addCell(buildCell(getCleanText(policy.getSourceUnit()), tableValFont));
+ infoTable.addCell(buildCell(getCleanText("受理单位"), tableHeadFont));
+ infoTable.addCell(buildCell(getCleanText(policy.getAcceptUnit()), tableValFont));
+
+ // 第三行
+ String publishDate = policy.getPublishTime() == null ? "无" : DATE_FORMAT.format(policy.getPublishTime());
+ infoTable.addCell(buildCell(getCleanText("发文时间"), tableHeadFont));
+ infoTable.addCell(buildCell(publishDate, tableValFont));
+ infoTable.addCell(buildCell(getCleanText("浏览数量"), tableHeadFont));
+ infoTable.addCell(buildCell(String.valueOf(policy.getViewNum()), tableValFont));
+
+ document.add(infoTable);
+ document.add(new Paragraph(" "));
+
+ // ========== 3、政策四大章节 ==========
+ Font sectionTitleFont = new Font(baseFont, 14, Font.BOLD);
+ Font contentFont = new Font(baseFont, 11);
+ float lineMulti = 1.45f;
+
+ addSection(document, "一、政策内容", policy.getZcContent(), sectionTitleFont, contentFont, lineMulti);
+ addSection(document, "二、补贴标准", policy.getSubsidyStandard(), sectionTitleFont, contentFont, lineMulti);
+ addSection(document, "三、申报条件", policy.getApplyCondition(), sectionTitleFont, contentFont, lineMulti);
+ addSection(document, "四、经办渠道", policy.getHandleChannel(), sectionTitleFont, contentFont, lineMulti);
+
+ /*// ========== 4、附件信息 ==========
+ Paragraph fileTitlePara = new Paragraph("五、附件文件信息", sectionTitleFont);
+ fileTitlePara.setSpacingBefore(18);
+ fileTitlePara.setSpacingAfter(8);
+ document.add(fileTitlePara);
+
+ String fileInfo = "文件名称:" + getCleanText(policy.getFileName()) + "\n文件地址:" + getCleanText(policy.getFileUrl());
+ Paragraph filePara = new Paragraph(fileInfo, contentFont);
+ filePara.setMultipliedLeading(lineMulti);
+ filePara.setIndentationLeft(5);
+ document.add(filePara);*/
+
+ document.close();
+ writer.flush();
+ out.flush();
+ }
+ }
+
+ /**
+ * 构建单元格:移除多余\n换行,仅靠段落间距,消除大量空白行
+ */
+ private static Cell buildCell(String text, Font font) {
+ Cell cell = new Cell();
+ Paragraph cellPara = new Paragraph(text, font);
+ cellPara.setSpacingBefore(4);
+ cellPara.setSpacingAfter(4);
+ cell.add(cellPara);
+ return cell;
+ }
+
+ /**
+ * 新增:统一处理章节标题+正文,自动清洗文本、缩进、行间距
+ */
+ private static void addSection(Document doc, String title, String rawContent, Font titleFont, Font contentFont, float lineHeight) throws DocumentException {
+ Paragraph titlePara = new Paragraph(title, titleFont);
+ titlePara.setSpacingBefore(14);
+ titlePara.setSpacingAfter(7);
+ doc.add(titlePara);
+
+ String cleanContent = getCleanText(rawContent);
+ Paragraph contentPara = new Paragraph(cleanContent, contentFont);
+ contentPara.setMultipliedLeading(lineHeight);
+ contentPara.setIndentationLeft(5);
+ doc.add(contentPara);
+ }
+
+ /**
+ * 核心文本清洗:清除多余换行、空格、统一全角符号,解决公文排版错乱
+ */
+ private static String getCleanText(String str) {
+ if (str == null || str.trim().isEmpty()) {
+ return "无";
+ }
+ // 去除首尾空白
+ String temp = str.trim();
+ // 多个换行替换为单个换行
+ temp = temp.replaceAll("\\s*\n\\s*", "\n");
+ // 多个空格压缩为单个
+ temp = temp.replaceAll(" +", " ");
+ // 全角顿号统一(可选,按需开启)
+ temp = temp.replace("、", "、");
+ return temp;
+ }
+}
\ No newline at end of file