소스 검색

质检资料上传pdf检测

lvy 1 주 전
부모
커밋
cd37eac49a

+ 55 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -26,6 +26,9 @@ import org.apache.commons.codec.Charsets;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang3.ObjectUtils;
+import org.apache.pdfbox.io.MemoryUsageSetting;
+import org.apache.pdfbox.pdmodel.*;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.poi.ss.usermodel.WorkbookFactory;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
@@ -1998,6 +2001,24 @@ public class ExcelTabController extends BladeController {
 
             tableFile.setStatus("finished");
             list.add(tableFile);
+            if (!checkPDFSizeAndDpi(tableFile.getDomainPdfUrl())) {
+                // 删除已上传的文件
+                List<String> urls = new ArrayList<>();
+                list.forEach(item -> {
+                    String url = item.getDomainUrl();
+                    if (url ==  null || url.isEmpty()) {
+                        return;
+                    }
+                    int uploadStart = url.indexOf("upload/");
+                    if (uploadStart != -1) {  // 如果找到了以 'upload/' 开始的字符串
+                        url = url.substring(uploadStart);
+                    }
+                    urls.add(url);
+                });
+                newIOSSClient.removeFiles(urls);
+                // 提示用户
+                return R.fail("上传文件的尺寸不规范或分辨率过低,请重新上传");
+            }
         }
 
         tableFileService.saveBatch(list);
@@ -2033,6 +2054,22 @@ public class ExcelTabController extends BladeController {
         return R.status(true);
     }
 
+    /**
+     * 校验pdf文件尺寸和dpi
+     * @param pdfUrl pdf oss 路径
+     * @return  true false
+     */
+    public boolean checkPDFSizeAndDpi(String pdfUrl) {
+        // 使用内存限制设置 10MB 主内存
+        MemoryUsageSetting memUsage = MemoryUsageSetting.setupMixed(10_000_000);
+        try (InputStream ois = CommonUtil.getOSSInputStream(pdfUrl);PDDocument doc = PDDocument.load(ois,memUsage);) {
+            return PDFAnalyzerUtils.checkPdfSizeAndImageDPI(doc);
+        } catch (IOException e) {
+            System.err.println("Error: " + e.getMessage());
+            return false;
+        }
+    }
+
     @GetMapping("/show-buss-tab")
     @ApiOperationSupport(order = 21)
     @ApiOperation(value = "隐藏表单", notes = "隐藏表单")
@@ -4854,6 +4891,24 @@ public class ExcelTabController extends BladeController {
                 } else if (fileExtension.contains("pdf")) {
                     tableFile.setDomainPdfUrl(bladeFile1.getLink());
                 }
+                if (!checkPDFSizeAndDpi(tableFile.getDomainPdfUrl())) {
+                    // 删除已上传的文件
+                    List<String> urls = new ArrayList<>();
+                    fileList.forEach(item -> {
+                        String url = item.getDomainUrl();
+                        if (url ==  null || url.isEmpty()) {
+                            return;
+                        }
+                        int uploadStart = url.indexOf("upload/");
+                        if (uploadStart != -1) {  // 如果找到了以 'upload/' 开始的字符串
+                            url = url.substring(uploadStart);
+                        }
+                        urls.add(url);
+                    });
+                    newIOSSClient.removeFiles(urls);
+                    // 提示用户
+                    return R.fail("上传文件的尺寸不规范或分辨率过低,请重新上传");
+                }
             }
             tableFileService.saveOrUpdateBatch(fileList);
 

+ 218 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PDFAnalyzerUtils.java

