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