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";
+ }
+
+}