@@ -0,0 +1,218 @@
+package org.springblade.manager.utils;
+
+import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDPageTree;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImage;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationText;
+import org.apache.pdfbox.rendering.PDFRenderer;
+import org.apache.pdfbox.util.Matrix;
+
+import javax.imageio.*;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * PDF分析工具类
+ * 目前仅实现一下功能
+ * 1、pdf尺寸检测,比如检测pdf是否为A4尺寸或者其它标准尺寸
+ * 2、检测pdf是否包含图片,如果包含图片,则判断图片dpi是否大于等于300
+ */
+public class PDFAnalyzerUtils {
+
+
+    /**
+     * 检测pdf尺寸是否是A4或者A3,如果所有页面尺寸都是A4或者A3,则返回true
+     * 同时检测pdf中是否包含图片,如果包含图片,则判断图片DPI是否大于等于300, 如果所有图片DPI都大于等于300,则返回true
+     * @param doc pdf文件
+     * @return true表示pdf尺寸是A4或者A3,且所有图片DPI都大于等于300,否则返回false
+     * @throws IOException 读取pdf文件时发生错误
+     */
+    public static boolean checkPdfSizeAndImageDPI(PDDocument doc) throws IOException {
+        PDPageTree pages = doc.getPages();
+        for (PDPage page : pages) {
+            PDRectangle mediaBox = page.getMediaBox();
+            String pageSize = getPageSize(mediaBox);
+            if (!pageSize.equals("A4") && !pageSize.equals("A3")) {
+                return false;
+            }
+            if (!checkImageDPI(page, 300, 2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+
+
+    /**
+     * 获取页面尺寸名称
+     *
+     * @param mediaBox 页面尺寸
+     * @return 页面尺寸名称,如果不是标准尺寸,则返回"Unknown"
+     */
+    private static String getPageSize(PDRectangle mediaBox) {
+        float width = mediaBox.getWidth() / 72 * 25.4f;
+        float height = mediaBox.getHeight() / 72 * 25.4f;
+
+        if ((Math.abs(width - 210) < 1 && Math.abs(height - 297) < 1) || (Math.abs(width - 297) < 1 && Math.abs(height - 210) < 1)) {
+            return "A4";
+        } else if ((Math.abs(width - 420) < 1 && Math.abs(height - 297) < 1) || (Math.abs(width - 297) < 1 && Math.abs(height - 420) < 1)) {
+            return "A3";
+        } else if ((Math.abs(width - 420) < 1 && Math.abs(height - 594) < 1) || (Math.abs(width - 594) < 1 && Math.abs(height - 420) < 1)) {
+            return "A2";
+        } else if ((Math.abs(width - 594) < 1 && Math.abs(height - 841) < 1) || (Math.abs(width - 841) < 1 && Math.abs(height - 594) < 1)) {
+            return "A1";
+        }
+        return "Unknown";
+    }
+    public static boolean checkImageDPI(PDPage page, int dpi, int tolerance) throws IOException {
+        if (hasImages(page)) {
+            return checkPageDpi(page, dpi, tolerance);
+        }
+        return true;
+    }
+
+    /**
+     * 判断页面是否包含图片
+     *
+     * @param page 页面
+     * @return 如果包含图片,则返回true,否则返回false
+     */
+    private static boolean hasImages(PDPage page) {
+        return page.getResources().getXObjectNames().iterator().hasNext();
+    }
+
+
+    /**
+     * 检查单个PDF页面中的图片是否满足最小DPI要求。
+     * @param page        要检查的PDPage对象。
+     * @param requiredDpi 要求的最小DPI值。
+     * @param tolerance   DPI的容差范围。
+     * @return 如果页面上所有图片都满足要求,则返回true;否则返回false。
+     * @throws IOException 如果处理页面内容流时发生错误。
+     */
+    private static boolean checkPageDpi(PDPage page, int requiredDpi, int tolerance) throws IOException {
+        final AtomicBoolean allOk = new AtomicBoolean(true);
+
+        // 创建一个自定义的图形流引擎
+        PDFGraphicsStreamEngine engine = new PDFGraphicsStreamEngine(page) {
+            @Override
+            public void drawImage(PDImage pdImage) throws IOException {
+                Matrix ctm = getGraphicsState().getCurrentTransformationMatrix();
+
+                // 图像的像素尺寸
+                int imageWidth = pdImage.getWidth();
+                int imageHeight = pdImage.getHeight();
+
+                // 从变换矩阵获取X和Y方向的缩放因子。
+                // 这个缩放因子表示图像在页面上被渲染后的大小(单位是磅, 1 inch = 72 points)。
+                float renderedWidthInPoints = ctm.getScalingFactorX();
+                float renderedHeightInPoints = ctm.getScalingFactorY();
+
+                // 将磅转换为英寸
+                float renderedWidthInInches = renderedWidthInPoints / 72f;
+                float renderedHeightInInches = renderedHeightInPoints / 72f;
+
+                // 避免除以零
+                if (renderedWidthInInches == 0 || renderedHeightInInches == 0) {
+                    return;
+                }
+
+                // 计算实际的DPI
+                // 注意:对于旋转过的图片,这里的计算可能需要更复杂的处理,但对于常规的缩放,这是准确的。
+                //
+                long dpiX = Math.round(imageWidth / renderedWidthInInches);
+                long dpiY = Math.round(imageHeight / renderedHeightInInches);
+
+                System.out.printf("找到图片: 像素=[%d x %d], 渲染尺寸=[%.2f\" x %.2f\"], 计算DPI=[%d x %d]%n",
+                        imageWidth, imageHeight, renderedWidthInInches, renderedHeightInInches, dpiX, dpiY);
+
+                // 检查DPI是否低于要求值(考虑容差)
+                if (dpiX < requiredDpi - tolerance || dpiY < requiredDpi - tolerance) {
+                    System.err.printf("不合格! 图片DPI [%d x %d] 低于要求的 %d DPI%n", dpiX, dpiY, requiredDpi);
+                    allOk.set(false);
+                }
+            }
+
+            @Override
+            public void appendRectangle(java.awt.geom.Point2D p0, java.awt.geom.Point2D p1, java.awt.geom.Point2D p2, java.awt.geom.Point2D p3) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void clip(int windingRule) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void moveTo(float x, float y) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void lineTo(float x, float y) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public java.awt.geom.Point2D getCurrentPoint() throws IOException {
+                return new java.awt.geom.Point2D.Float(0, 0);
+            }
+
+            @Override
+            public void closePath() throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void endPath() throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void strokePath() throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void fillPath(int windingRule) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void fillAndStrokePath(int windingRule) throws IOException {
+                // 不需要处理,留空
+            }
+
+            @Override
+            public void shadingFill(org.apache.pdfbox.cos.COSName shadingName) throws IOException {
+                // 不需要处理,留空
+            }
+        };
+
+        // 处理当前页面的内容流
+        engine.processPage(page);
+
+        return allOk.get();
+    }
+}
+