Procházet zdrojové kódy

Merge remote-tracking branch 'origin/dev' into dev

LHB před 2 dny
rodič
revize
fdbcbf2999
15 změnil soubory, kde provedl 670 přidání a 545 odebrání
  1. 2 2
      blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/TaskSplitMapper.xml
  2. 16 22
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  3. 15 2
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/Archive2Controller.java
  4. 0 497
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ArchiveController.java
  5. 1 1
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java
  6. 7 9
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java
  7. 61 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  8. 16 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/SubTable.java
  9. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IExcelTabService.java
  10. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreeContractService.java
  11. 135 4
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  12. 12 4
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  13. 75 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  14. 109 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/FileUtils.java
  15. 219 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/utils/PDFAnalyzerUtils.java

+ 2 - 2
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/TaskSplitMapper.xml

@@ -40,11 +40,11 @@
     </select>
 
     <update id="updateArchiveByContratId" >
-        update u_archives_auto set split_status = 0 where contract_id = #{contractId} and is_deleted = 0
+        update u_archives_auto set split_status = 2 where contract_id = #{contractId} and is_deleted = 0
     </update>
 
     <update id="updateArchiveByIds">
-        update u_archives_auto set split_status = 0 where is_deleted = 0 and id in
+        update u_archives_auto set split_status = 2 where is_deleted = 0 and id in
         <foreach item="item" collection="split" separator="," close=")" open="(" index="index">
             #{item}
         </foreach>

+ 16 - 22
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml

@@ -514,28 +514,22 @@
         WHERE uaa.project_id = #{projectId} and uaa.is_deleted = 0
     </select>
     <select id="getAllArchiveFileByContractTypeSummary" resultType="java.util.Map">
-        select sum(a.key1 + a.key11) key1,sum(a.key2 + a.key12) key2,sum(a.key3 +a.key13) key3 ,a.key4,a.key5,a.key6 from (
-          SELECT
-              sum(uaf.source_type = 1 and matc.tree_code is null) key4,
-              sum((uaf.source_type != 1 or uaf.source_type is null ) and matc.tree_code is null) key3,
-              sum(uaf.source_type = 1 and matc.tree_code = 'S') key5,
-              sum((uaf.source_type != 1 or uaf.source_type is null ) and matc.tree_code = 'S') key2,
-              sum(uaf.source_type = 1 and matc.tree_code = 'C') key6,
-              sum((uaf.source_type != 1 or uaf.source_type is null ) and matc.tree_code = 'C') key1,
-              sum(mci.id is not null and mci.contract_type = 1) key11,
-              sum(mci.id is not null and mci.contract_type = 2) key12,
-              sum(mci.id is not null and mci.contract_type not in(1,2)) key13
-
-          FROM
-              u_archive_file uaf
-                  LEFT JOIN m_archive_tree_contract matc ON uaf.node_id = matc.id
-                  left join m_contract_info mci on matc.tree_code = mci.id and mci.is_deleted = 0
-          WHERE
-              uaf.project_id = #{projectId}
-            AND matc.is_deleted = 0
-            AND uaf.is_deleted = 0
-            AND ( uaf.is_auto_file IS NULL OR uaf.is_auto_file != 1 )
-      ) a
+        SELECT
+            sum(uaf.source_type = 1 and ((matc.tree_code is null or matc.tree_code = 'null') or (mci.id is not null and mci.contract_type not in(1,2)))) key4,
+            sum((uaf.source_type != 1 or uaf.source_type is null ) and ((matc.tree_code is null or matc.tree_code = 'null') or (mci.id is not null and mci.contract_type not in(1,2))) ) key3,
+            sum(uaf.source_type = 1 and (matc.tree_code = 'S' or (mci.id is not null and mci.contract_type = 2))) key5,
+            sum((uaf.source_type != 1 or uaf.source_type is null ) and (matc.tree_code = 'S' or (mci.id is not null and mci.contract_type = 2)) ) key2,
+            sum(uaf.source_type = 1 and (matc.tree_code = 'C' or (mci.id is not null and mci.contract_type = 1)) ) key6,
+            sum((uaf.source_type != 1 or uaf.source_type is null ) and (matc.tree_code = 'C' or (mci.id is not null and mci.contract_type = 1))) key1
+        FROM
+            u_archive_file uaf
+                LEFT JOIN m_archive_tree_contract matc ON uaf.node_id = matc.id
+                left join m_contract_info mci on matc.tree_code = mci.id and mci.is_deleted = 0
+        WHERE
+            uaf.project_id = #{projectId}
+          AND matc.is_deleted = 0
+          AND uaf.is_deleted = 0
+          AND ( uaf.is_auto_file IS NULL OR uaf.is_auto_file != 1 )
     </select>
     <select id="getAllArchiveFileByContractTypeCount" resultType="java.lang.Integer">
         SELECT count(0) `count`

+ 15 - 2
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/Archive2Controller.java

