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