diff --git a/pom.xml b/pom.xml index ebf2814ed..b0b38e0bc 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,9 @@ 7.0.1 + 5.5.13.4 + 5.2.0 + 3.2.2 3.2.2 @@ -328,6 +331,17 @@ ${fastjson.version} + + com.itextpdf + itext-asian + ${itextpdf.asian.version} + + + com.itextpdf + itextpdf + ${itextpdf.version} + + org.dromara ruoyi-system diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 2930fd0b0..5b855aaf2 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -34,6 +34,7 @@ ruoyi-common-tenant ruoyi-common-websocket ruoyi-common-sse + ruoyi-common-pdf ruoyi-common diff --git a/ruoyi-common/ruoyi-common-bom/pom.xml b/ruoyi-common/ruoyi-common-bom/pom.xml index dbc5f1c2b..e19b53761 100644 --- a/ruoyi-common/ruoyi-common-bom/pom.xml +++ b/ruoyi-common/ruoyi-common-bom/pom.xml @@ -82,6 +82,12 @@ ${revision} + + org.dromara + ruoyi-common-pdf + ${revision} + + org.dromara diff --git a/ruoyi-common/ruoyi-common-pdf/pom.xml b/ruoyi-common/ruoyi-common-pdf/pom.xml new file mode 100644 index 000000000..3bad57399 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pdf/pom.xml @@ -0,0 +1,41 @@ + + + + org.dromara + ruoyi-common + ${revision} + + 4.0.0 + + ruoyi-common-pdf + + + ruoyi-common-pdf pdf模块 + + + + + org.dromara + ruoyi-common-core + + + + com.itextpdf + itext-asian + + + + com.itextpdf + itextpdf + + + + org.dromara + ruoyi-common-json + + + + + diff --git a/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/domain/PdfEntity.java b/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/domain/PdfEntity.java new file mode 100644 index 000000000..5cdc4abf1 --- /dev/null +++ b/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/domain/PdfEntity.java @@ -0,0 +1,27 @@ +package org.dromara.common.pdf.domain; + + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * Pdf基类 + * + * @author AprilWind + */ +@Data +public class PdfEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * PDF模板路径 + */ + @JsonIgnore + private String templatePath; + +} diff --git a/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/utils/PdfUtils.java b/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/utils/PdfUtils.java new file mode 100644 index 000000000..e11b6923e --- /dev/null +++ b/ruoyi-common/ruoyi-common-pdf/src/main/java/org/dromara/common/pdf/utils/PdfUtils.java @@ -0,0 +1,102 @@ +package org.dromara.common.pdf.utils; + + +import com.itextpdf.text.pdf.AcroFields; +import com.itextpdf.text.pdf.BaseFont; +import com.itextpdf.text.pdf.PdfReader; +import com.itextpdf.text.pdf.PdfStamper; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.pdf.domain.PdfEntity; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Map; + +/** + * PDF操作工具类 + * + * @author AprilWind + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@Slf4j +public class PdfUtils { + + /** + * 根据继承自 PdfEntity 的实体对象,填充 PDF 模板 + * + * @param 继承自 PdfEntity 的实体类型 + * @param person 传入的实体对象,必须是 PdfEntity 或其子类的实例 + * @return 填充后的 PDF 输入流 + * @throws IllegalArgumentException 如果实体类中的模板路径为空或无效 + */ + public static InputStream fillPdfTemplateByAlias(T person) { + String templatePath = person.getTemplatePath(); + if (StringUtils.isBlank(templatePath)) { + throw new IllegalArgumentException("模板路径未指定或无效"); + } + Map fieldValues = JsonUtils.parseObject(JsonUtils.toJsonString(person), Map.class); + return fillPdfTemplate(templatePath, fieldValues); + } + + /** + * 填充 PDF 模板 + * + * @param templatePath 模板文件路径 + * @param fieldValues 表单字段及其值的映射 + * @return 填充后的 PDF 输入流 + */ + private static InputStream fillPdfTemplate(String templatePath, Map fieldValues) { + try ( + InputStream templateStream = PdfUtils.class.getClassLoader().getResourceAsStream(templatePath); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream() + ) { + if (templateStream == null) { + throw new FileNotFoundException("PDF 模板文件未找到:" + templatePath); + } + + // 读取 PDF 模板 + PdfReader reader = new PdfReader(templateStream); + PdfStamper stamper = new PdfStamper(reader, outputStream); + + try { + // 获取表单字段 + AcroFields form = stamper.getAcroFields(); + + // 添加中文字体支持(可选) + BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + form.addSubstitutionFont(bf); + + // 填充表单字段 + for (Map.Entry entry : fieldValues.entrySet()) { + String fieldName = entry.getKey(); + String fieldValue = entry.getValue(); + if (form.getField(fieldName) != null) { + form.setField(fieldName, fieldValue); + } else { + System.err.printf("PDF模板填充失败:未找到字段 [%s],对应值为 [%s]%n", fieldName, fieldValue); + } + } + + // 设置表单为不可编辑 + stamper.setFormFlattening(true); + } finally { + // 确保 PDF 相关资源关闭 + stamper.close(); + reader.close(); + } + + // 返回填充后的 PDF 文件流 + return new ByteArrayInputStream(outputStream.toByteArray()); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + +} diff --git a/ruoyi-common/ruoyi-common-pdf/src/main/resources/templates/sample_template.pdf b/ruoyi-common/ruoyi-common-pdf/src/main/resources/templates/sample_template.pdf new file mode 100644 index 000000000..0e6321a00 Binary files /dev/null and b/ruoyi-common/ruoyi-common-pdf/src/main/resources/templates/sample_template.pdf differ diff --git a/ruoyi-modules/ruoyi-system/pom.xml b/ruoyi-modules/ruoyi-system/pom.xml index 0fc6d5513..556e395ec 100644 --- a/ruoyi-modules/ruoyi-system/pom.xml +++ b/ruoyi-modules/ruoyi-system/pom.xml @@ -48,6 +48,11 @@ ruoyi-common-log + + org.dromara + ruoyi-common-pdf + + org.dromara diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/AhhrfsPdfController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/AhhrfsPdfController.java new file mode 100644 index 000000000..1312ea8f9 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/AhhrfsPdfController.java @@ -0,0 +1,90 @@ +package org.dromara.system.controller.system; + +import cn.hutool.core.io.IoUtil; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; +import org.dromara.common.pdf.utils.PdfUtils; +import org.dromara.common.web.core.BaseController; +import org.dromara.system.domain.vo.AhhrfsPdfSampleVo; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.InputStream; + +/** + * 下载PDF + * + * @author AprilWind + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/ahhrfs/pdf") +public class AhhrfsPdfController extends BaseController { + + /** + * 下载PDF对象 + */ + @GetMapping("/download/{id}") + public void download(@PathVariable Long id, HttpServletResponse response) { + AhhrfsPdfSampleVo sampleVo = new AhhrfsPdfSampleVo(); + sampleVo.setChdActNme("测试数据123"); + sampleVo.setChdActNo("123213123123"); + sampleVo.setSvsActNo("42131231231231"); + try (InputStream inputStream = PdfUtils.fillPdfTemplateByAlias(sampleVo)) { + int available = inputStream.available(); + IoUtil.copy(inputStream, response.getOutputStream(), available); + response.setContentLength(available); + } catch (Exception e) { + throw new ServiceException(e.getMessage()); + } + } + + /** + * 前端 multipart/form-data 请求 + */ + @PostMapping("/download") + public void download(AhhrfsPdfSampleVo sampleVo, HttpServletResponse response) { +// AhhrfsPdfSampleVo sampleVo = new AhhrfsPdfSampleVo(); +// sampleVo.setChdActNme("测试数据123"); +// sampleVo.setChdActNo("123213123123"); +// sampleVo.setSvsActNo("42131231231231"); + String fileName = StringUtils.isBlank(sampleVo.getChdActNme()) ? "default.pdf" : sampleVo.getChdActNme(); + try (InputStream inputStream = PdfUtils.fillPdfTemplateByAlias(sampleVo)) { + // 设置响应头 + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + FileUtils.percentEncode(fileName) + "\""); + // 将 PDF 数据写入响应流 + IoUtil.copy(inputStream, response.getOutputStream(), inputStream.available()); + response.flushBuffer(); + } catch (Exception e) { + throw new ServiceException("PDF 下载失败: " + e.getMessage()); + } + } + + /** + * 前端 application/json 请求 + */ + @PostMapping("/download2") + public void download2(@RequestBody AhhrfsPdfSampleVo sampleVo, HttpServletResponse response) { +// AhhrfsPdfSampleVo sampleVo = new AhhrfsPdfSampleVo(); +// sampleVo.setChdActNme("测试数据123"); +// sampleVo.setChdActNo("123213123123"); +// sampleVo.setSvsActNo("42131231231231"); + String fileName = StringUtils.isBlank(sampleVo.getChdActNme()) ? "default.pdf" : sampleVo.getChdActNme(); + try (InputStream inputStream = PdfUtils.fillPdfTemplateByAlias(sampleVo)) { + // 设置响应头 + response.setContentType("application/pdf"); + response.setHeader("Content-Disposition", "attachment; filename=\"" + FileUtils.percentEncode(fileName) + "\""); + // 将 PDF 数据写入响应流 + IoUtil.copy(inputStream, response.getOutputStream(), inputStream.available()); + response.flushBuffer(); + } catch (Exception e) { + throw new ServiceException("PDF 下载失败: " + e.getMessage()); + } + } + +} diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/AhhrfsPdfSampleVo.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/AhhrfsPdfSampleVo.java new file mode 100644 index 000000000..0ba744768 --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/AhhrfsPdfSampleVo.java @@ -0,0 +1,36 @@ +package org.dromara.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import lombok.Data; +import org.dromara.common.pdf.domain.PdfEntity; + +/** + * 示例PDF + * + * @author AprilWind + */ +@Data +@ExcelIgnoreUnannotated +public class AhhrfsPdfSampleVo extends PdfEntity { + + /** + * 监管账户 + */ + private String svsActNo; + + /** + * 子账户 + */ + private String chdActNo; + + /** + * 子账户名称 + */ + private String chdActNme; + + @Override + public String getTemplatePath() { + return "templates/sample_template.pdf"; + } + +}