@@ -64,7 +64,7 @@ public class Archive2Controller {
         //执行代码
         log.info("分解pdf专图片");
        // String sql = "SELECT distinct b.id,b.archive_id as archiveId ,REPLACE(b.file_url,'https://xinan1.zos.ctyun.cn','http://100.86.2.1:80') as fileUrl from u_archives_auto a ,u_archive_file b  where a.id=b.archive_id  and a.is_deleted=0 and b.is_deleted=0 and a.split_status=10 LIMIT 20";
-        String sql = "SELECT distinct b.id,b.archive_id as archiveId ,b.file_url as fileUrl from u_archives_auto a ,u_archive_file b  where a.id=b.archive_id  and a.is_deleted=0 and b.is_deleted=0 and a.split_status=10 LIMIT 20";
+        String sql = "SELECT distinct b.id,b.archive_id as archiveId ,b.file_url  as fileUrl from u_archives_auto a ,u_archive_file b  where a.id=b.archive_id  and a.is_deleted=0 and b.is_deleted=0 and a.split_status=2 LIMIT 20";
         List<TaskArchiveSplitVO> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(TaskArchiveSplitVO.class));
 
         if (query != null && query.size() >= 1) {
@@ -339,7 +339,7 @@ public class Archive2Controller {
 
     public static int getPdfByPage(int startPage, int endPage, String filePath, String savePath) {
         try {
-            InputStream inputStreamByUrl = CommonUtil.getOSSInputStream3(filePath);
+            InputStream inputStreamByUrl = CommonUtil.getOSSInputStream(filePath);
             // 加载PDF文件
             PDDocument document = PDDocument.load(inputStreamByUrl);
             // 创建新文档
@@ -430,4 +430,17 @@ public class Archive2Controller {
         fmfile.delete();
         return 200;
     }
+
+/*    public static void main(String[] args) {
+        // 获取pdf第二页的数据
+        String fileUrl = "/Users/hongchuangyanfa/Desktop/archiveSplit/PDF合并.pdf";
+        String firstUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + 123 + "first__" + 1 + "__.pdf";
+        int pdfByPage = getPdfByPage(0, 1, fileUrl, firstUrl);
+        File file = new File(firstUrl);
+
+        // 保存第一页为300DPI图片
+        String imagePath = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + 123 + "first__" + 1 + "__.png";
+        File imgfile = new File(imagePath);
+        int dataNum = savePdfAsImage(1, fileUrl, imagePath);
+    }*/
 }

+ 0 - 497
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ArchiveController.java

@@ -1,497 +0,0 @@
-package org.springblade.evisa.controller;
-
-import io.swagger.annotations.Api;
-import lombok.AllArgsConstructor;
-import lombok.Synchronized;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDPage;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-import org.springblade.common.utils.CommonUtil;
-import org.springblade.common.utils.SafeURLEncoder;
-import org.springblade.common.utils.SnowFlakeUtil;
-import org.springblade.core.oss.model.BladeFile;
-import org.springblade.core.tool.utils.Func;
-import org.springblade.core.tool.utils.IoUtil;
-import org.springblade.evisa.utils.FileUtils;
-import org.springblade.evisa.vo.ArchivesSplitInfoVO;
-import org.springblade.evisa.vo.TaskArchiveSplitVO;
-import org.springblade.resource.feign.NewIOSSClient;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.jdbc.core.BeanPropertyRowMapper;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import java.io.*;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.pdfbox.rendering.PDFRenderer;
-
-import java.awt.image.BufferedImage;
-import javax.imageio.ImageIO;
-
-
-/**
- * 清表基础数据表 控制器
- *
- * @author BladeX
- * @since 2022-05-18
- */
-@RestController
-@AllArgsConstructor
-@Api(value = "电签类", tags = "电签类接口")
-@Slf4j
-public class ArchiveController {
-
-    private final StringRedisTemplate RedisTemplate;
-
-    private final JdbcTemplate jdbcTemplate;
-
-    private final NewIOSSClient newIOSSClient;
-
-    // 线程池
-    @Resource(name = "archivePoolExecutor")
-    private ThreadPoolExecutor archExecutor;
-
-   // @Scheduled(cron = "0/30 * * * * ?")
-    public void SignTaskBatchPng() {
-        //执行代码
-        log.info("分解pdf专图片");
-       // String sql = "SELECT distinct b.id,b.archive_id as archiveId ,REPLACE(b.file_url,'https://xinan1.zos.ctyun.cn','http://100.86.2.1:80') as fileUrl,c.id as taskId from u_archives_auto a , u_archive_file b ,u_task_split c  where a.id=b.archive_id and ((FIND_IN_SET(a.id,c.ids) and c.type=3) or (a.contract_id=c.contract_id and c.type=2)) and a.is_deleted=0 and b.is_deleted=0 and a.split_status not in(1,2) LIMIT 20";
-        String sql = "SELECT distinct b.id,b.archive_id as archiveId ,b.file_url as fileUrl,c.id as taskId from u_archives_auto a , u_archive_file b ,u_task_split c  where a.id=b.archive_id and ((FIND_IN_SET(a.id,c.ids) and c.type=3) or (a.contract_id=c.contract_id and c.type=2)) and a.is_deleted=0 and b.is_deleted=0 and a.split_status not in(1,2) LIMIT 20";
-        List<TaskArchiveSplitVO> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(TaskArchiveSplitVO.class));
-
-        if (query != null && query.size() >= 1) {
-            for (TaskArchiveSplitVO dataInfo : query) {
-                if (archExecutor.getQueue().size() <= 20) {
-                    Boolean aBoolean = RedisTemplate.hasKey("splitpng-" + dataInfo.getArchiveId());
-                    if (!aBoolean) {
-                        RedisTemplate.opsForValue().setIfAbsent("splitpng-" + dataInfo.getArchiveId(), "1", 600, TimeUnit.SECONDS);
-                        CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
-                            try {
-                                signTaskBatchpngToHtml(dataInfo);
-                            } catch (Exception e) {
-                                e.printStackTrace();
-                            }
-                        }, archExecutor);
-                    }
-                }
-            }
-        }
-        System.out.println("队列数量_img" + archExecutor.getQueue().size());
-        System.out.println("活跃数量_img" + archExecutor.getActiveCount());
-        System.out.println("总共数量_img" + archExecutor.getTaskCount());
-        System.out.println("完成数量_img" + archExecutor.getCompletedTaskCount());
-    }
-
-    // 分解第一页的任务
-
-    public void signTaskBatchpngToHtml(TaskArchiveSplitVO taskSign) {
-        try {
-            String fileUrl = taskSign.getFileUrl();
-            String archiveId = taskSign.getArchiveId();
-            String id = taskSign.getId();
-            String taskId = taskSign.getTaskId();
-            List<String> listPdf = new ArrayList<>();
-            int startPage = 0;
-            for (int i = 2; i <= 10; i++) {
-                // 获取pdf第二页的数据
-                String firstUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "first__" + i + "__.pdf";
-                File file = new File(firstUrl);
-                if (!file.exists()) {
-                    int pdfByPage = getPdfByPage(i, i, fileUrl, firstUrl);
-                    if(pdfByPage==1){
-                        String sql22 = "INSERT INTO u_archives_split_info(id,status,file_url,first_file_url,task_id,archive_id,create_time) VALUES(?,?,?,?,?,?,SYSDATE())";
-                        int addCount = jdbcTemplate.update(sql22, id, 4, fileUrl, "获取数据源失败", taskId, archiveId);
-                        String updateSql = "UPDATE u_archives_auto SET split_status=? WHERE id=?";
-                        int delCount = jdbcTemplate.update(updateSql, 1, archiveId);
-                        return;
-                    }
-                }
-
-                // 保存第一页为300DPI图片
-                String imagePath = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "first__" + i + "__.png";
-                File imgfile = new File(imagePath);
-                if (!imgfile.exists()) {
-                    int dataNum = savePdfAsImage(1, firstUrl, imagePath);
-                }
-                // 删除pdf
-                file.delete();
-                String state = OcrTitle(imagePath, "1");
-                if (state.equals("1")) {
-                    if (startPage < 2) {
-                        startPage = i;
-                    }
-                    listPdf.add(imagePath);
-                } else {
-                    imgfile.delete();
-                    break;
-                }
-            }
-            System.out.println(listPdf.size());
-            String filePath = startPage + "--" + (listPdf.size() + 1);
-            //判断
-            List<Map<String, Object>> mapList = jdbcTemplate.queryForList("select * from u_archives_split_info where id=" + id + "");
-            if (mapList != null && Func.isNotEmpty(mapList) && mapList.size() >= 1) {
-                String status = mapList.get(0).get("status") + "";
-               // if (status.equals("3")) {
-                    String updateSql = "update u_archives_split_info set status=2 ,file_url='"+fileUrl+"',first_file_url ='"+filePath+"' where id=" + id;
-                    jdbcTemplate.update(updateSql);
-                String uateSql = "UPDATE u_archives_auto SET split_status=? WHERE id=?";
-                int delCount = jdbcTemplate.update(uateSql, 2, archiveId);
-              //  }
-
-            } else {
-                // 所有SQL都改为参数化查询
-                String sql22 = "INSERT INTO u_archives_split_info(id,status,file_url,first_file_url,task_id,archive_id,create_time) VALUES(?,?,?,?,?,?,SYSDATE())";
-                int addCount = jdbcTemplate.update(sql22, id, 2, fileUrl, filePath, taskId, archiveId);
-
-                String updateSql = "UPDATE u_archives_auto SET split_status=? WHERE id=?";
-                int delCount = jdbcTemplate.update(updateSql, 2, archiveId);
-            }
-            String delSql = "DELETE FROM u_archive_file WHERE id<>? AND archive_id=?";
-            jdbcTemplate.update(delSql, id, archiveId);
-            RedisTemplate.delete("splitpng-" + archiveId);
-
-        } catch (Exception e) {
-            System.out.println("12321312");
-            e.printStackTrace();
-        }
-    }
-  //  @Scheduled(cron = "0/30 * * * * ?")
-    public void SplitPdfInfo() {
-        //执行代码
-        log.info("分解html开始");
-        String sql = "select  * from u_archives_split_info where status =2 LIMIT 20 "; // and TIMESTAMPDIFF(MINUTE, create_time, NOW()) >=3";
-        List<ArchivesSplitInfoVO> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ArchivesSplitInfoVO.class));
-        log.info("分解html开始===" + query.size());
-        if (query != null && query.size() >= 1) {
-            for (ArchivesSplitInfoVO dataInfo : query) {
-                if (archExecutor.getQueue().size() <= 10) {
-                    Boolean aBoolean = RedisTemplate.hasKey("splithtml-" + dataInfo.getArchiveId());
-                    if (!aBoolean) {
-
-                        if (!aBoolean) {
-                            RedisTemplate.opsForValue().setIfAbsent("splithtml-" + dataInfo.getArchiveId(), "1", 1200, TimeUnit.SECONDS);
-                            CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
-                                try {
-                                    /*===============执行批量任务===============*/
-                                    signTaskBatchpng(dataInfo);
-                                } finally {
-                                    RedisTemplate.delete("splithtml-" + dataInfo.getArchiveId());
-                                }
-                            }, archExecutor);
-                        }
-                    }
-                }
-            }
-        }
-        System.out.println("队列数量_html" + archExecutor.getQueue().size());
-        System.out.println("活跃数量_html" + archExecutor.getActiveCount());
-        System.out.println("总共数量_html" + archExecutor.getTaskCount());
-        System.out.println("完成数量_html" + archExecutor.getCompletedTaskCount());
-    }
-
-    public void signTaskBatchpng(ArchivesSplitInfoVO taskSign) {
-        try {
-            System.out.println("分解001");
-            String archiveId = taskSign.getArchiveId();
-            String fileUlr = taskSign.getFileUrl();
-            String firstPage = FileUtils.getSysLocalFileUrl() + "archiveSplit/";
-            String firstFileUrl = taskSign.getFirstFileUrl();
-            String firstUrl[] = firstFileUrl.split("--");
-            int basePage = Integer.parseInt(firstUrl[1]);
-            int baseStart = Integer.parseInt(firstUrl[0]);
-            String dutyUser = "";
-            int bkb = 0;
-            //将imagePath 的数据转成一个可解析的html
-            String htmlUrl = pngToHtml(firstPage, archiveId, taskSign.getFirstFileUrl());
-            System.out.println("分解002=" + htmlUrl);
-
-            if (htmlUrl.indexOf("_001.html") >= 0 && htmlUrl.indexOf("archiveSplit") >= 0) {
-                String htmlString = IoUtil.readToString(new FileInputStream(htmlUrl));
-                Document doc = Jsoup.parse(htmlString);
-                Element table = doc.select("table").first();
-                Elements trs = table.select("tr");
-                //由于解析已经成功,可能数据已经分解过,需要删除
-                if (trs != null && trs.size() >= 1) {
-                    String sql = "delete from u_archive_file where id<>'" + taskSign.getId() + "' and archive_id='" + archiveId + "'";
-                    jdbcTemplate.execute(sql);
-                }
-
-                for (int i = 0; i <= trs.size() - 1; i++) {
-                    Element tr = trs.get(i);
-                    String zrz = tr.select("td").get(0).text();
-                    String wjtm = tr.select("td").get(1).text();
-                    String rq = tr.select("td").get(2).text();
-                    String ym = tr.select("td").get(3).text();
-                    if (zrz.equals("责任者") && wjtm.equals("文件题名") && rq.equals("日期")) {
-                        continue;
-                    }
-                    int startYm = 0;
-                    int endYm = 0;
-                    if (i < trs.size() - 1) {
-                        startYm = Func.toInt(ym);
-                        String enData = trs.get(i + 1).select("td").get(3).text();
-                        if (enData.indexOf("页") >= 0) {
-                            enData = trs.get(i + 2).select("td").get(3).text();
-                        }
-
-                        String[] parts = enData.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
-                        if (parts != null && parts.length >= 1) {
-                            endYm = Func.toInt(parts[0]);
-                        }
-                    } else {
-                        String[] split = ym.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)");
-                        if (split != null && split.length >= 3) {
-                            startYm = Func.toInt(split[0]);
-                            endYm = Func.toInt(split[2]);
-                        } else {
-                            startYm = Func.toInt(split[0]);
-                            endYm = Func.toInt(split[0]);
-                        }
-                    }
-                    startYm = basePage + startYm;
-                    endYm = basePage + endYm;
-                    dutyUser = zrz;
-                    System.out.println("序号=" + i + "--文件提名:" + wjtm + "--开始(" + startYm + "-" + endYm + ")---页数" + (endYm - startYm + 1));
-                    // 分解文件
-                    String fmlUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_cf_00" + i + ".pdf";
-                    getPdfByPage(startYm, endYm, fileUlr, fmlUrl);
-                    saveDataToMysql(fmlUrl, wjtm, taskSign.getId(), endYm - startYm + 1, i, zrz, rq);
-                    bkb = endYm;
-                }
-            } else {
-                return;
-            }
-
-            // 添加封面信息
-            String fmlUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_fm_001.pdf";
-            getPdfByPage(1, 1, fileUlr, fmlUrl);
-            saveDataToMysql(fmlUrl, "封面", taskSign.getId(), 1, -4, dutyUser, "");
-
-            // 卷内目录
-            String jnmuUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_jnml_001.pdf";
-            getPdfByPage(baseStart, basePage, fileUlr, jnmuUrl);
-            saveDataToMysql(jnmuUrl, "卷内目录", taskSign.getId(), 1, -3, dutyUser, "");
-
-            // 卷内备考表
-            String jnbkbUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_jnbkb_001.pdf";
-            getPdfByPage(bkb + 1, bkb + 1, fileUlr, jnbkbUrl);
-
-            File jlPdfFile = new File(jnbkbUrl);
-            if (jlPdfFile.exists()) {
-                saveDataToMysql(jnbkbUrl, "卷内备考表", taskSign.getId(), 1, 100, dutyUser, "");
-            }
-
-            // 背脊表
-            String bjbUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_beiji_001.pdf";
-            String bjbUrlPng = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_beiji_001.png";
-
-            int pdfByPage = getPdfByPage(bkb + 2, bkb + 2, fileUlr, bjbUrl);
-            if(pdfByPage==0){
-                File bgImgFile = new File(bjbUrlPng);
-                if (!bgImgFile.exists()) {
-                    int dataNum = savePdfAsImage(1, bjbUrl, bjbUrlPng);
-                }
-                String state = OcrTitle(bjbUrlPng, "3");
-                if (state.equals("1")) {
-                    saveDataToMysql(bjbUrl, "背脊表", taskSign.getId(), 1, 101, dutyUser, "");
-                }
-                bgImgFile.delete();
-            }
-            // 修改任务状态
-            String updateSql = "update u_archives_split_info set status=3 where id=" + taskSign.getId();
-            jdbcTemplate.execute(updateSql);
-            // 修改 u_archives_auto 为 已经分解
-            String updateSqlAuto = "update u_archives_auto set split_status=1 where id=" + taskSign.getArchiveId();
-            jdbcTemplate.execute(updateSqlAuto);
-
-            // 统计各个任务的结果
-            String taxkSql = "UPDATE u_task_split a set finished = (SELECT count(1) from u_archives_auto b where FIND_IN_SET(b.id,a.ids) and b.split_status=1) where FIND_IN_SET(" + taskSign.getArchiveId() + ",a.ids)  and a.type=3";
-            String taxkSql2 = "UPDATE u_task_split a set finished = (SELECT count(1) from u_archives_auto b where a.contract_id=b.contract_id) where a.id=" + taskSign.getTaskId() + " and a.type=2";
-            jdbcTemplate.execute(taxkSql);
-            jdbcTemplate.execute(taxkSql2);
-
-            // 删除html
-            File fileHtml = new File(fmlUrl);
-            fileHtml.delete();
-
-            // 修改完成情况
-            RedisTemplate.delete("splithtml-" + archiveId);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public static String pngToHtml(String fileUrl, String pKeyId, String pageNum) {
-        String lasHhtmlUrl = "";
-        try {
-            // 定义Python解释器路径和脚本路径
-            String pythonScript = "/Users/hongchuangyanfa/Desktop/PycharmProjects/splitPngToHtml.py";
-            // 构建命令
-            ProcessBuilder pb = new ProcessBuilder("python3", pythonScript, fileUrl, pKeyId, pageNum);
-            Process process = pb.start();
-
-            // 读取Python脚本输出
-            BufferedReader reader = new BufferedReader(
-                    new InputStreamReader(process.getInputStream()));
-            String htmlUrl;
-            while ((htmlUrl = reader.readLine()) != null) {
-                System.out.println(htmlUrl);
-                if (htmlUrl.indexOf("html文件路径") >= 0 && htmlUrl.indexOf("_001.html") >= 0 && htmlUrl.indexOf("archiveSplit") >= 0) {
-                    lasHhtmlUrl = htmlUrl.replace("html文件路径", "");
-                }
-            }
-            // 等待进程结束
-            int exitCode = process.waitFor();
-            if (exitCode == 0) {
-                return lasHhtmlUrl;
-            } else {
-                return "1";
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            return "1";
-        }
-    }
-
-    public static String OcrTitle(String fileUrl, String type) {
-        String lasHhtmlUrl = "";
-        try {
-            // 定义Python解释器路径和脚本路径
-            String pythonScript = "/Users/hongchuangyanfa/Desktop/PycharmProjects/splitPngByTitle.py";
-            // 构建命令
-            ProcessBuilder pb = new ProcessBuilder("python3", pythonScript, fileUrl, type);
-            Process process = pb.start();
-
-            // 读取Python脚本输出
-            BufferedReader reader = new BufferedReader(
-                    new InputStreamReader(process.getInputStream()));
-            String htmlUrl;
-            while ((htmlUrl = reader.readLine()) != null) {
-                System.out.println("222" + htmlUrl);
-                if (htmlUrl.indexOf("图片中是否有卷内目录") >= 0 && htmlUrl.indexOf("True") >= 0) {
-                    return "1";
-                }
-            }
-            // 等待进程结束
-            int exitCode = process.waitFor();
-            if (exitCode == 0) {
-                return lasHhtmlUrl;
-            } else {
-                return "1";
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            return "1";
-        }
-    }
-
-
-    public static int getPdfByPage(int startPage, int endPage, String filePath, String savePath) {
-        try {
-            InputStream inputStreamByUrl = CommonUtil.getOSSInputStream3(filePath);
-            // 加载PDF文件
-            PDDocument document = PDDocument.load(inputStreamByUrl);
-            // 创建新文档
-            PDDocument newDocument = new PDDocument();
-
-            // 注意:PDFBox中的页面索引从0开始
-            int actualStart = Math.max(0, startPage - 1); // 将用户输入的1转换为0
-            int actualEnd = Math.min(document.getNumberOfPages() - 1, endPage - 1); // 将用户输入的10转换为9
-
-            // 添加指定范围的页面
-            for (int i = actualStart; i <= actualEnd; i++) {
-                PDPage page = document.getPage(i);
-                newDocument.addPage(page);
-            }
-
-            // 保存为新文件
-            newDocument.save(savePath);
-            newDocument.close();
-            document.close();
-            return 0;
-        } catch (Exception e) {
-            return 1;
-        }
-    }
-
-    public static int savePdfAsImage(int pageNum, String filePath, String outputPath) {
-        try (InputStream inputStream = FileUtils.getInputStreamByUrl(filePath);
-             PDDocument document = PDDocument.load(inputStream)) {
-
-            // 验证页码范围
-            if (pageNum < 1 || pageNum > document.getNumberOfPages()) {
-                return 1;
-            }
-
-            PDFRenderer renderer = new PDFRenderer(document);
-
-            // 设置DPI为300
-            final int DPI = 300;
-
-            // 渲染指定页面(注意PDFBox使用0-based索引)
-            //BufferedImage image = renderer.renderImage(pageNum - 1, DPI / 72f);
-            BufferedImage image = renderer.renderImageWithDPI(0, DPI); // 0 表示第一页
-            // 确保输出目录存在
-            File outputFile = new File(outputPath);
-            outputFile.getParentFile().mkdirs();
-
-            // 保存为PNG格式(可改为JPG等)
-            ImageIO.write(image, "PNG", outputFile);
-
-            log.info("PDF页面已成功保存为图片: {}", outputPath);
-            inputStream.close();
-            document.close();
-            return 0;
-        } catch (Exception e) {
-            log.error("PDF转图片失败", e);
-            return 1;
-        }
-    }
-
-    public int saveDataToMysql(String upFileUrl, String fileName, String fileId, int filePage, int sort, String dutyUser, String fileTime) {
-        // 获取封面信息
-        long newPkId = SnowFlakeUtil.getId(); //主键Id
-        File fmfile = new File(upFileUrl);
-        if (fmfile.exists()) {
-            BladeFile bladeFile = this.newIOSSClient.uploadFile(fileName + ".pdf", upFileUrl);
-            if (bladeFile != null && Func.isNotEmpty(bladeFile.getLink())) {
-                String FmPdfUrl = bladeFile.getLink();
-                String sql = " insert into u_archive_file( " +
-                        " id,project_id,contract_id,node_id,file_number,file_name,file_time,file_url,pdf_file_url,file_page,is_approval,is_certification,is_need_certification,duty_user,create_user,create_dept,create_time,update_user,update_time,status,is_deleted,sheet_type,sheet_source, " +
-                        " drawing_no,cite_change_number,certification_time,e_visa_file,node_ext_id,file_type,archive_id,origin_id,filming_time,filmingor_time,tag_id,pic_code,refer_code,film_code,width,height,ftime,utime,del_time,sort,box_name,box_number,is_auto_file,is_archive,page_num, " +
-                        " file_size,source_type,is_element,pdf_page_url,fid,rectification,classify,m_wbs_tree_contract_p_key_id,u_image_classification_file_id,archive_file_storage_type,node_tree_structure,date_name,archive_file_stroage_type,out_id,sort_num " +
-                        "   ) " +
-                        " SELECT " + newPkId + ",project_id,contract_id,node_id,file_number,'" + fileName + "','" + fileTime + "','" + FmPdfUrl + "','" + FmPdfUrl + "'," + filePage + ",is_approval,is_certification,is_need_certification,'" + dutyUser + "',create_user,create_dept,create_time,update_user,update_time,status,is_deleted,sheet_type,sheet_source, " +
-                        "        drawing_no,cite_change_number,certification_time,e_visa_file,node_ext_id,file_type,archive_id,origin_id,filming_time,filmingor_time,tag_id,pic_code,refer_code,film_code,width,height,ftime,utime,del_time," + sort + ",box_name,box_number,is_auto_file,is_archive,page_num, " +
-                        "        file_size,source_type,is_element,pdf_page_url,fid,rectification,classify,m_wbs_tree_contract_p_key_id,u_image_classification_file_id,archive_file_storage_type,node_tree_structure,date_name,archive_file_stroage_type,out_id,sort_num " +
-                        " from u_archive_file where id=" + fileId;
-                System.out.println(fileName + "----" + sql);
-                jdbcTemplate.execute(sql);
-
-            } else {
-                // 检查一下oss是否启动
-                System.out.println("oss服务未启动,无法上传文件到oss");
-                return 500;
-            }
-        } else {
-            return 404;
-        }
-        fmfile.delete();
-        return 200;
-    }
-}

+ 1 - 1
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java

@@ -47,7 +47,7 @@ public class ChekSignData {
     @Resource(name = "taskExecutor1")
     private ThreadPoolExecutor executor;
 
-//    @Scheduled(cron = "0/10 * * * * ?")
+    @Scheduled(cron = "0/10 * * * * ?")
     public void SignInfo() {
         // 质检SQL
         String sql = "SELECT a.id ,a.e_visa_pdf_url,b.process_instance_id,a.contract_id,a.project_id,c.remark_type from u_information_query a ,u_task b ,m_project_info c where  c.id=a.project_id  and a.`status` = 2 and a.is_deleted=0 and a.e_visa_pdf_url is not null  and b.form_data_id = a.id and b.`status` = 2 and a.chek_status=1 LIMIT 30";

+ 7 - 9
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java

@@ -454,10 +454,6 @@ public class EVDataServiceImpl implements EVDataService {
                 taskApp.setPdfDataType(type);
                 if (StringUtils.isNotEmpty(pdfTrialUrlPosition) || StringUtils.isNotEmpty(pdfTrialUrl) || StringUtils.isNotEmpty(eVisaPdfUrl) || StringUtils.isNotEmpty(pdfUrl)) {
                     if ("1".equals(type)) {
-                       /* String approvalPdf = eVisaPdfUrl.length() >= 10 ? eVisaPdfUrl : pdfUrl;
-                        approvalPdf = pdfTrialUrl.length() >= 10 ? pdfTrialUrl : approvalPdf;
-                        approvalPdf = pdfTrialUrlPosition.length() >= 10 ? pdfTrialUrlPosition : approvalPdf;
-                        taskApp.setSignPdfUrl(getHppsToHttp(approvalPdf));*/
                         String nodeDdfUrl = map.get("node_pdf_url") + "";
                         if(pdfTrialUrlPosition!=null && pdfTrialUrlPosition.length()>=10){
                             taskApp.setSignPdfUrl(getHppsToHttp(pdfTrialUrlPosition));
@@ -577,17 +573,19 @@ public class EVDataServiceImpl implements EVDataService {
                 SignBackPdfInfo(taskApp);
                 return;
             }
-            ProjectInfo projectInfo = projectClient.getById(taskApp.getProjectId());
-            if (projectInfo == null || Func.isEmpty(projectInfo)) {
+
+            List<Map<String, Object>> projectList = jdbcTemplate.queryForList("select * from u_project_info where  id = " + taskApp.getProjectId());
+            if (projectList == null || Func.isEmpty(projectList)) {
                 taskApp.setSigState(2);
                 taskApp.setSignSmg("未获取项目信息");
                 SignBackPdfInfo(taskApp);
                 return;
             } else {
-                Integer remarkType = projectInfo.getRemarkType();
-                if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType == 2) {
+                Map<String, Object> projectInfo = projectList.get(0);
+                String remarkType = projectInfo.get("remarkType")+"";//.getRemarkType();
+                if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType.equals("2")) {
                     taskApp.setRemarkType("2");
-                }else if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType == 3) {
+                }else if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType .equals("3")) {
                     taskApp.setRemarkType("3");
                 }else {
                     taskApp.setRemarkType("1");

+ 61 - 1
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;
@@ -2010,6 +2013,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("文件尺寸不符合要求,请上传A3或A4尺寸的文件");
+            }
         }
 
         tableFileService.saveBatch(list);
@@ -2045,6 +2066,26 @@ public class ExcelTabController extends BladeController {
         return R.status(true);
     }
 
+    /**
+     * 校验pdf文件尺寸和dpi
+     * @param pdfUrl pdf oss 路径
+     * @return  true false
+     */
+    public boolean checkPDFSizeAndDpi(String pdfUrl) {
+        String value = ParamCache.getValue("upload.pdf.check");
+        if (value == null || "0".equals( value.trim())) {
+            return true;
+        }
+        // 使用内存限制设置 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 = "隐藏表单")
@@ -2333,7 +2374,8 @@ public class ExcelTabController extends BladeController {
         }
 
         //保存数据到数据库
-        R<Object> result = this.excelTabService.saveOrUpdateInfo(tableInfoList,singnType,flag);
+//        R<Object> result = this.excelTabService.saveOrUpdateInfo(tableInfoList,singnType,flag);
+        R<Object> result = this.excelTabService.saveOrUpdateInfo1(tableInfoList,flag);
         RandomNumberHolder.RandomTemplateTypeclear();
         RandomNumberHolder.RandomWbsTreeContractclear();
         executionTime.info("----数据保存结束----");
@@ -4854,6 +4896,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("文件尺寸不符合要求,请上传A3或A4尺寸的文件");
+                }
             }
             tableFileService.saveOrUpdateBatch(fileList);
 

+ 16 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/SubTable.java

@@ -42,6 +42,8 @@ public class SubTable {
     private FormData design;
     /**实测值*/
     private FormData data;
+    // 频率对象
+    private String plStr;
 
     private TableElementConverter tec;
     private List<Long> delTabList = new ArrayList<>();
@@ -212,8 +214,21 @@ public class SubTable {
              designs = this.design.getValues().stream().map(ElementData::stringValue).collect(Collectors.toList());
         }
         for(FormData item:this.mainList){
+
             String key = FormulaUtils.parseItemName(item.getEName()).trim();
-            keyMap.put(key, "1");
+            String code = item.getCode();
+            if(code!=null && code.contains(":")){
+                String[] split = code.split(":");
+                String key1 = split[0];
+                String key2 = split[1];
+                if(this.getPlStr().indexOf(key1)>=0 && this.getPlStr().indexOf(key2)>=0){
+                    System.out.println("123");
+                }else{
+                    keyMap.put(key, "1");
+                }
+            }else{
+                keyMap.put(key, "1");
+            }
         }
 
         // 计算元素是否需要覆盖

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IExcelTabService.java

@@ -109,6 +109,7 @@ public interface IExcelTabService extends BaseService<ExcelTab> {
      * 结果信息持久化
      */
     R<Object> saveOrUpdateInfo(List<TableInfo> tableInfoList,String singType,Boolean flag) throws SQLException;
+    R<Object> saveOrUpdateInfo1(List<TableInfo> tableInfoList,Boolean flag) throws SQLException;
 
     Map<String, String> getTablbCols(String pkeyid, String colkey) throws FileNotFoundException;
 

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsTreeContractService.java

@@ -43,6 +43,7 @@ public interface IWbsTreeContractService extends BaseService<WbsTreeContract> {
     List<WbsTreeContractTreeVO3> lazyTreeThree(String contractIdRelation, Long parentId, String contractId);
 
     List<AppWbsTreeContractVO> searchNodeAllTable(String primaryKeyId, String tableOwner, String contractId, String projectId, HttpServletRequest request);
+    List<AppWbsTreeContractVO> searchNodeAllTable(String primaryKeyId, String tableOwner);
 
     List<WbsTreeContract> searchParentAllNode(long primaryKeyId, Long contractId);
 

+ 135 - 4
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -661,7 +661,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
     private List<NodeTable> createNodeTables(Long nodeId, String contractId, String projectId, ExecuteType type ,String tableOwner, String recordId) {
         List<NodeTable> tableAll = new ArrayList<>();
         if (type.equals(ExecuteType.INSPECTION)) {
-            List<AppWbsTreeContractVO> treeNode = wbsTreeContractService.searchNodeAllTable(nodeId.toString(), tableOwner, contractId, projectId, null);
+            List<AppWbsTreeContractVO> treeNode = wbsTreeContractService.searchNodeAllTable(nodeId.toString(), tableOwner);
             tableAll = BeanUtil.copyProperties(treeNode, NodeTable.class);
         } else if (type.equals(ExecuteType.TESTING)) {
             List<WbsTreePrivateVO4> wbsTreePrivateVO4s = wbsTreePrivateService.searchNodeAllTable(nodeId.toString(), "1", "9", contractId, projectId, null, null);
@@ -697,7 +697,16 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
             } else {
                 List<WbsTreeContract> list = wbsTreeContractService.getBaseMapper().selectList(Wrappers.<WbsTreeContract>query().lambda().in(WbsTreeContract::getPKeyId, pkeyIds));
                 if (list.size() > 0) {
-                    list.forEach(e -> coordinateMap.computeIfAbsent(e.getInitTableName(), k -> FormulaUtils.getElementCell(e.getHtmlUrl())));
+                    Map<String, Map<String, String>> htmlElementCellMap = new HashMap<>();
+                    list.forEach(e -> {
+                        Map<String, String> map = htmlElementCellMap.get(e.getHtmlUrl());
+                        if (map == null) {
+                            map = FormulaUtils.getElementCell(e.getHtmlUrl());
+                            htmlElementCellMap.put(e.getHtmlUrl(), map);
+                        }
+                        Map<String, String> elementCell = map;
+                        coordinateMap.computeIfAbsent(e.getInitTableName(), k -> elementCell);
+                    });
                 }
             }
         }
@@ -1539,6 +1548,123 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
        return R.data(tableInfoList2);
        // return R.success(fileName1);
     }
+
+    /**
+     * 保存数据, 仅针对同一节点下的表
+     * @param tableInfoList 表数据
+     * @param flag 是否回滚
+     * @return
+     */
+    @Override
+    public R<Object> saveOrUpdateInfo1(List<TableInfo> tableInfoList,Boolean flag) {
+        List<TableInfo> tableInfoList2 = new ArrayList<>();
+        String fileName1="";
+        if (ListUtils.isNotEmpty(tableInfoList)) {
+            //施工资料填报
+            String pkids = "";
+            StringBuilder log = new StringBuilder("异常:");
+            StringBuilder saveData = new StringBuilder(tableInfoList.toString());
+            try {
+                Set<Long> pKeyIds = tableInfoList.stream().filter(item -> StringUtil.isNumeric(item.getPkeyId())).map(item -> Long.parseLong(item.getPkeyId())).collect(Collectors.toSet());
+                Map<String, WbsTreeContract> contractMap = new HashMap<>();
+                WbsTreeContract wbsTreeContractByP = null;
+                if (!pKeyIds.isEmpty()) {
+                    List<WbsTreeContract> list = wbsTreeContractService.list(Wrappers.<WbsTreeContract>query().lambda().in(WbsTreeContract::getPKeyId, pKeyIds));
+                    contractMap = list.stream().collect(toMap(item -> item.getPKeyId() + "", item -> item, (k1, k2) -> k1));
+                    Set<Long> pIds = list.stream().filter(item -> item.getPId() != null && item.getPId() > 0).map(WbsTreeContract::getPId).collect(Collectors.toSet());
+                    if (!pIds.isEmpty()) {
+                        wbsTreeContractByP = wbsTreeContractService.getOne(Wrappers.<WbsTreeContract>query().lambda().in(WbsTreeContract::getPKeyId, pIds).last( " limit 1"));
+                    }
+                }
+                if (wbsTreeContractByP == null) {
+                    return R.fail("没有找到表单数据对应的节点");
+                }
+                for (TableInfo tableInfo : tableInfoList) {
+                    WbsTreeContract wbsTreeContract = contractMap.get(tableInfo.getPkeyId());
+                    StringBuilder tableName = new StringBuilder("");
+                    if (wbsTreeContract == null) {
+                        continue;
+                    }
+                    tableInfo.setIsTypePrivatePid(wbsTreeContract.getExcelId()+"");
+                    tableInfoList2.add(tableInfo);
+                    pkids += tableInfo.getPkeyId() + ",";
+                    String tabName = wbsTreeContract.getInitTableName();
+                    // 判读修改还是 添加
+                    String delSql = "delete from " + tabName + " where p_key_id=" + tableInfo.getPkeyId();
+                    String sqlInfo = "";
+                    LinkedHashMap<String, String> dataMap2 = tableInfo.getDataMap();
+                    /*检查发现有p_key_id缺失的情况,导致表单数据丢失,所以强制覆盖*/
+                    dataMap2.put("p_key_id", tableInfo.getPkeyId());
+                    dataMap2=isPartition(wbsTreeContract,tabName,dataMap2);
+                    sqlInfo = buildMTableInsertSql(tabName, dataMap2, SnowFlakeUtil.getId(), null, null).toString();
+
+                    UpdateWrapper<WbsTreeContract> updateWrapper = new UpdateWrapper<>();
+                    updateWrapper.in("p_key_id", tableInfo.getPkeyId());
+                    updateWrapper.set("is_tab_pdf", 2);
+                    BladeUser user = AuthUtil.getUser();
+                    TransactionStatus transactionStatus = this.beginTransaction(transactionManager1);
+                    try {
+                        wbsTreeContractService.update(updateWrapper);
+                        //添加被张表的操作日志
+                        StringBuffer sb = new StringBuffer();
+                        String sqlInfoNew = sqlInfo.replace("\"", "'");
+                        sb.append("insert into blade_tab_sql(p_key_id,user_id,create_time,sql_info) VALUES(");
+                        sb.append(tableInfo.getPkeyId());sb.append(",");sb.append(user.getUserId());
+                        sb.append(","); sb.append("SYSDATE()");sb.append(",\"");sb.append(sqlInfoNew);
+                        sb.append("\")");
+
+                        jdbcTemplate.execute(delSql);
+                        jdbcTemplate.execute(sqlInfo.replace("\\","\\\\"));
+                        jdbcTemplate.execute(sb.toString());
+
+                        transactionManager1.commit(transactionStatus);
+                    } catch (Exception e) {
+                        //是否回滚
+                        if(flag){
+                            transactionManager1.rollback(transactionStatus);
+                            log.append(e.getMessage()).append("@@");
+                            e.printStackTrace();
+                            return R.fail(reason(e.getMessage()));
+                        }else {
+                            tableName.append(wbsTreeContract.getNodeName()+",");
+                            RandomNumberHolder.setLogMessage(tableName.toString());
+                        }
+                    }
+                }
+                //处理文件提名
+                fileName1= this.wbsParamService.createFileTitle(wbsTreeContractByP);
+                if(wbsTreeContractByP.getMajorDataType()!=null&&wbsTreeContractByP.getMajorDataType()==4){
+                    String sql1="Select sg_suffix,jl_suffix from m_project_info where id="+wbsTreeContractByP.getProjectId()+" and is_deleted=0";
+                    List<ProjectInfo> query = jdbcTemplate.query(sql1, new BeanPropertyRowMapper<>(ProjectInfo.class));
+                    if(query.size()>0){
+                        if(tableInfoList.get(0).getClassify()!=null&&tableInfoList.get(0).getClassify().equals("1")){
+                            fileName1=fileName1+(query.get(0).getSgSuffix()==null?"":query.get(0).getSgSuffix());
+                        }else {
+                            fileName1=fileName1+(query.get(0).getJlSuffix()==null?"":query.get(0).getJlSuffix());
+                        }
+                    }
+                }
+                //huangjn 保存成功后调用生成资料查询列表数据
+                this.informationQueryClient.saveOrUpdateInformationQueryData(wbsTreeContractByP.getPKeyId() + "", "首件使用字段", "业务ID(主要将来给首件使用)", fileName1, Integer.parseInt(tableInfoList.get(0).getClassify()), 2, "false", "源文件(首件字段)", "pdf文件(首件字段)", "首件上传总结报告名称", new ArrayList<>());
+                JSONObject json = new JSONObject();
+                json.put("operationObjIds", Func.toStrList(pkids));
+                json.put("operationObjName", wbsTreeContractByP.getNodeName() + "节点数据操作");
+                json.put("saveData", saveData.toString());
+                json.put("projectId", wbsTreeContractByP.getProjectId());
+                json.put("contractId", wbsTreeContractByP.getContractId());
+
+                //保存操作记录
+                this.operationLogClient.saveUserOperationLog(1, "资料填报", "工序填报页面", json);
+            } catch (Exception e) {
+                e.printStackTrace();
+                return R.fail("操作失败");
+            }
+            if (log.length() > 5) {
+                return R.fail(reason(log.toString()));
+            }
+        }
+        return R.data(tableInfoList2);
+    }
     public LinkedHashMap<String ,String> isPartition(WbsTreeContract wbsTreeContract,String tabName,LinkedHashMap<String, String> dataMap2){
         //判断是否需要移除编号 当工序 子分项 分项的划分编码有一个不为null就移除编号
         String sqlContractInfo="select * from m_contract_info where id="+wbsTreeContract.getContractId();
@@ -2989,7 +3115,8 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
     public void getBussPdfs(String nodeId, String classify, String contractId, String projectId) throws Exception {
         String file_path = FileUtils.getSysLocalFileUrl();
         // 获取有权限的节点信息
-        List<AppWbsTreeContractVO> wbsTreeContractList = wbsTreeContractService.searchNodeAllTable(nodeId, classify, contractId, projectId, null);
+//        List<AppWbsTreeContractVO> wbsTreeContractList = wbsTreeContractService.searchNodeAllTable(nodeId, classify, contractId, projectId, null);
+        List<AppWbsTreeContractVO> wbsTreeContractList = wbsTreeContractService.searchNodeAllTable(nodeId, classify);
         List<String> data = new ArrayList<>();
         if (nodeId.indexOf(":") >= 0) {
             nodeId = nodeId.split(":")[0];
@@ -3019,8 +3146,12 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         }
         if (data.size() >= 1) {
             //资料填报原始pdf合并
-            FileUtils.mergePdfPublicMethods(data, listPdf);
+//            FileUtils.mergePdfPublicMethods(data, listPdf);
+            long startTime = System.currentTimeMillis();
+            FileUtils.mergePDFParallel(data, listPdf);
+            long endTime = System.currentTimeMillis();
             BladeFile bladeFile = this.newIOSSClient.uploadFile(nodeId + ".pdf", listPdf);
+            System.out.println("pdf合并耗时:" + (endTime - startTime) + "ms, pdf合并个数:" + data.size() + ", pdf 上传耗时:" + (System.currentTimeMillis() - endTime) + "ms");
             if(bladeFile == null){
                 System.out.println("Oss上传失败");
             }

+ 12 - 4
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -237,7 +237,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             tec.constantMap.put(WP, getWpMap(one, tec));
 
             /*表格名称*/
-            List<AppWbsTreeContractVO> tableList = wbsTreeContractService.searchNodeAllTable(one.getPkId().toString(), "1", tec.getContractId().toString(), tec.getProjectId().toString(), null);
+            List<AppWbsTreeContractVO> tableList = wbsTreeContractService.searchNodeAllTable(one.getPkId().toString(), "1");
             tec.constantMap.put(TABLE_LIST, tableList);
 
             /*监表质量附件,过滤掉隐藏表格*/
@@ -445,7 +445,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                                 /*手写部分的数据加载*/
                                 if (t.getPoint().contains("混凝土强度") && false) {
 //                                    List<Long> ids = getNodeType46(tec).stream().map(CurrentNode::getPkId).collect(Collectors.toList());
-                                    /*质检附表m_20230316104657_1636197331206406144*/
+                                    /*质检m_20230316104657_1636197331206406144*/
                                     List<Map<String, Object>> listMaps = this.jdbcTemplate.queryForList("select a.p_key_id,b.key_10 name,b.key_6 design,b.key_5 data from m_wbs_tree_contract a join m_20230316104657_1636197331206406144 b on a.p_key_id=b.p_key_id where  a.is_deleted=0 and a.parent_id =" + tec.getCurrentNode().getId());
                                     if (listMaps.size() > 0) {
                                         Map<String, String> coordsMap = FormulaUtils.getElementCell("/www/wwwroot/Users/hongchuangyanfa/Desktop/privateUrl/1636291886580760576.html");
@@ -1894,6 +1894,16 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             List<FormData> subTableFds = tec.pick(e -> StringUtils.isEquals(first.getInitTableName(), e.getTableName()));
             /*初始化附表对象*/
             SubTable sta = new SubTable(subTableFds, tec, mainTableList);
+            // 频率 数据的处理
+
+            List<Map<String, Object>> mapList = jdbcTemplate.queryForList("select * from m_formula_option where id = " + tec.getNodeId());
+            if(mapList!=null && mapList.size()>0){
+                String initTableName = mainTableList.get(0).getInitTableName();
+                String plStr = mapList.get(0).get("val")+"";
+                if(plStr!=null && plStr.length()>10 && plStr.contains(initTableName)){
+                    sta.setPlStr(plStr);
+                }
+            }
             sta.put(inspectionList);
             /*把附表数据刷入对应的附表元素对象*/
             sta.flush();
@@ -1905,8 +1915,6 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             /*把主表的表头表尾信息拷贝*/
             headerFooterSub(subTableFds, tec);
             /*如果识别到手填内容需要在附表写入数据后,更新评定关联数据*/
-
-
             if (StringUtils.isNotEmpty(tec.getIsRemoveForSubTab()) && "1".equals(tec.getIsRemoveForSubTab())) {
                 if (!subTabList.isEmpty()) {
                     String queryIds = subTabList.stream().map(e -> e.getPKeyId() + "").collect(Collectors.joining(","));

+ 75 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -671,6 +671,67 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
         return resultTabs;
     }
 
+    /**
+     * 根据节点pKeyId获取节点下所属 tableOwer 表单
+     * 没有用户填报信息
+     */
+    @Override
+    public List<AppWbsTreeContractVO> searchNodeAllTable(String primaryKeyId, String tableOwner) {
+        WbsTreeContract wbsTreeContract = baseMapper.selectOne(Wrappers.<WbsTreeContract>query().lambda()
+                .eq(WbsTreeContract::getPKeyId, primaryKeyId));
+        if (wbsTreeContract == null) {
+            return Collections.emptyList();
+        }
+        //接口请求表单类型(施工质检 或 监理抽检)
+        Set<String> tabTableOwnerSets = new HashSet<>();
+        /*tableOwner!=null,表示从资料填报查询,查权限数据,roleTableOwnerSets=取交集后*/
+        if (StringUtils.isNotEmpty(tableOwner)) {
+            String tableOwners;
+            if (tableOwner.equals("1")) {
+                tableOwners = "1,2,3";
+            } else if (tableOwner.equals("2")) {
+                tableOwners = "4,5,6";
+            } else {
+                tableOwners = "7,8,9";
+            }
+            tabTableOwnerSets.addAll(Func.toStrList(tableOwners));
+        }
+        //判断是客户端还是APP
+        List<AppWbsTreeContractVO> resultTabs;
+        //其他接口调用默认保持原始调用方式查询
+        if (!tabTableOwnerSets.isEmpty()) {
+            List<WbsTreeContract> wbsTreeContracts = baseMapper.selectList(Wrappers.<WbsTreeContract>lambdaQuery()
+                    .eq(WbsTreeContract::getProjectId, wbsTreeContract.getProjectId()).eq(WbsTreeContract::getWbsId, wbsTreeContract.getWbsId())
+                    .eq(WbsTreeContract::getContractId, wbsTreeContract.getContractId()).eq(WbsTreeContract::getParentId, wbsTreeContract.getId()).eq(WbsTreeContract::getType, 2)
+                    .eq(WbsTreeContract::getStatus, 1).eq(StringUtil.hasText(wbsTreeContract.getContractIdRelation()) , WbsTreeContract::getContractId, wbsTreeContract.getContractIdRelation())
+                    .in(WbsTreeContract::getTableOwner, tabTableOwnerSets).last(" ORDER BY sort,full_name,create_time"));
+            resultTabs = wbsTreeContracts.stream().map(item -> {
+                AppWbsTreeContractVO appWbsTreeContractVO = new AppWbsTreeContractVO();
+                BeanUtil.copy(item, appWbsTreeContractVO);
+                return appWbsTreeContractVO;
+            }).collect(Collectors.toList());
+        } else {
+            resultTabs = new ArrayList<>();
+        }
+        if (ObjectUtil.isNotEmpty(wbsTreeContract.getMajorDataType()) && wbsTreeContract.getMajorDataType().equals(2)) {
+            resultTabs.sort(Comparator.comparing((WbsTreeContract contract) -> contract.getNodeName().contains("JS107"))
+                    .thenComparing(WbsTreeContract::getNodeName));
+        }
+        if (Optional.ofNullable(wbsTreeContract.getIsUseSort()).orElse(0) == 0) {
+            //表单排序
+            if (!resultTabs.isEmpty()) {
+                sortTabsByIsTypePrivatePid(resultTabs);
+                // 使用自定义 Comparator 进行排序
+                resultTabs.sort(new WbsTreeContractComparator());
+                List<AppWbsTreeContractVO> resultTabsToCopy = this.sortTabs(resultTabs, "__"); //复制表排序
+                return this.sortTabs(resultTabsToCopy, "_PL_"); //频率表排序
+            }
+        }
+        // 使用自定义 Comparator 进行排序
+        resultTabs.sort(new WbsTreeContractComparator());
+        return resultTabs;
+    }
+
     private void sortTabsByIsTypePrivatePid(List<AppWbsTreeContractVO> resultTabs) {
         if(!resultTabs.isEmpty()){
            StringBuilder ids=new StringBuilder("");
@@ -3139,7 +3200,7 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
             int size = list.size();
             Long importId=SnowFlakeUtil.getId();
             try{
-                String sqlList1 = "Select parent_id,node_name,p_id from m_wbs_tree_contract where contract_id=" + wbsTreeContractRoot.getContractId() + " and wbs_id=" + wbsTreeContractRoot.getWbsId() + " and is_deleted=0";
+                String sqlList1 = "Select parent_id,node_name,p_id ,node_type from m_wbs_tree_contract where contract_id=" + wbsTreeContractRoot.getContractId() + " and wbs_id=" + wbsTreeContractRoot.getWbsId() + " and is_deleted=0";
                 List<WbsTreeContract> WbsTreeContractList = jdbcTemplate.query(sqlList1, new BeanPropertyRowMapper<>(WbsTreeContract.class));
                 for (ImportTreeDto dto : list) {
                     if(size-i>=1){
@@ -3816,22 +3877,31 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
     private Boolean isExist(ImportTreeDto dto, List<WbsTreeContract> wbsTreeContractList, int type, WbsTreeContract tree) {
         Long contractId = Long.valueOf(tree.getContractId());
         String nodeName = "";
+        Integer nodeType;
         if (type == 1) {
             nodeName = dto.getUnitName();
+            nodeType=1;
         } else if (type == 2) {
             nodeName = dto.getSubUnitName();
+            nodeType=18;
         } else if (type == 3) {
             nodeName = dto.getDivisionName();
+            nodeType=2;
         } else if (type == 4) {
             nodeName = dto.getSubDivisionName();
+            nodeType=3;
         } else if (type == 5) {
             nodeName = dto.getItemName();
+            nodeType=4;
         } else if (type == 6) {
             nodeName = dto.getSubItemName();
+            nodeType=5;
+        } else {
+            nodeType = 0;
         }
         String finalNodeName = nodeName;
         List<Long> list = wbsTreeContractList.stream()
-                .filter(item -> finalNodeName.equals(item.getNodeName()))
+                .filter(item -> finalNodeName.equals(item.getNodeName())&&item.getNodeType()==nodeType)
                 .map(item -> item.getPId())
                 .collect(Collectors.toList());
         if (list.size() == 0) {
@@ -3863,6 +3933,9 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                     ids = Arrays.stream(ids)
                             .filter(i -> !i.equals("0"))
                             .toArray(String[]::new);
+                    if(ids.length<=0){
+                        return false;
+                    }
                     String sql1 = "Select node_name from m_wbs_tree_contract where p_key_id in(" + String.join(",", ids) + ") and contract_id=" + wbsContract.getContractId() + " and is_deleted=0";
                     List<String> nodeNames = jdbcTemplate.query(sql1, new SingleColumnRowMapper<>(String.class));
                     String join = String.join("", nodeNames);

+ 109 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/utils/FileUtils.java

@@ -55,6 +55,7 @@ import java.net.URLEncoder;
 import java.nio.file.*;
 import java.util.List;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Matcher;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
@@ -302,6 +303,114 @@ public class FileUtils {
             doc.close();
         }
     }
+    private static void mergeChunkSequentially(List<String> urlList, String outputFile) {
+        Document doc = new Document();
+        PdfCopy pdfCopy = null;
+        try {
+            pdfCopy = new PdfCopy(doc, Files.newOutputStream(Paths.get(outputFile)));
+            doc.open();
+            for (String urlStr : urlList) {
+                try (InputStream inputStream = CommonUtil.getOSSInputStream(urlStr)) {
+                    if (inputStream == null) {
+                        continue;
+                    }
+                    PdfReader reader = new PdfReader(inputStream);
+                    PdfReader.unethicalreading = true;
+
+                    int pageCount = reader.getNumberOfPages();
+                    for (int i = 1; i <= pageCount; i++) {
+                        pdfCopy.addPage(pdfCopy.getImportedPage(reader, i));
+                    }
+                    reader.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            doc.close();
+            if (pdfCopy != null) {
+                pdfCopy.close();
+            }
+        }
+    }
+
+    private static void mergeTempFiles(List<String> tempFiles, String outputFile) {
+        Document doc = new Document();
+        PdfCopy pdfCopy = null;
+        try {
+            pdfCopy = new PdfCopy(doc, new FileOutputStream(outputFile));
+            doc.open();
+
+            for (String tempFile : tempFiles) {
+                try (FileInputStream fis = new FileInputStream(tempFile)) {
+                    PdfReader reader = new PdfReader(fis);
+                    int pageCount = reader.getNumberOfPages();
+                    for (int i = 1; i <= pageCount; i++) {
+                        pdfCopy.addPage(pdfCopy.getImportedPage(reader, i));
+                    }
+                    reader.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            doc.close();
+            if (pdfCopy != null) {
+                pdfCopy.close();
+            }
+        }
+    }
+    public static void mergePDFParallel(List<String> urlList, String localImgUrl) {
+        int chunkSize = 50; // 每块处理的PDF数量
+        long tempId = SnowFlakeUtil.getId();
+        try {
+            if (urlList.size() % chunkSize != 0) {
+                int numChunks =  urlList.size() / chunkSize + 1;
+                chunkSize = urlList.size() / numChunks + 1;
+            }
+            // 将URL列表分块
+            List<Map<Integer, List<String>>> chunks = new ArrayList<>();
+            for (int i = 0; i < urlList.size(); i += chunkSize) {
+                int end = Math.min(i + chunkSize, urlList.size());
+                Map<Integer, List<String>> chunkMap = new HashMap<>();
+                chunkMap.put(i,urlList.subList(i, end));
+                chunks.add(chunkMap);
+            }
+            // 并行处理每个块,生成临时文件
+            Map<String, Integer> tempFilesSortMap = new ConcurrentHashMap<>();
+            chunks.parallelStream().forEach(chunk -> {
+                try {
+                    String tempFile = System.getProperty("java.io.tmpdir") + "/temp_" + tempId + "_" + SnowFlakeUtil.getId() + ".pdf";
+                    chunk.forEach((key, value) -> {
+                        tempFilesSortMap.put(tempFile, key);
+                        mergeChunkSequentially(value, tempFile);
+                    });
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            });
+            if (!tempFilesSortMap.isEmpty()) {
+                List<String> tempFiles = new ArrayList<>(tempFilesSortMap.keySet());
+                tempFiles.sort(Comparator.comparingInt(tempFilesSortMap::get));
+                // 最后合并所有临时文件
+                mergeTempFiles(tempFiles, localImgUrl);
+                // 清理临时文件
+                tempFiles.forEach(tempFile -> {
+                    try {
+                        Files.deleteIfExists(Paths.get(tempFile));
+                    } catch (IOException e) {
+                        // ignore
+                    }
+                });
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
 
     /**
      * Excel 转 PDF(完整缩放到 A4 纸张)

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

@@ -0,0 +1,219 @@
+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();
+    }
+}
+