diff --git a/extLib/poi-ooxml-5.4.1.jar b/extLib/poi-ooxml-5.4.1.jar
new file mode 100644
index 0000000..115897f
Binary files /dev/null and b/extLib/poi-ooxml-5.4.1.jar differ
diff --git a/seeyon/WEB-INF/cfgHome/component/esign/spring/spring-bean.xml b/seeyon/WEB-INF/cfgHome/component/esign/spring/spring-bean.xml
index c64e43e..415b5fc 100644
--- a/seeyon/WEB-INF/cfgHome/component/esign/spring/spring-bean.xml
+++ b/seeyon/WEB-INF/cfgHome/component/esign/spring/spring-bean.xml
@@ -22,6 +22,7 @@
+
diff --git a/src/main/java/com/seeyon/apps/esign/node/A4CheckNode.java b/src/main/java/com/seeyon/apps/esign/node/A4CheckNode.java
new file mode 100644
index 0000000..bdcf7c9
--- /dev/null
+++ b/src/main/java/com/seeyon/apps/esign/node/A4CheckNode.java
@@ -0,0 +1,76 @@
+package com.seeyon.apps.esign.node;
+
+import com.seeyon.apps.common.workflow.node.ACommonSuperNode;
+import com.seeyon.apps.esign.constants.EsignConfigConstants;
+import com.seeyon.apps.esign.constants.FlowFormFieldConstants;
+import com.seeyon.apps.esign.utils.DocumentA4Checker;
+import com.seeyon.apps.esign.utils.FileUtil;
+import com.seeyon.apps.ext.workflow.vo.FieldDataVo;
+import com.seeyon.apps.ext.workflow.vo.FormDataVo;
+import com.seeyon.apps.ext.workflow.vo.SuperNodeContext;
+import com.seeyon.cap4.form.bean.FormDataMasterBean;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.util.List;
+
+
+public class A4CheckNode extends ACommonSuperNode {
+
+ private static final Log log = LogFactory.getLog(A4CheckNode.class);
+
+ @Override
+ public String getPluginId() {
+ return EsignConfigConstants.getPluginId();
+ }
+
+ @Override
+ public String getFormParse() {
+ return "";
+ }
+
+ @Override
+ public SuperNodeContext proceed(String s, FormDataVo formDataVo, FormDataMasterBean formDataMasterBean) throws Exception {
+ SuperNodeContext context = new SuperNodeContext();
+ context.setNeedSave(true);
+ log.info("进入文件A4格式校验超级节点");
+ try {
+ FieldDataVo fieldData = formDataVo.getFieldData(FlowFormFieldConstants.CONTRACT_APPROVAL_ATTACHMENT);
+ String attachmentId = fieldData.getStringValue();
+ if (attachmentId == null) throw new RuntimeException("合同文件不能为空");
+ String tempDir = System.getProperty("java.io.tmpdir");
+ File file = null;
+ FileUtil fileUtil = new FileUtil();
+ List paths = fileUtil.fieldFileDownload(Long.parseLong(attachmentId), tempDir + File.separator + "oafile" + File.separator);
+ for (String path : paths) {
+ file = new File(path);
+ boolean firstPageA4 = DocumentA4Checker.isFirstPageA4(file);
+ if (!firstPageA4) {
+ String errMsg = "上传的附件中" + file.getName() +"非标准A4格式的,请修改后再上传(标准A4为21:29)";
+ formDataVo.getNewFieldDataMap().put("合同审批附件格式校验结果", errMsg);
+ return context.back(errMsg);
+ }
+ file.delete();
+ }
+
+ return context.success("文件A4格式校验通过");
+ } catch (Exception e) {
+ log.error("校验产生错误", e);
+ e.printStackTrace();
+ context.setErrMsg("校验产生错误: " + e.getMessage());
+ return context.back("校验产生错误: " + e.getMessage());
+ }
+ }
+
+
+ @Override
+ public String getNodeId() {
+ return "nd_20260512";
+ }
+
+ @Override
+ public String getNodeName() {
+ return "A4格式校验节点";
+ }
+}
diff --git a/src/main/java/com/seeyon/apps/esign/utils/DocumentA4Checker.java b/src/main/java/com/seeyon/apps/esign/utils/DocumentA4Checker.java
new file mode 100644
index 0000000..5dc0b84
--- /dev/null
+++ b/src/main/java/com/seeyon/apps/esign/utils/DocumentA4Checker.java
@@ -0,0 +1,135 @@
+package com.seeyon.apps.esign.utils;
+
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.poi.hwpf.HWPFDocument;
+import org.apache.poi.hwpf.usermodel.Section;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPageSz;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STPageOrientation;
+
+import java.io.*;
+import java.util.Optional;
+
+public class DocumentA4Checker {
+
+ // A4标准磅值 宽595 高842,允许误差5pt
+ private static final float A4_WIDTH = 595.0f;
+ private static final float A4_HEIGHT = 842.0f;
+ private static final float TOLERANCE = 5.0f;
+ // twips 转 pt: /20
+ private static final float TWIPS_TO_PT = 20.0f;
+
+ public static boolean isFirstPageA4(File file) {
+ if (file == null || !file.exists() || !file.isFile()) {
+ return false;
+ }
+
+ String fileName = Optional.ofNullable(file.getName()).orElse("");
+ String suffix = getFileSuffix(fileName).toLowerCase();
+
+ try {
+ switch (suffix) {
+ case "pdf":
+ try (InputStream in = new FileInputStream(file)) {
+ return checkPdfFirstPageA4(in);
+ }
+ case "doc":
+ // 先尝试 doc 解析,若文件实际为 OOXML 则回退到 docx
+ try (InputStream in = new FileInputStream(file)) {
+ return checkDocFirstPageA4(in);
+ } catch (IllegalArgumentException e) {
+ try (InputStream in = new FileInputStream(file)) {
+ return isDocxA4Portrait(in);
+ }
+ }
+ case "docx":
+ try (InputStream in = new FileInputStream(file)) {
+ return isDocxA4Portrait(in);
+ }
+ default:
+ return false;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 校验 PDF 第一页是否竖版 A4
+ */
+ private static boolean checkPdfFirstPageA4(InputStream in) throws IOException {
+ try (PDDocument document = PDDocument.load(in)) {
+ if (document.getNumberOfPages() == 0) return false;
+
+ PDPage page = document.getPage(0);
+ float w = page.getMediaBox().getWidth();
+ float h = page.getMediaBox().getHeight();
+
+ return Math.abs(w - A4_WIDTH) <= TOLERANCE
+ && Math.abs(h - A4_HEIGHT) <= TOLERANCE;
+ }
+ }
+
+ /**
+ * 校验 doc 第一页是否竖版 A4
+ */
+ private static boolean checkDocFirstPageA4(InputStream in) throws IOException {
+ try (HWPFDocument doc = new HWPFDocument(in)) {
+ Section section = doc.getRange().getSection(0);
+
+ int widthTwips = section.getPageWidth();
+ int heightTwips = section.getPageHeight();
+
+ float widthPt = widthTwips / 20f;
+ float heightPt = heightTwips / 20f;
+
+ return Math.abs(widthPt - A4_WIDTH) <= TOLERANCE
+ && Math.abs(heightPt - A4_HEIGHT) <= TOLERANCE;
+ }
+ }
+
+ /**
+ * 校验 docx 第一页是否竖版 A4(POI 5.x 兼容版)
+ */
+ public static boolean isDocxA4Portrait(InputStream in) throws IOException {
+ try (XWPFDocument docx = new XWPFDocument(in)) {
+ CTSectPr sectPr = docx.getDocument().getBody().getSectPr();
+ if (sectPr == null || sectPr.getPgSz() == null) {
+ // 没有设置页面大小 → 默认认为是A4(Word默认行为)
+ return true;
+ }
+
+ CTPageSz pgSz = sectPr.getPgSz();
+
+ // 判断是否横向
+ if (pgSz.getOrient() != null) {
+ STPageOrientation.Enum orient = pgSz.getOrient();
+ if (STPageOrientation.LANDSCAPE.equals(orient)) {
+ return false;
+ }
+ }
+
+ // 兼容 BigInteger 和 BigDecimal 两种实现
+ Number wTwips = (Number) pgSz.getW();
+ Number hTwips = (Number) pgSz.getH();
+
+ if (wTwips == null || hTwips == null) {
+ return false;
+ }
+
+ float wPt = wTwips.floatValue() / TWIPS_TO_PT;
+ float hPt = hTwips.floatValue() / TWIPS_TO_PT;
+
+ return Math.abs(wPt - A4_WIDTH) <= TOLERANCE
+ && Math.abs(hPt - A4_HEIGHT) <= TOLERANCE;
+ }
+ }
+
+ private static String getFileSuffix(String fileName) {
+ int idx = fileName.lastIndexOf('.');
+ return idx == -1 ? "" : fileName.substring(idx + 1);
+ }
+}
\ No newline at end of file