Browse Source

Merge branch 'dev' of http://219.151.181.73:3000/zhuwei/bladex into dev

laibulaizheli 5 days ago
parent
commit
a99c32fb1c
100 changed files with 5058 additions and 438 deletions
  1. 159 37
      blade-common/src/main/java/org/springblade/common/utils/CommonUtil.java
  2. 13 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/SaveVolumeDto.java
  3. 10 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/SaveVolumeDto1.java
  4. 21 15
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ArchivesAuto.java
  5. 8 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/feign/ArchiveAutoClient.java
  6. 20 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ArchiveExaminingVo.java
  7. 14 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto1.java
  8. 13 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto2.java
  9. 13 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto3.java
  10. 18 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto4.java
  11. 17 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java
  12. 7 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/TrialSelfInspectionRecord.java
  13. 5 2
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java
  14. 4 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/MetadataClassificationClient.java
  15. 300 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/utils/DigestUtil.java
  16. 69 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/InformationImportRecord.java
  17. 7 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/WbsFormElement.java
  18. 3 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ArchiveTreeContractClient.java
  19. 10 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ContractClient.java
  20. 4 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ExcelTabClient.java
  21. 5 1
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ExcelTabClientFallBack.java
  22. 7 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/SaveUserInfoByProjectClient.java
  23. 35 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InformationImportRecordVO.java
  24. 59 0
      blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ExcelUserTemp.java
  25. 32 0
      blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ImportPorjectInfoDTO.java
  26. 19 0
      blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ImportUserLog.java
  27. 58 0
      blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/UserImporterNew.java
  28. 5 0
      blade-service/blade-archive/pom.xml
  29. 14 9
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveExaminingReportController.java
  30. 22 4
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java
  31. 5 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/feign/ArchiveAutoClientImpl.java
  32. 9 4
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java
  33. 6 6
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveAutoPdfServiceImpl.java
  34. 534 144
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveExaminingReportImpl.java
  35. 2 2
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveOfflineVersionInfoServiceImpl.java
  36. 193 32
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java
  37. 49 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ClamAVClientScanner.java
  38. 97 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileAnalysisResult.java
  39. 352 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileFormatSecurityChecker.java
  40. 33 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileExtension.java
  41. 306 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileMD5Calculator.java
  42. 114 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileSecurityChecker.java
  43. 25 0
      blade-service/blade-business/src/main/java/org/springblade/business/controller/ArchiveFileController.java
  44. 6 1
      blade-service/blade-business/src/main/java/org/springblade/business/controller/EVisaTaskCheckController.java
  45. 8 16
      blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java
  46. 15 0
      blade-service/blade-business/src/main/java/org/springblade/business/controller/TrialDetectionController.java
  47. 37 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java
  48. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ContractLogClientImpl.java
  49. 7 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/MetadataClassificationClientImpl.java
  50. 2 0
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.java
  51. 19 22
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.xml
  52. 2 2
      blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.xml
  53. 8 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/IArchiveFileService.java
  54. 3 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/IMetadataClassificationService.java
  55. 3 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/ITrialSelfInspectionRecordService.java
  56. 129 1
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java
  57. 4 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/MetadataClassificationServiceImpl.java
  58. 138 4
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java
  59. 194 3
      blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java
  60. 63 39
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/Archive2Controller.java
  61. 1 1
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/ChekSignData.java
  62. 1 1
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/controller/EVController.java
  63. 16 7
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/service/impl/EVDataServiceImpl.java
  64. 4 4
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/FileUtils.java
  65. 2 1
      blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PDFUtils.java
  66. 1 1
      blade-service/blade-land/src/main/java/org/springblade/land/mapper/CompensationInfoMapper.xml
  67. 10 14
      blade-service/blade-manager/src/main/java/com/mixsmart/utils/CustomFunction.java
  68. 13 0
      blade-service/blade-manager/src/main/java/com/mixsmart/utils/FormulaUtils.java
  69. 141 19
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  70. 773 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/InformationImportRecordController.java
  71. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/LinkdataInfoController.java
  72. 8 16
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/NodeBaseInfoController.java
  73. 335 24
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java
  74. 142 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreePrivateController.java
  75. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ArchiveTreeContractImpl.java
  76. 20 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ContractClientImpl.java
  77. 27 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ExcelTabClientImpl.java
  78. 29 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/feign/SaveUserInfoByProjectClientImpl.java
  79. 4 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/KeyMapper.java
  80. 11 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/SubTable.java
  81. 3 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.java
  82. 13 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml
  83. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.java
  84. 10 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.xml
  85. 26 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/InformationImportRecordMapper.java
  86. 3 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ProjectInfoMapper.java
  87. 9 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ProjectInfoMapper.xml
  88. 3 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeMapper.xml
  89. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml
  90. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IArchiveTreeContractService.java
  91. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IContractInfoService.java
  92. 2 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IExcelTabService.java
  93. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IProjectInfoService.java
  94. 30 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/InformationImportRecordService.java
  95. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractServiceImpl.java
  96. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ContractInfoServiceImpl.java
  97. 25 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  98. 1 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  99. 27 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/InformationImportRecordServiceImpl.java
  100. 3 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ProfilerOffsetServiceImpl.java

+ 159 - 37
blade-common/src/main/java/org/springblade/common/utils/CommonUtil.java

@@ -1,6 +1,7 @@
 package org.springblade.common.utils;
 
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.extra.spring.SpringUtil;
 import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
@@ -14,6 +15,7 @@ import org.apache.commons.imaging.ImageReadException;
 import org.apache.commons.imaging.Imaging;
 import org.apache.commons.lang.StringUtils;
 import org.springblade.common.constant.CommonConstant;
+import org.springframework.context.ApplicationContext;
 import org.springframework.util.CollectionUtils;
 
 import java.awt.*;
@@ -174,7 +176,7 @@ public class CommonUtil {
     public static InputStream getOSSInputStream(String urlStr) {
         try {
             System.out.println("----前-------"+urlStr);
-            urlStr = replaceOssUrl(urlStr);
+            urlStr = replaceOssUrl1(urlStr);
             int lastIndex = urlStr.lastIndexOf("/") + 1;
             String fileName = urlStr.substring(lastIndex);
             urlStr = urlStr.substring(0, lastIndex) + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
@@ -183,6 +185,7 @@ public class CommonUtil {
             URLConnection conn = url.openConnection();
 
             conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
+            System.out.println("-----后------"+urlStr);
             return conn.getInputStream();
         } catch (Exception e) {
             System.out.println("-----后------"+urlStr);
@@ -193,7 +196,7 @@ public class CommonUtil {
         try {
             System.out.println("----前-------"+urlStr);
             if (type == 1) {
-                urlStr = replaceOssUrl(urlStr);
+                urlStr = replaceOssUrl1(urlStr);
             }
             int lastIndex = urlStr.lastIndexOf("/") + 1;
             String fileName = urlStr.substring(lastIndex);
@@ -1018,6 +1021,47 @@ public class CommonUtil {
         return workDays;
     }
 
+    public static void main1(String[] args) {
+        String[] urls = new String[] {
+//                "null","",null,
+//                "https://xinan1.zos.ctyun.cn/blade-oss-chongqing/upload/20250912/6e06f82609f6f4be11534485920b5e6f.pdf",
+//                "/mnt/sdc/Users/hongchuangyanfa/Desktop/privateUrlCopy/1904814720589430785/1963431259535310848.html",
+//                "/home/www/wwwroot/Users/hongchuangyanfa/Desktop/privateUrlCopy/1750070685257990145/1845013941906833408.html",
+//                "http://183.247.216.148:9000/minio-oss-chongqing/upload/20251204/e69bb9adfba91c85b6bde9ae70d5808a.pdf",
+                "http://100.86.2.1/blade-oss-chongqing/upload/20250912/6e06f82609f6f4be11534485920b5e6f.pdf",
+                "http://152.168.2.15:9000/minio-oss-chongqing/upload/20251204/e69bb9adfba91c85b6bde9ae70d5808a.pdf"
+
+        };
+        String[] isOnlineArr = new String[] {
+//                null, "", "null","1", "2", "20"
+                "1", "2", "20"
+        };
+        String[] sysLocalUrlArr = new String[] {
+//                null, "","null" ,"/mnt/sdc/Users/hongchuangyanfa/Desktop/", "/home/www/wwwroot/Users/hongchuangyanfa/Desktop/"
+                "/mnt/sdc/Users/hongchuangyanfa/Desktop/", "/home/www/wwwroot/Users/hongchuangyanfa/Desktop/"
+        };
+        String[] sysFileNetUrlArr = new String[] {
+//                null, "","null","http://183.247.216.148:22776/", "http://fileinfo.hczcxx.cn/"
+                "http://183.247.216.148:22776/", "http://fileinfo.hczcxx.cn/"
+        };
+        String[] osArr = new String[] {"Linux", "Windows", "Mac"};
+
+        for (String os : osArr) {
+            System.setProperty("os.name", os);
+            for (String isOnline : isOnlineArr) {
+                for (String sysLocalUrl : sysLocalUrlArr) {
+                    for (String sysFileNetUrl : sysFileNetUrlArr) {
+                        for (String url : urls) {
+                            String netUrl = getNetUrl(url, isOnline, sysFileNetUrl, sysLocalUrl);
+                            System.out.println( "os:" + os + ", isOnline:" + isOnline + ", sysLocalUrl:" + sysLocalUrl + ", sysFileNetUrl:" + sysFileNetUrl + ", url:" + url);
+                            System.out.println("result:" + getNetUrl(netUrl, isOnline, sysFileNetUrl, sysLocalUrl));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public static String replaceOssUrl(String url) {
         //本地部署- 甬台温
         if (url.indexOf("183.247.216.148") >= 0 || url.indexOf("152.168.2.15") >= 0) {
@@ -1027,68 +1071,146 @@ public class CommonUtil {
             } else {
                 url = url.replace("https://", "http://").replace("183.247.216.148", "152.168.2.15").replace(":9000//", ":9000/");
             }
+        }
+        return url;
+    }
+    public static String replaceOssUrl1(String url) {
+        String sysLocalUrl = getCacheValue(CommonConstant.SYS_LOCAL_URL);
+        String sysFileNetUrl = getCacheValue(CommonConstant.SYS_FILE_NET_URL);
+        String sysIsOnline = getCacheValue(CommonConstant.SYS_ISONLINE);
+        String resultUrl = "";
+        if (StringUtils.isBlank(sysIsOnline)) {
+            resultUrl =  replaceOssUrl( url);
         } else {
-            getNetUrl(url);
+            resultUrl =  getNetUrl(url, sysIsOnline, sysFileNetUrl, sysLocalUrl);
+        }
+        System.out.println("--------------resultUrl:" + resultUrl + ",sysIsOnline:" + sysIsOnline + ",sysFileNetUrl:" + sysFileNetUrl + ",sysLocalUrl:" + sysLocalUrl + ",url:" + url);
+        return resultUrl;
+    }
+    public static String getNetUrl(String url, String sysIsOnline, String sysFileNetUrl, String sysLocalUrl) {
+        if (StringUtils.isBlank(url)) {
+            return url;
         }
+        if (SystemUtils.isLinux()) {
+            if ("1".equals(sysIsOnline)) {
+                // 210
+                if (url.contains("xinan1.zos.ctyun.cn") || url.contains("aliyuncs.com")) {
+                    // 正式环境,走内网
+                    return url.replace("https://", "http://").replace("xinan1.zos.ctyun.cn", "100.86.2.1");
+                } else if (url.contains("152.168.2.15")) {
+                    return url.replace("https://", "http://").replace("152.168.2.15","183.247.216.148").replace(":9000//", ":9000/");
+                }
+            } else if ("20".equals(sysIsOnline)) {
+                // 183
+                if (url.contains("183.247.216.148") || url.contains("152.168.2.15")) {
+                    return url.replace("https://", "http://").replace("183.247.216.148", "152.168.2.15").replace(":9000//", ":9000/");
+                } else if (url.contains("100.86.2.1")) {
+                    return url.replace("https://", "http://").replace("100.86.2.1","xinan1.zos.ctyun.cn");
+                }
+            } else if ("2".equals(sysIsOnline)){
+                if (url.contains("152.168.2.15")) {
+                    return url.replace("https://", "http://").replace("152.168.2.15","183.247.216.148").replace(":9000//", ":9000/");
+                }
+                if (url.contains("100.86.2.1")) {
+                    return url.replace("https://", "http://").replace("100.86.2.1","xinan1.zos.ctyun.cn");
+                }
+            }
+        } else  {
+            if (url.contains("152.168.2.15")) {
+                return url.replace("https://", "http://").replace("152.168.2.15","183.247.216.148").replace(":9000//", ":9000/");
+            }
+            if (url.contains("100.86.2.1")) {
+                return url.replace("https://", "http://").replace("100.86.2.1","xinan1.zos.ctyun.cn");
+            }
+            if (url.contains("xinan1.zos.ctyun.cn") || url.contains("aliyuncs.com") || url.contains("183.247.216.148")) {
+                return url;
+            }
+            if (StringUtils.isBlank(sysFileNetUrl) || sysFileNetUrl.equals("null") || url.contains(sysFileNetUrl)) {
+                return url;
+            }
+            if (url.startsWith("https://") || url.startsWith("http://")) {
+                return url;
+            }
+            //本地环境
+            String fileUrl = getSysLocalFileUrl(sysLocalUrl, sysIsOnline);
+            if(url.contains("/mnt/sdc/Users/hongchuangyanfa/Desktop/")){
+                fileUrl = sysLocalUrl;
+            }
+            if("20".equals(sysIsOnline) && sysLocalUrl != null && !sysLocalUrl.equals("null")){
+                fileUrl = sysLocalUrl;
+            }
+            String s1 = url.replaceAll("//", "/").replace("///","/");
+            if (fileUrl == null || fileUrl.equals("null")) {
+                fileUrl = "";
+            } else {
+                fileUrl= fileUrl.replaceAll("//","/").replaceAll("///","/");
+            }
+            String s2= s1.replaceAll(fileUrl, "");
+            return sysFileNetUrl + s2;
+        }
+
         return url;
     }
     public static String getCacheValue(String key){
         try {
             if (paramCacheMethod != null && paramCacheObj != null) {
-                return (String) paramCacheMethod.invoke(paramCacheObj, key);
+                Object object = paramCacheMethod.invoke(paramCacheObj, key);
+                if (object != null) {
+                    return (String) object ;
+                }
             }
-        } catch (IllegalAccessException | InvocationTargetException e) {
+            return getValueFromRedis( key);
+        } catch (Exception e) {
             e.printStackTrace();
         }
         return "";
     }
-    public static String getNetUrl(String fileUrl) {
-        String filePath = getCacheValue(CommonConstant.SYS_LOCAL_URL);
-        String sysFileNetUrl = getCacheValue(CommonConstant.SYS_FILE_NET_URL);
-        String sysIsOnline = getCacheValue(CommonConstant.SYS_ISONLINE);
-        if (StringUtils.isBlank(filePath) || StringUtils.isBlank(sysFileNetUrl) || StringUtils.isBlank(sysIsOnline)) {
-            return fileUrl;
-        }
-        String filePath2 = getSysLocalFileUrl(filePath, sysIsOnline);
-        if(fileUrl.contains("aliyuncs.com") || fileUrl.contains("xinan1.zos.ctyun.cn") || fileUrl.contains("/mnt/sdc/Users/hongchuangyanfa/Desktop/")){
-            if(fileUrl.contains("/mnt/sdc/Users/hongchuangyanfa/Desktop/")){
-                if(SystemUtils.isWindows() || SystemUtils.isMacOs()){
-                    filePath2 = filePath;
-                }else{
-                    return sysFileNetUrl + fileUrl.replaceAll("//", "/").replaceAll(filePath2, "");
-                }
-            }else{
-                if (SystemUtils.isLinux() && ("1".equals(sysIsOnline) || sysIsOnline.equals("20"))) {
-                    // 正式环境,走内网
-                    return fileUrl.replace("https://", "http://").replace("xinan1.zos.ctyun.cn", "100.86.2.1");
-                } else {
-                    return fileUrl;
-                }
+    /**
+     * 通过反射获取RedisTemplate并执行get操作
+     */
+    public static String getValueFromRedis(String key) {
+        try {
+            Object redisTemplate = SpringUtil.getBean("redisTemplate");
+            if (redisTemplate == null) {
+                return "";
             }
-        } else {
-            if("20".equals(sysIsOnline)){
-                filePath2 = filePath;
+            // 直接使用RedisTemplate的boundValueOps方法
+            Method boundValueOpsMethod = redisTemplate.getClass().getMethod("boundValueOps", Object.class);
+            boundValueOpsMethod.setAccessible(true);
+            Object boundValueOperations = boundValueOpsMethod.invoke(redisTemplate, "blade:param::param:value:" + key);
+
+            // 获取值
+            Method getMethod = boundValueOperations.getClass().getMethod("get");
+            getMethod.setAccessible(true);
+            Object result = getMethod.invoke(boundValueOperations);
+
+            if (result == null) {
+                boundValueOperations = boundValueOpsMethod.invoke(redisTemplate, "000000:blade:param::param:value:" + key);
+                // 获取值
+                getMethod = boundValueOperations.getClass().getMethod("get");
+                getMethod.setAccessible(true);
+                result = getMethod.invoke(boundValueOperations);
             }
+            return result == null ? "" : result.toString();
+        } catch (Exception e) {
+            e.printStackTrace();
         }
-        String s1 = fileUrl.replaceAll("//", "/").replace("///","/");
-        filePath2= filePath2.replaceAll("//","/").replaceAll("///","/");
-        String s2= s1.replaceAll(filePath2, "");
-        return sysFileNetUrl + s2;
+        return "";
     }
     public static String getSysLocalFileUrl(String filePath, String sysIsOnline) {
-        if (sysIsOnline.equals("1")) { //正式环境
+        if ("1".equals(sysIsOnline)) { //正式环境
             if (SystemUtils.isMacOs()) {
                 filePath = "/Users/hongchuangyanfa/Desktop/";
             } else if (SystemUtils.isWindows()) {
                 filePath = "C://upload//";
             }
-        } else if (sysIsOnline.equals("2")) { //109测试环境
+        } else if ("2".equals(sysIsOnline)) { //109测试环境
             if (SystemUtils.isMacOs()) {
                 filePath = "/www/wwwroot/Users/hongchuangyanfa/Desktop/";
             } else if (SystemUtils.isWindows()) {
                 filePath = "C://upload//";
             }
-        } else if (sysIsOnline.equals("20")) { //183
+        } else if ("20".equals(sysIsOnline)) { //183
             if (SystemUtils.isLinux()) {
                 filePath = "/home/www/wwwroot/Users/hongchuangyanfa/Desktop/";
             } else if (SystemUtils.isMacOs()) {

+ 13 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/SaveVolumeDto.java

@@ -0,0 +1,13 @@
+package org.springblade.archive.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class SaveVolumeDto {
+    private String archiveId;
+    private String archiveName;
+    private List<SaveVolumeDto1> list=new ArrayList<>();
+}

+ 10 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/dto/SaveVolumeDto1.java

@@ -0,0 +1,10 @@
+package org.springblade.archive.dto;
+
+import lombok.Data;
+
+@Data
+public class SaveVolumeDto1 {
+    private Long id;
+    private String archiveName;
+    private String fileIds;
+}

+ 21 - 15
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ArchivesAuto.java

@@ -80,13 +80,13 @@ public class ArchivesAuto extends BaseEntity {
     /**
      * 文件起始时间
      */
-    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime startDate;
+    //@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd")
+    private String startDate;
     /**
      * 文件结束时间
      */
-    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime endDate;
+    //@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyyMMdd")
+    private String endDate;
     /**
      * 保管期限(单位/年)
      */
@@ -192,6 +192,12 @@ public class ArchivesAuto extends BaseEntity {
 
     @ApiModelProperty("案卷操作(0:无,1:正在并卷,2:正在重组,3:等待重组(拆卷完成后),-1:并卷失败,-2:重组失败)")
     private Integer actionType;
+
+    @ApiModelProperty("分卷ID,逗号分隔")
+    private String volumeIds;
+    @ApiModelProperty("是否是分卷0否 1是 2是子分卷")
+    private Integer isVolume;
+
     //是否是影音
     public boolean isMedia() {
         return (this.getCarrierType() != null &&
@@ -210,17 +216,17 @@ public class ArchivesAuto extends BaseEntity {
         BeanUtils.copyProperties(autoVo, this);
 
         // 2. 手动处理类型不兼容的字段
-        // Date -> LocalDateTime 转换
-        if (autoVo.getStartDate() != null) {
-            this.setStartDate(autoVo.getStartDate().toInstant()
-                    .atZone(ZoneId.systemDefault())
-                    .toLocalDateTime());
-        }
-        if (autoVo.getEndDate() != null) {
-            this.setEndDate(autoVo.getEndDate().toInstant()
-                    .atZone(ZoneId.systemDefault())
-                    .toLocalDateTime());
-        }
+         //Date -> LocalDateTime 转换
+//        if (autoVo.getStartDate() != null) {
+//            this.setStartDate(autoVo.getStartDate().toInstant()
+//                    .atZone(ZoneId.systemDefault())
+//                    .toLocalDateTime());
+//        }
+//        if (autoVo.getEndDate() != null) {
+//            this.setEndDate(autoVo.getEndDate().toInstant()
+//                    .atZone(ZoneId.systemDefault())
+//                    .toLocalDateTime());
+//        }
         if (autoVo.getReviewDate() != null) {
             this.setReviewDate(autoVo.getReviewDate().toInstant()
                     .atZone(ZoneId.systemDefault())

+ 8 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/feign/ArchiveAutoClient.java

@@ -49,5 +49,13 @@ public interface ArchiveAutoClient {
     @PostMapping(API_PREFIX + "/removeArchivesByNodeIds")
     void removeArchivesByNodeIds(@RequestBody List<Long> ids);
 
+    /**
+     * 根据ID获取档案案卷
+     * @param id
+     * @return
+     */
+    @PostMapping(API_PREFIX + "/getArchiveById")
+    ArchivesAuto getArchiveById(@RequestParam Long id);
+
 
 }

+ 20 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ArchiveExaminingVo.java

@@ -12,6 +12,8 @@ import lombok.Data;
 public class ArchiveExaminingVo {
     @ApiModelProperty("项目ID")
     private Long projectId;
+    @ApiModelProperty("合同段ID")
+    private Long contractId;
 
     @ApiModelProperty("报告ID")
     private Long reportId;
@@ -19,12 +21,30 @@ public class ArchiveExaminingVo {
     @ApiModelProperty("真实性")
     private String authenticity;
 
+    @ApiModelProperty("真实性-检测项")
+    private String authenticityList;
+
     @ApiModelProperty("完整性")
     private String integrality;
 
+    @ApiModelProperty("完整性-检测项")
+    private String integralityList;
+
     @ApiModelProperty("可用性")
     private String usability;
 
+    @ApiModelProperty("可用性-检测项")
+    private String usabilityList;
+
     @ApiModelProperty("安全性")
     private String security;
+
+    @ApiModelProperty("安全性-检测项")
+    private String securityList;
+
+    @ApiModelProperty("节点")
+    private String nodeIds;
+
+    @ApiModelProperty("文件")
+    private String fileIds;
 }

+ 14 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto1.java

@@ -0,0 +1,14 @@
+package org.springblade.business.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class VolumeDto1 {
+    private Long id;
+    private String name;
+    private Integer total;
+    private List<VolumeDto2> list;
+}

+ 13 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto2.java

@@ -0,0 +1,13 @@
+package org.springblade.business.dto;
+
+import lombok.Data;
+
+@Data
+public class VolumeDto2 {
+    private Long id;
+    private String fileNumber;
+    private String fileName;
+    private String fileTime;
+    private String dutyUser;
+    private Integer filePage;
+}

+ 13 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto3.java

@@ -0,0 +1,13 @@
+package org.springblade.business.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+@Data
+public class VolumeDto3 {
+    private Long id;
+    private String name;
+    private Integer total;
+    private List<VolumeDto4> list=new ArrayList<>();
+}

+ 18 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/dto/VolumeDto4.java

@@ -0,0 +1,18 @@
+package org.springblade.business.dto;
+
+import io.swagger.models.auth.In;
+import lombok.Data;
+
+@Data
+public class VolumeDto4 {
+    private Long id;
+    private String fileNumber;
+    private String fileName;
+    private String fileTime;
+    private String dutyUser;
+    private Integer filePage;
+    //是否已被分卷 0否 1是
+    private Integer isCheck=0;
+
+    private Long archiveId;
+}

+ 17 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java

@@ -342,6 +342,23 @@ public class ArchiveFile extends BaseEntity {
     @ApiModelProperty("是否锁定 1已锁定")
     private Integer isLock;
 
+    /**
+     * fileUrl md5值
+     */
+    @ApiModelProperty("fileUrl md5值")
+    private String fileMd5;
+    /**
+     * pdfFileUrl md5值
+     */
+    @ApiModelProperty("pdfFileUrl md5值")
+    private String pdfMd5;
+
+    @ApiModelProperty("旧案卷ID")
+    private Long oldArchiveId;
+
+    @ApiModelProperty("是否分卷0否1是")
+    private Integer isVolume;
+
     public void fromExternal(ArchiveFileVo vo) {
         if (vo == null) {
             return;

+ 7 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/TrialSelfInspectionRecord.java

@@ -92,4 +92,11 @@ public class TrialSelfInspectionRecord extends BaseEntity {
     @ApiModelProperty("基础信息")
     private String baseInfo;
 
+    @ApiModelProperty("记录表pdf")
+    private String recordPdfUrl;
+
+    @ApiModelProperty("报告表pdf")
+    private String reportPdfUrl;
+
+
 }

+ 5 - 2
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/ArchiveFileClient.java

@@ -180,9 +180,12 @@ public interface ArchiveFileClient {
     @PostMapping(API_PREFIX + "/getAllArchiveFileByIds")
     List<ArchiveFile> getAllArchiveFileByIds(@RequestBody List<String> strList);
 
+    @PostMapping(API_PREFIX + "/selectMaxSortByContractId")
+    Integer selectMaxSortByContractId(@RequestParam Long contractId);
+
     @PostMapping(API_PREFIX + "/saveBatchArchiveFile")
     void saveBatchArchiveFile(@RequestBody List<ArchiveFile> list);
 
-    @PostMapping(API_PREFIX + "/selectMaxSortByContractId")
-    Integer selectMaxSortByContractId(@RequestParam Long contractId);
+    @PostMapping(API_PREFIX + "/getArchiveFileById")
+    ArchiveFile getArchiveFileById(@RequestParam Long fileId);
 }

+ 4 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/MetadataClassificationClient.java

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -32,4 +33,7 @@ public interface MetadataClassificationClient {
 
     @PostMapping(API_PREFIX + "/createMetadataFiles")
     boolean createMetadataFiles(@RequestBody List<Long> fileId);
+
+    @PostMapping(API_PREFIX + "/getMetadaFileByFileId")
+    List<HashMap<String, Object>> getMetadaFileByFileId(Long id);
 }

+ 300 - 0
blade-service-api/blade-business-api/src/main/java/org/springblade/business/utils/DigestUtil.java

@@ -0,0 +1,300 @@
+package org.springblade.business.utils;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 扩展的摘要算法工具类
+ * 新增对文件流、网络资源等的摘要计算支持
+ */
+public class DigestUtil {
+
+    private static final int BUFFER_SIZE = 8192;
+
+
+    /**
+     * 计算本地文件的MD5摘要
+     * @param filePath 文件路径
+     * @return 文件的MD5摘要
+     */
+    public static String md5OfFile(String filePath) throws IOException {
+        return digestOfFile(filePath, "MD5");
+    }
+
+    /**
+     * 计算本地文件的SHA-256摘要
+     * @param filePath 文件路径
+     * @return 文件的SHA-256摘要
+     */
+    public static String sha256OfFile(String filePath) throws IOException {
+        return digestOfFile(filePath, "SHA-256");
+    }
+
+    /**
+     * 计算本地文件的指定算法摘要
+     * @param filePath 文件路径
+     * @param algorithm 摘要算法
+     * @return 文件的摘要
+     */
+    public static String digestOfFile(String filePath, String algorithm) throws IOException {
+        Path path = Paths.get(filePath);
+        try (InputStream fis = Files.newInputStream(path)) {
+            return digestOfStream(fis, algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        }
+    }
+
+    /**
+     * 计算MultipartFile的MD5摘要
+     * @param multipartFile MultipartFile对象
+     * @return 文件的MD5摘要
+     */
+    public static String md5OfMultipartFile(org.springframework.web.multipart.MultipartFile multipartFile) throws IOException {
+        return digestOfMultipartFile(multipartFile, "MD5");
+    }
+
+    /**
+     * 计算MultipartFile的SHA-256摘要
+     * @param multipartFile MultipartFile对象
+     * @return 文件的SHA-256摘要
+     */
+    public static String sha256OfMultipartFile(org.springframework.web.multipart.MultipartFile multipartFile) throws IOException {
+        return digestOfMultipartFile(multipartFile, "SHA-256");
+    }
+
+    /**
+     * 计算MultipartFile的指定算法摘要
+     * @param multipartFile MultipartFile对象
+     * @param algorithm 摘要算法
+     * @return 文件的摘要
+     */
+    public static String digestOfMultipartFile(org.springframework.web.multipart.MultipartFile multipartFile, String algorithm) throws IOException {
+        try (InputStream inputStream = multipartFile.getInputStream()) {
+            return digestOfStream(inputStream, algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        }
+    }
+
+    /**
+     * 计算网络文件的MD5摘要
+     * @param fileUrl 网络文件URL
+     * @return 文件的MD5摘要
+     */
+    public static String md5OfUrl(String fileUrl) throws IOException {
+        return digestOfUrl(fileUrl, "MD5");
+    }
+
+    /**
+     * 计算网络文件的SHA-256摘要
+     * @param fileUrl 网络文件URL
+     * @return 文件的SHA-256摘要
+     */
+    public static String sha256OfUrl(String fileUrl) throws IOException {
+        return digestOfUrl(fileUrl, "SHA-256");
+    }
+
+    /**
+     * 计算网络文件的指定算法摘要
+     * @param fileUrl 网络文件URL
+     * @param algorithm 摘要算法
+     * @return 文件的摘要
+     */
+    public static String digestOfUrl(String fileUrl, String algorithm) throws IOException {
+        URL url = new URL(fileUrl);
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("GET");
+        connection.connect();
+
+        try (InputStream inputStream = connection.getInputStream()) {
+            return digestOfStream(inputStream, algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        } finally {
+            connection.disconnect();
+        }
+    }
+
+    /**
+     * 计算输入流的指定算法摘要
+     * @param inputStream 输入流
+     * @param algorithm 摘要算法
+     * @return 流的摘要
+     */
+    public static String digestOfStream(InputStream inputStream, String algorithm) throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance(algorithm);
+        try (DigestInputStream dis = new DigestInputStream(inputStream, md)) {
+            byte[] buffer = new byte[BUFFER_SIZE];
+            while (dis.read(buffer) != -1) {
+                // 读取数据的同时自动更新摘要
+            }
+        }
+        return bytesToHex(md.digest());
+    }
+
+
+    /**
+     * 计算大文件的摘要(适用于内存受限情况)
+     * @param filePath 文件路径
+     * @param algorithm 摘要算法
+     * @return 文件的摘要
+     */
+    public static String digestOfLargeFile(String filePath, String algorithm) throws IOException {
+        Path path = Paths.get(filePath);
+        try (FileChannel channel = FileChannel.open(path);
+             InputStream inputStream = Channels.newInputStream(channel)) {
+            return digestOfStream(inputStream, algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        }
+    }
+
+    /**
+     * MD5摘要算法
+     * @param input 输入字符串
+     * @return 32位MD5摘要
+     */
+    public static String md5(String input) {
+        return digest(input, "MD5");
+    }
+
+    /**
+     * SHA-1摘要算法
+     * @param input 输入字符串
+     * @return SHA-1摘要
+     */
+    public static String sha1(String input) {
+        return digest(input, "SHA-1");
+    }
+
+    /**
+     * SHA-256摘要算法
+     * @param input 输入字符串
+     * @return SHA-256摘要
+     */
+    public static String sha256(String input) {
+        return digest(input, "SHA-256");
+    }
+
+    /**
+     * SHA-512摘要算法
+     * @param input 输入字符串
+     * @return SHA-512摘要
+     */
+    public static String sha512(String input) {
+        return digest(input, "SHA-512");
+    }
+
+    /**
+     * 通用摘要算法
+     * @param input 输入字符串
+     * @param algorithm 算法名称 MD5, SHA-1, SHA-256, SHA-512 ....
+     * @return 指定算法的摘要
+     */
+    private static String digest(String input, String algorithm) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(algorithm);
+            byte[] hashBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
+            return bytesToHex(hashBytes);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        }
+    }
+
+    /**
+     * 通用摘要算法
+     * @param input 输入字符串
+     * @param algorithm 算法名称
+     * @return 指定算法的摘要
+     */
+    public static String digest(byte[] input, String algorithm) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(algorithm);
+            byte[] hashBytes = md.digest(input);
+            return bytesToHex(hashBytes);
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException("不支持的摘要算法: " + algorithm, e);
+        }
+    }
+
+    /**
+     * 字节数组转十六进制字符串
+     * @param bytes 字节数组
+     * @return 十六进制字符串
+     */
+    public static String bytesToHex(byte[] bytes) {
+        StringBuilder result = new StringBuilder();
+        for (byte b : bytes) {
+            result.append(String.format("%02x", b));
+        }
+        return result.toString();
+    }
+
+    /**
+     * 十六进制字符串转字节数组
+     * @param hex 十六进制字符串
+     * @return 字节数组
+     */
+    public static byte[] hexToBytes(String hex) {
+        int len = hex.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+                    + Character.digit(hex.charAt(i+1), 16));
+        }
+        return data;
+    }
+
+    /**
+     * 字节数组转Base64字符串
+     * @param bytes 字节数组
+     * @return Base64编码字符串
+     */
+    public static String bytesToBase64(byte[] bytes) {
+        return java.util.Base64.getEncoder().encodeToString(bytes);
+    }
+
+    /**
+     * Base64字符串转字节数组
+     * @param base64 Base64编码字符串
+     * @return 字节数组
+     */
+    public static byte[] base64ToBytes(String base64) {
+        return java.util.Base64.getDecoder().decode(base64);
+    }
+
+    /**
+     * 字节数组转十进制字符串
+     * @param bytes 字节数组
+     * @return 十进制字符串表示
+     */
+    public static String bytesToDecimal(byte[] bytes) {
+        BigInteger bi = new BigInteger(1, bytes);
+        return bi.toString(10);
+    }
+
+    /**
+     * 获取指定长度的摘要(截取前length位)
+     * @param input 输入字符串
+     * @param algorithm 算法名称
+     * @param length 截取长度
+     * @return 截取后的摘要
+     */
+    public static String digestWithLength(String input, String algorithm, int length) {
+        String digest = digest(input, algorithm);
+        return digest.substring(0, Math.min(digest.length(), length));
+    }
+}
+

+ 69 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/InformationImportRecord.java

@@ -0,0 +1,69 @@
+package org.springblade.manager.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("u_information_import_record")
+@ApiModel(value = "Dict对象", description = "Dict对象")
+public class InformationImportRecord implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("主键id")
+    @TableId(
+            value = "id",
+            type = IdType.ASSIGN_ID
+    )
+    private Long id;
+
+    @ApiModelProperty("导入文件名称")
+    private String fileName;
+
+    @ApiModelProperty("导入文件路径")
+    private String filePath;
+
+    @ApiModelProperty("合同段id")
+    private Long contractId;
+
+    @ApiModelProperty("节点id")
+    private Long nodeId;
+
+    @ApiModelProperty("目标节点id")
+    private Long targetId;
+
+    @ApiModelProperty("所属方")
+    private Integer classify;
+
+    @ApiModelProperty("导入时间")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("更新时间")
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty("导入人")
+    private Long createUser;
+
+    @ApiModelProperty("状态")
+    private Integer status;
+
+    @ApiModelProperty("进度")
+    private Integer process;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("是否删除")
+    private Integer isDeleted;
+
+    @ApiModelProperty("用户名")
+    @TableField(exist = false)
+    private String createUserName;
+}

+ 7 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/WbsFormElement.java

@@ -77,4 +77,11 @@ public class WbsFormElement extends BaseEntity {
      */
     @ApiModelProperty(value = "exid")
     private Long exid;
+
+    /**
+     * 是否辅助(1-是,0-不是)
+     */
+    @ApiModelProperty(value = "是否辅助(1-是,0-不是)")
+    private Integer assist;
+
 }

+ 3 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ArchiveTreeContractClient.java

@@ -90,4 +90,7 @@ public interface ArchiveTreeContractClient {
 
     @PostMapping(API_PREFIX + "/getAuthCode")
     public String getAuthCode(@RequestParam Long contractId);
+
+    @PostMapping(API_PREFIX + "/getArchiveTreeContractListByListOrderByTreeSort")
+    List<ArchiveTreeContract> getArchiveTreeContractListByListOrderByTreeSort(@RequestBody List<Long> nodeIds);
 }

+ 10 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ContractClient.java

@@ -4,6 +4,7 @@ package org.springblade.manager.feign;
 import org.springblade.core.tool.api.R;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
 import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.ProjectInfo;
 import org.springblade.manager.vo.RoleSignPfxUserVO3;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.transaction.annotation.Transactional;
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestParam;
 
 import java.util.List;
+import java.util.Set;
 
 import static org.springblade.core.launch.constant.AppConstant.APPLICATION_NAME_PREFIX;
 
@@ -80,4 +82,12 @@ public interface ContractClient {
     @GetMapping(API_PREFIX+"/findAllUserAndRoleList")
     RoleSignPfxUserVO3 findAllUserAndRoleList(@RequestParam Long contractId, @RequestParam(required = false) Long roleId);
 
+    @PostMapping(API_PREFIX+"/queryProjectList")
+    List<ProjectInfo> queryProjectList(@RequestBody Set<String> projectId);
+
+    @PostMapping(API_PREFIX+"/queryProjectIds")
+    List<String> queryProjectIds(@RequestBody Set<String> projectId);
+
+    @GetMapping(API_PREFIX+"/queryContractNamesListByProjectId")
+    List<ContractInfo> queryContractNamesListByProjectId(@RequestParam String projectId);
 }

+ 4 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ExcelTabClient.java

@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSONObject;
 import org.springblade.business.dto.ReSigningEntrustDto;
 import org.springblade.business.dto.TrialSelfInspectionRecordDTO;
 import org.springblade.core.tool.api.R;
+import org.springblade.manager.entity.ExcelEditCallback;
 import org.springblade.manager.entity.ExcelTab;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.*;
@@ -99,4 +100,7 @@ public interface ExcelTabClient {
 
     @PostMapping(API_PREFIX + "/getTheLogPdInfo")
     void getTheLogPdInfo(@RequestParam String logPkeyId,  @RequestParam String nodePrimaryKeyId, @RequestParam String recordTime, @RequestParam String contractId,@RequestParam Long createUser) throws Exception;
+
+    @PostMapping(API_PREFIX + "/feign/copeBussTab")
+    R copeBussTab(@RequestParam Long pKeyId, @RequestParam String header) throws Exception;
 }

+ 5 - 1
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/ExcelTabClientFallBack.java

@@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory;
 import org.springblade.business.dto.ReSigningEntrustDto;
 import org.springblade.business.dto.TrialSelfInspectionRecordDTO;
 import org.springblade.core.tool.api.R;
+import org.springblade.manager.entity.ExcelEditCallback;
 import org.springblade.manager.entity.ExcelTab;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -106,5 +107,8 @@ public class ExcelTabClientFallBack implements ExcelTabClient {
 
     }
 
-
+    @Override
+    public R copeBussTab(Long pKeyId, String header) throws Exception {
+        return null;
+    }
 }

+ 7 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/feign/SaveUserInfoByProjectClient.java

@@ -23,6 +23,8 @@ public interface SaveUserInfoByProjectClient {
     String SEARCH_USER_INFO_AND_PROJECT_BY_USER_IDS = "/api/manager/searchUserInfoAndProjectByUserIds";
     String QUERY_USER_CONTRACT_ROLE = "/api/manager/queryUserContractRole";
     String SAVE_INFO_RELATION = "/api/manager/saveInfoRelation";
+    String INSERT_USER_INFO_BATCH = "/api/manager/insertUserInfoBatch";
+    String UPDATE_USER_INFO_BATCH = "/api/manager/updateUserInfoBatch";
 
     @PostMapping(QUERY_USER_CONTRACT_ROLE)
     List<JSONObject> queryUserContractRole(@RequestBody List<Long> userIds, @RequestParam String contractId);
@@ -51,4 +53,9 @@ public interface SaveUserInfoByProjectClient {
     @PostMapping(SAVE_INFO_RELATION)
     void saveInfoRelation(@RequestParam Long userId, @RequestParam Long projectId, @RequestParam Long contractId, @RequestParam Long roleId);
 
+    @PostMapping(INSERT_USER_INFO_BATCH)
+    void insertUserInfoBatch(@RequestBody List<SaveUserInfoByProjectDTO>list);
+
+    @PostMapping(UPDATE_USER_INFO_BATCH)
+    void updateUserInfoBatch(@RequestBody List<SaveUserInfoByProjectDTO> updateUserInfoByProjectList);
 }

+ 35 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/InformationImportRecordVO.java

@@ -0,0 +1,35 @@
+package org.springblade.manager.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+@Data
+public class InformationImportRecordVO implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("文件名")
+    private String fileName;
+
+    @ApiModelProperty("开始时间")
+    private String startTime;
+
+    @ApiModelProperty("结束时间")
+    private String endTime;
+
+    @ApiModelProperty("状态,0:未开始, 1:进行中, 2:成功, 3:失败")
+    private Integer status;
+
+    @ApiModelProperty("操作人ID")
+    private Long userId;
+
+    @ApiModelProperty("所属方,1:施工, 2:监理")
+    private Integer classify = 1;
+
+    @ApiModelProperty("合同段ID, 必填")
+    private Long contractId;
+
+}

+ 59 - 0
blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ExcelUserTemp.java

@@ -0,0 +1,59 @@
+package org.springblade.system.user.dto;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+@Data
+public class ExcelUserTemp {
+    // A列:登录账号 → index=0
+    @ExcelProperty(index = 0)
+    private String account;
+
+    // B列:登录密码 → index=1
+    @ExcelProperty(index = 1)
+    private String password;
+
+    // C列:用户姓名 → index=2
+    @ExcelProperty(index = 2)
+    private String realName;
+
+    // D列:用户平台 → index=3
+    @ExcelProperty(index = 3)
+    private String userType;
+
+    // E列:手机号码 → index=4
+    @ExcelProperty(index = 4)
+    private String phone;
+
+    // F列:身份证号码 → index=5
+    @ExcelProperty(index = 5)
+    private String idNumber;
+
+    // G列:所属部门 → index=6
+    @ExcelProperty(index = 6)
+    private String deptName;
+
+    // H列:电签信息 → index=7
+    @ExcelProperty(index = 7)
+    private String accCode;
+
+    // I列:项目id → index=8
+    @ExcelProperty(index = 8)
+    private String projectId;
+
+    // J列:合同段名称/编号 → index=9
+    @ExcelProperty(index = 9)
+    private String contractInfo;
+
+    // K列:所属方 → index=10
+    @ExcelProperty(index = 10)
+    private String owner;
+
+    // L列:岗位名称 → index=11
+    @ExcelProperty(index = 11)
+    private String roleName;
+
+    // M列:登录状态 → index=12
+    @ExcelProperty(index = 12)
+    private String status;
+}

+ 32 - 0
blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ImportPorjectInfoDTO.java

@@ -0,0 +1,32 @@
+package org.springblade.system.user.dto;
+
+import lombok.Data;
+
+@Data
+public class ImportPorjectInfoDTO{
+    /**
+     * 项目id
+     */
+    private String projectId;
+
+    /**
+     * 合同段名称/编号
+     */
+    private String contractInfo;
+
+    /**
+     * 所属方(1业主方 2监理方 3施工方)
+     */
+    private String owner;
+
+    /**
+     * 岗位名称(中文匹配)
+     */
+    private String roleName;
+
+    private Integer status=0;
+
+    private Long contractId;
+
+    private Long roleId;
+}

+ 19 - 0
blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/ImportUserLog.java

@@ -0,0 +1,19 @@
+package org.springblade.system.user.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ImportUserLog {
+    private Long id;
+    private String account;
+    private String name;
+    private String message;
+    private String importTime;
+    private Long importId;
+    private Long importUser;
+    private String filePath;
+}

+ 58 - 0
blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/dto/UserImporterNew.java

@@ -0,0 +1,58 @@
+package org.springblade.system.user.dto;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class UserImporterNew {
+    /**
+     * 登录账号
+     */
+    private String account;
+    /**
+     * 登录密码
+     */
+    private String password;
+
+    /**
+     * 用户姓名
+     */
+    private String realName;
+
+    /**
+     * 用户平台
+     */
+    private String userType;
+
+    /**
+     * 手机号码
+     */
+    private String phone;
+
+    /**
+     * 身份证号码
+     */
+    private String idNumber;
+
+    /**
+     * 所属部门(中文名称)
+     */
+    private String deptName;
+
+    /**
+     * 电签信息(东方中迅电签码)
+     */
+    private String accCode;
+    /**
+     * 登录状态(允许为空,默认为是)
+     */
+    private String status;
+
+    private Long deptId;
+
+    private List<ImportPorjectInfoDTO> projectInfoList=new ArrayList<>();
+}

+ 5 - 0
blade-service/blade-archive/pom.xml

@@ -161,6 +161,11 @@
             <artifactId>pdfbox</artifactId>
             <version>2.0.24</version>
         </dependency>
+        <dependency>
+            <groupId>xyz.capybara</groupId>
+            <artifactId>clamav-client</artifactId>
+            <version>2.1.2</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 14 - 9
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveExaminingReportController.java

@@ -1,26 +1,22 @@
 package org.springblade.archive.controller;
 
+import cn.hutool.json.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.itextpdf.text.DocumentException;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
-import lombok.extern.java.Log;
 import org.springblade.archive.entity.ArchiveExaminingReport;
 import org.springblade.archive.service.IArchiveExaminingReportService;
 import org.springblade.archive.vo.ArchiveExaminingVo;
 import org.springblade.common.utils.SnowFlakeUtil;
-import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.redis.cache.BladeRedis;
 import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.utils.SecureUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.evisa.feign.EVisaClient;
 import org.springblade.evisa.vo.CertBeanVO;
-import org.springblade.resource.feign.NewIOSSClient;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
 import java.time.LocalDateTime;
@@ -41,6 +37,9 @@ public class ArchiveExaminingReportController {
 
     private final EVisaClient eVisaClient;
 
+    private final BladeRedis bladeRedis;
+
+    private final static String REDIS_STR = "blade-archive-examining:";
     @GetMapping("test")
     public String test() throws DocumentException, IOException {
 //        BladeFile bladeFile = iossClient.uploadFile("124456sdf", "D:\\develop\\test\\test1.pdf");
@@ -82,9 +81,9 @@ public class ArchiveExaminingReportController {
         return R.data(-1);
     }
 
-    @GetMapping("/getExamining")
+    @PostMapping("/getExamining")
     @ApiOperation(value = "档案四性检测")
-    public R getExamining(ArchiveExaminingVo vo) throws InterruptedException, IOException, DocumentException {
+    public R getExamining(@RequestBody ArchiveExaminingVo vo) throws InterruptedException, IOException, DocumentException {
 
         //如果id为0则为一键检测,不为0则有报告正在进行
         if (vo.getReportId() == 0) {
@@ -98,6 +97,12 @@ public class ArchiveExaminingReportController {
             report.setCreateTime(new Date());
             report.setProjectId(vo.getProjectId());
             archiveExaminingReportService.save(report);
+            //添加缓存
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.set("status",1);
+            jsonObject.set("number",0);
+            jsonObject.set("success",0);
+            bladeRedis.setEx(REDIS_STR + id,jsonObject,300L);
             //调用四性检测方法
             archiveExaminingReportService.getExamining(vo, id);
             //调用socket,每隔3秒检测当前报告状态然后推送

+ 22 - 4
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchivesAutoController.java

@@ -33,10 +33,7 @@ import org.apache.commons.lang.StringUtils;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.ss.usermodel.*;
-import org.springblade.archive.dto.ArchiveWarningDTO;
-import org.springblade.archive.dto.AutoOCRDTO;
-import org.springblade.archive.dto.FindAndReplaceDto;
-import org.springblade.archive.dto.SaveApplyDTO;
+import org.springblade.archive.dto.*;
 import org.springblade.archive.entity.ArchiveConclusion;
 import org.springblade.archive.entity.ExpertInspection;
 import org.springblade.archive.service.IArchiveAutoPdfService;
@@ -1332,4 +1329,25 @@ public class ArchivesAutoController extends BladeController {
 		return R.data(archivesAutoService.jointNomination(Arrays.asList(authId.split(","))));
 	}
 
+	@PostMapping("/saveVolume")
+	@ApiOperationSupport(order = 45)
+	@ApiOperation(value = "保存档案分卷文件")
+	public R saveVolume(@RequestBody SaveVolumeDto dto){
+		return R.success(archivesAutoService.saveVolume(dto));
+	}
+	@PostMapping("/removeVolume")
+	@ApiOperationSupport(order = 46)
+	@ApiOperation(value = "删除案卷分卷")
+	public R removeVolume(Long archiveId,Long volumeId){
+		return R.status(archivesAutoService.removeVolume(archiveId,volumeId));
+	}
+
+	@PostMapping("/backVolume")
+	@ApiOperationSupport(order = 47)
+	@ApiOperation(value = "案卷分卷文件回退")
+	public R backVolume(Long fileId){
+		return R.status(archivesAutoService.backVolume(fileId));
+	}
+
+
 }

+ 5 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/feign/ArchiveAutoClientImpl.java

@@ -74,4 +74,9 @@ public class ArchiveAutoClientImpl implements ArchiveAutoClient {
 
         archivesAutoService.reomoveArchiveAndFile(archiveIds);
     }
+
+    @Override
+    public ArchivesAuto getArchiveById(Long id) {
+        return archivesAutoService.getById(id);
+    }
 }

+ 9 - 4
blade-service/blade-archive/src/main/java/org/springblade/archive/service/IArchivesAutoService.java

@@ -17,10 +17,7 @@
 package org.springblade.archive.service;
 
 
-import org.springblade.archive.dto.ArchiveWarningDTO;
-import org.springblade.archive.dto.FindAndReplaceDto;
-import org.springblade.archive.dto.JiLinQueryDto;
-import org.springblade.archive.dto.SaveApplyDTO;
+import org.springblade.archive.dto.*;
 import org.springblade.archive.entity.ArchiveConclusion;
 import org.springblade.archive.entity.ArchivesAuto;
 import org.springblade.archive.entity.ExpertInspection;
@@ -194,4 +191,12 @@ public interface IArchivesAutoService extends BaseService<ArchivesAuto> {
 
 
     String jointNomination(List<String> authId);
+
+	Boolean updateArchivePage(List<Long>archiveIds);
+
+	String saveVolume(SaveVolumeDto dto);
+
+	boolean removeVolume(Long archiveId, Long volumeId);
+
+	boolean backVolume(Long fileId);
 }

+ 6 - 6
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveAutoPdfServiceImpl.java

@@ -158,8 +158,8 @@ public class ArchiveAutoPdfServiceImpl implements IArchiveAutoPdfService {
         archivesAuto.setKeywords("主题词");
         archivesAuto.setReviewer("审核人");
         archivesAuto.setReviewDate(LocalDateTime.now());
-        archivesAuto.setStartDate(LocalDateTime.now());
-        archivesAuto.setEndDate(LocalDateTime.now());
+        //archivesAuto.setStartDate(LocalDateTime.now());
+        //archivesAuto.setEndDate(LocalDateTime.now());
         archivesAuto.setStorageLocation("存放位置");
         archivesAuto.setIsArchive(0);
         archivesAuto.setRemark("备注");
@@ -883,14 +883,14 @@ public class ArchiveAutoPdfServiceImpl implements IArchiveAutoPdfService {
         if (!minDate.isEmpty() && !minDate.equals("null") ) {
             LocalDateTime localDateTime = FormulaUtil.parseStringToLocalDateTime(minDate, "yyyyMMdd");
             if (localDateTime != null) {
-                archive.setStartDate(localDateTime);
+                archive.setStartDate(localDateTime.toString());
             }
 
         }
         if (!maxDate.isEmpty() && !maxDate.equals("null") ) {
             LocalDateTime localDateTime = FormulaUtil.parseStringToLocalDateTime(maxDate, "yyyyMMdd");
             if (localDateTime != null) {
-                archive.setEndDate(localDateTime);
+                archive.setEndDate(localDateTime.toString());
             }
         }
 
@@ -904,11 +904,11 @@ public class ArchiveAutoPdfServiceImpl implements IArchiveAutoPdfService {
         }
 
         if (archive.getStartDate() != null) {
-            archivesAutoMap.put("startDate", FormulaUtil.formatLocalDateTime(archive.getStartDate(), "yyyyMMdd"));
+            archivesAutoMap.put("startDate", archive.getStartDate());
         }
 
         if (archive.getEndDate() != null) {
-            archivesAutoMap.put("endDate", FormulaUtil.formatLocalDateTime(archive.getEndDate(), "yyyyMMdd"));
+            archivesAutoMap.put("endDate", archive.getEndDate());
         }
 
         Map<String, Object> variables = new HashMap<>();

+ 534 - 144
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveExaminingReportImpl.java

@@ -1,45 +1,55 @@
 package org.springblade.archive.service.impl;
 
-import com.alibaba.fastjson.JSON;
-import com.aliyun.oss.OSSClient;
+import cn.hutool.json.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.itextpdf.text.*;
 import com.itextpdf.text.pdf.BaseFont;
 import com.itextpdf.text.pdf.PdfPCell;
 import com.itextpdf.text.pdf.PdfPTable;
 import com.itextpdf.text.pdf.PdfWriter;
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import lombok.var;
 import org.apache.commons.lang.StringUtils;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
 import org.springblade.archive.entity.ArchiveExaminingReport;
 import org.springblade.archive.entity.ArchiveExaminingReportDetail;
 import org.springblade.archive.mapper.ArchiveExaminingReportMapper;
 import org.springblade.archive.service.IArchiveExaminingReportDetailService;
 import org.springblade.archive.service.IArchiveExaminingReportService;
 import org.springblade.archive.socket.WebSocketServer;
-import org.springblade.archive.utils.FileUtils;
+import org.springblade.archive.utils.*;
 import org.springblade.archive.vo.ArchiveExaminingSocketVo;
 import org.springblade.archive.vo.ArchiveExaminingVo;
+import org.springblade.archive.vo.ArchivesAutoVO;
 import org.springblade.business.entity.ArchiveFile;
+import org.springblade.business.entity.MetadataClassification;
 import org.springblade.business.feign.ArchiveFileClient;
+import org.springblade.business.feign.MetadataClassificationClient;
 import org.springblade.common.constant.ArchiveConstant;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.oss.model.BladeFile;
-import org.springblade.core.secure.BladeUser;
-import org.springblade.core.secure.utils.AuthUtil;
-import org.springblade.core.secure.utils.SecureUtil;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tool.utils.CollectionUtil;
 import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.evisa.feign.EVisaClient;
 import org.springblade.evisa.vo.CertBeanVO;
+import org.springblade.manager.entity.ArchiveTreeContract;
+import org.springblade.manager.feign.ArchiveTreeContractClient;
 import org.springblade.resource.feign.NewIOSSClient;
-import org.springblade.resource.feign.NewISmsClient;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.util.*;
 import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * @Param
@@ -47,6 +57,7 @@ import java.util.List;
  * @Date 2023/4/20 17:36
  **/
 @Service
+@Slf4j
 @AllArgsConstructor
 public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExaminingReportMapper, ArchiveExaminingReport> implements IArchiveExaminingReportService {
     private final WebSocketServer webSocketServer;
@@ -59,6 +70,15 @@ public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExamining
 
     private final EVisaClient eVisaClient;
 
+    private final ArchivesAutoServiceImpl archivesAutoService;
+
+    private final ArchiveTreeContractClient archiveTreeContractClient;
+    private final MetadataClassificationClient metadataClassificationClient;
+
+    private final BladeRedis bladeRedis;
+
+    private final static String REDIS_STR = "blade-archive-examining:";
+
     /**
      * 推送状态到前端
      *
@@ -74,14 +94,18 @@ public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExamining
             Thread.sleep(4000L);
             //判断报告状态
             ArchiveExaminingReport report = this.getById(id);
-            Integer reportStatus = report.getStatus();
-            Integer detailStatus = report.getReportDetailStatus();
-            //判断详情状态
+            statusR = report.getStatus();
 
-            if (reportStatus != statusR || detailStatus != statusD) {
-                webSocketServer.sendMessagesToArchive(userId + "", "true");
-                statusR = reportStatus;
-                statusD = detailStatus;
+            List<ArchiveExaminingReportDetail> list = detailService.list(Wrappers.<ArchiveExaminingReportDetail>lambdaQuery().eq(ArchiveExaminingReportDetail::getReportId, id));
+            //判断详情状态
+            JSONObject json = bladeRedis.get(REDIS_STR + id);
+            if(json == null){
+               break;
+            }
+            json.set("list",list);
+            webSocketServer.sendMessagesToArchive(userId + "", json.toString());
+            if(json.getInt("status") == 5){
+                break;
             }
         } while (statusR != 4);
 
@@ -95,141 +119,486 @@ public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExamining
     @Async
     @Override
     public void getExamining(ArchiveExaminingVo vo, Long id) throws InterruptedException, DocumentException, IOException {
-        ArchiveExaminingReport report = new ArchiveExaminingReport();
-        report.setId(id);
-        report.setStatus(2);
-        this.updateById(report);
-        //获取项目中所有档案文件的工序资料pdfUrl
-        List<ArchiveFile> files = archiveFileClient.getAllPdfFileUrlByProjectIdAndFileType(vo.getProjectId());
-        //不合格对象
-        List<Map<String, String>> mapList = new ArrayList<>();
-        int unqualifiedCount = 0;
-        //检测中
-        //真实性
-        if (StringUtils.isNotBlank(vo.getAuthenticity()) && "1".equals(vo.getAuthenticity())) {
-            //检测项目下所有工序资料PDF签章有效性
-            for (ArchiveFile file : files) {
-                if (StringUtils.isNotBlank(file.getPdfFileUrl())) {
-                    CertBeanVO cb = eVisaClient.onlineCheckSeal(file.getPdfFileUrl());
-                    if (cb == null) {
+        try {
+            ArchiveExaminingReport report = new ArchiveExaminingReport();
+            report.setId(id);
+            report.setStatus(2);
+            this.updateById(report);
+
+            //优化 获取权限表示码
+            String authCode = archiveTreeContractClient.getAuthCode(vo.getContractId());
+            //封装查询参数查询
+            ArchivesAutoVO queryVo = new ArchivesAutoVO();
+            queryVo.setProjectId(vo.getProjectId());
+            queryVo.setContractId(vo.getContractId());
+            queryVo.setCurrent(1);
+            queryVo.setSize(999999999);
+            queryVo.setIsArchive(1);
+            queryVo.setAuthCode(authCode);
+            //多个节点查询数据
+            String nodeIds = vo.getNodeIds();
+            Set<String> collect = new HashSet<>();
+            if(StringUtils.isNotEmpty(nodeIds)){
+                for (String s : nodeIds.split(",")) {
+                    queryVo.setNodeIds(s);
+                    //封装参数查询组卷列表
+                    IPage<ArchivesAutoVO> page = archivesAutoService.selectArchivesAutoFilePage(queryVo);
+                    List<ArchivesAutoVO> records = page.getRecords();
+                    if(CollectionUtil.isNotEmpty(records)){
+                        collect.addAll(records.stream().map(ArchivesAutoVO::getId).map(String::valueOf).collect(Collectors.toSet()));
+                    }
+                }
+            }
+            //选中的指定文件也加入进来
+            if(StringUtils.isNotEmpty(vo.getFileIds())){
+                Set<String> collect1 = Arrays.stream(vo.getFileIds().split(",")).collect(Collectors.toSet());
+                collect.addAll(collect1);
+            }
+            List<ArchiveFile> files = new ArrayList<>();
+            if(CollectionUtil.isNotEmpty(collect)){
+                //类型转换
+                List<String> strings = new ArrayList<>(collect);
+                //根据组件id查询文件数据
+                files = archiveFileClient.getAllArchiveFileByArchiveIds(strings);
+            }
+            //文件类型范围
+            List<String> strings = new ArrayList<>();
+            strings.add("pdf");
+            strings.add("dwg");
+            strings.add("jpg");
+            strings.add("png");
+            //不合格对象
+            List<Map<String, String>> mapList = new ArrayList<>();
+            //1-1 电签验证
+            int unqualifiedCount = 0;
+            //1-2 一致性检测
+            int consistency = 0;
+            //1-5 对设定值域的元数据项值域符合度检测
+            int metadataCompliance = 0;
+            //1-6 对元数据项数据重复性检测
+            int metadataRepeat = 0;
+            //1-8 信息包一致性检测
+            int infoConsistency = 0;
+            //2-3 对元数据项完整性检测
+            int metadataComplete = 0;
+            //2-4 对元数据项必填项
+            int metadataRequiredField = 0;
+
+            //3-1 文件格式
+            int fileType = 0;
+            //3-2 文件是否能够访问
+            int fileIsAccess = 0;
+            //3-5 信息包加密
+            int infoEncryption = 0;
+            //4-1 病毒检测
+            int virusDetection = 0;
+            //4-3 病毒安装
+            int virusInstall = 0;
+
+            //检测中 文件为空,不允许检测
+            if(CollectionUtil.isNotEmpty(files)){
+                Map<Integer,String> storageTypeMap = new HashMap<>();
+                storageTypeMap.put(1,"a");
+                storageTypeMap.put(2,"b");
+                storageTypeMap.put(3,"c");
+                storageTypeMap.put(4,"d");
+                storageTypeMap.put(5,"e");
+                storageTypeMap.put(7,"f");
+                storageTypeMap.put(6,"g");
+                storageTypeMap.put(8,"h");
+                storageTypeMap.put(9,"i");
+                //获取nodeId 查询节点信息
+                Map<String, List<ArchiveFile>> collect2 = files.stream().collect(Collectors.groupingBy(ArchiveFile::getNodeId));
+                List<Long> collect11 = collect2.keySet().stream().map(String::trim).filter(s -> !s.isEmpty()).map(Long::valueOf).collect(Collectors.toList());
+                List<ArchiveTreeContract> archiveTreeContractListByList = archiveTreeContractClient.getArchiveTreeContractListByList(collect11);
+                //获取所有的元数据配置项
+                List<MetadataClassification> metadataClassification = metadataClassificationClient.getMetadataClassification();
+                //组装数据,节点与元数据配置型的映射关系
+                HashMap<Long, List<MetadataClassification>> longStringHashMap = new HashMap<>();
+                if(CollectionUtil.isNotEmpty(metadataClassification)){
+                    archiveTreeContractListByList.forEach(f -> {
+                        if(f.getStorageType() == null){
+                            return;
+                        }
+                        List<MetadataClassification> collect1 = metadataClassification.stream().filter(e -> StringUtils.isNotEmpty(e.getFileStorageType()) && e.getFileStorageType().contains(storageTypeMap.get(f.getStorageType()))).collect(Collectors.toList());
+                        longStringHashMap.put(f.getId(),collect1);
+                    });
+                }
+                //开始检测
+                JSONObject json = bladeRedis.get(REDIS_STR + id);
+                if(json == null){
+                    json = new JSONObject();
+                    json.set("success",0);
+                }
+                json.set("number",files.size());
+                json.set("status",2);
+                bladeRedis.setEx(REDIS_STR + id,json,300L);
+                for (ArchiveFile file : files) {
+                    json = bladeRedis.get(REDIS_STR + id);
+                    if(json == null){
+                        json = new JSONObject();
+                        json.set("success",0);
+                        json.set("status",2);
+                        json.set("number",files.size());
+                    }
+
+
+                    //当前文件父节点对应的元数据配置项
+                    List<MetadataClassification> metadataClassifications = longStringHashMap.get(Long.valueOf(file.getNodeId()));
+                    //获取文件元数据项
+                    List<HashMap<String, Object>> list = metadataClassificationClient.getMetadaFileByFileId(file.getId());
+                    //优先检查文件是否可读
+                    if(StringUtils.isEmpty(file.getPdfFileUrl())
+                            || !validateWithHead(file.getPdfFileUrl())
+                            || StringUtils.isEmpty(file.getFileUrl())
+                            || !validateWithHead(file.getFileUrl())){
                         Map<String, String> map = new HashMap<>();
-                        map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测");
+                        map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子档案内容数据的可读性检测");
                         map.put("unqualifiedObject", file.getFileName());
                         mapList.add(map);
-                        unqualifiedCount++;
+                        fileIsAccess++;
+                        json.set("success",json.getInt("success") + 1);
+                        bladeRedis.setEx(REDIS_STR + id,json,300L);
+                        continue;
+                    }
+                    //真实性
+                    log.info("{}正在检测真实性",file.getFileName());
+                    if (StringUtils.isNotBlank(vo.getAuthenticity()) && "1".equals(vo.getAuthenticity())) {
+                        //获取文件数字摘要
+                        RemoteFileMD5Calculator.MD5Result fileUrL = RemoteFileMD5Calculator.getRemoteFileMD5FromHeaders(file.getFileUrl());
+                        RemoteFileMD5Calculator.MD5Result pdfFileUrl = RemoteFileMD5Calculator.getRemoteFileMD5FromHeaders(file.getPdfFileUrl());
+                        String fileMd5 = fileUrL.getMd5Hash();
+                        String pdfMd5 = pdfFileUrl.getMd5Hash();
+                        if(vo.getAuthenticityList().contains("1")){
+                            //检测项目下所有工序资料PDF签章有效性
+                            CertBeanVO cb = eVisaClient.onlineCheckSeal(file.getPdfFileUrl());
+                            //数字摘要判断和电签
+                            if (cb == null || !file.getFileMd5().equals(fileMd5) || !file.getPdfMd5().equals(pdfMd5)) {
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                unqualifiedCount++;
+                            }
+                        }
+                        if(vo.getAuthenticityList().contains("2")){
+                            if (!file.getFileMd5().equals(fileMd5) || !file.getPdfMd5().equals(pdfMd5)) {
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                consistency++;
+                            }
+                        }
+                        if(vo.getAuthenticityList().contains("3")){}
+                        if(vo.getAuthenticityList().contains("4")){}
+                        if(vo.getAuthenticityList().contains("5")){
+                            //获取元数据
+                            if(CollectionUtil.isEmpty(list) ){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对设定值域的元数据项值域符合度检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                metadataCompliance++;
+                            }
+
+                        }
+                        if(vo.getAuthenticityList().contains("6")){
+                            //获取元数据
+                            if(CollectionUtil.isEmpty(list)){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据重复性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                metadataRepeat++;
+                            }else{
+                                List<String> data = new ArrayList<>();
+                                HashMap<String, Object> stringObjectHashMap = list.get(0);
+                                for (MetadataClassification classification : metadataClassifications) {
+                                    String fieldKey = classification.getFieldKey();
+                                    data.add(stringObjectHashMap.get(fieldKey).toString());
+                                }
+                                HashSet<String> strings1 = new HashSet<>(data);
+                                //存在重复数据
+                                if(data.size() != strings1.size()){
+                                    Map<String, String> map = new HashMap<>();
+                                    map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据重复性检测");
+                                    map.put("unqualifiedObject", file.getFileName());
+                                    mapList.add(map);
+                                    metadataRepeat++;
+                                }
+                            }
+
+                        }
+                        if(vo.getAuthenticityList().contains("7")){}
+                        if(vo.getAuthenticityList().contains("8")){
+                            if(!file.getFileMd5().equals(fileMd5) || !file.getPdfMd5().equals(pdfMd5)){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                infoConsistency++;
+                            }
+                        }
+                        if(vo.getAuthenticityList().contains("9")){}
+                        if(vo.getAuthenticityList().contains("10")){}
+                        report.setReportDetailStatus(1);
+                        this.updateById(report);
+                    }
+                    //完整性
+                    log.info("{}正在检测完整性",file.getFileName());
+                    if (StringUtils.isNotBlank(vo.getIntegrality()) && "1".equals(vo.getIntegrality())) {
+                        if(vo.getIntegralityList().contains("1")){}
+                        if(vo.getIntegralityList().contains("2")){}
+                        if(vo.getIntegralityList().contains("3")){
+                            //获取元数据
+                            if(CollectionUtil.isEmpty(list)){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项完整性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                metadataComplete++;
+                            }else{
+                                //元数据
+                                HashMap<String, Object> stringObjectHashMap = list.get(0);
+                                //元数据项
+                                List<String> collect1 = metadataClassifications.stream().map(MetadataClassification::getFieldKey).collect(Collectors.toList());
+                                Set<String> strings2 = stringObjectHashMap.keySet();
+                                //元数据项是否缺失
+                                if(!strings2.containsAll(collect1)){
+                                    Map<String, String> map = new HashMap<>();
+                                    map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项完整性检测");
+                                    map.put("unqualifiedObject", file.getFileName());
+                                    mapList.add(map);
+                                    metadataComplete++;
+                                }
+                            }
+                        }
+                        if(vo.getIntegralityList().contains("4")){
+                            //获取元数据
+                            if(CollectionUtil.isEmpty(list)){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据必填项检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                metadataRequiredField++;
+                            }else{
+                                //元数据
+                                HashMap<String, Object> stringObjectHashMap = list.get(0);
+                                //元数据项-必选项
+                                List<String> collect1 = metadataClassifications.stream().filter(f -> f.getMandatoryType() == 1).map(MetadataClassification::getFieldKey).collect(Collectors.toList());
+                                for (String s : collect1) {
+                                    Object o = stringObjectHashMap.get(s);
+                                    //必选项没有值
+                                    if(o == null){
+                                        Map<String, String> map = new HashMap<>();
+                                        map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据必填项检测");
+                                        map.put("unqualifiedObject", file.getFileName());
+                                        mapList.add(map);
+                                        metadataRequiredField++;
+                                        break;
+                                    }
+                                }
+                            }
+
+                        }
+                        if(vo.getIntegralityList().contains("5")){}
+                        if(vo.getIntegralityList().contains("6")){}
+                        if(vo.getIntegralityList().contains("7")){}
+                        if(vo.getIntegralityList().contains("8")){}
+                        report.setReportDetailStatus(2);
+                        this.updateById(report);
                     }
+                    //可用性
+                    log.info("{}正在检测可用性",file.getFileName());
+                    if (StringUtils.isNotBlank(vo.getUsability()) && "1".equals(vo.getUsability())) {
+                        if(vo.getUsabilityList().contains("1")){
+                            //获取文件后缀,后缀为
+                            String fileExtensionFromUrl = RemoteFileExtension.getFileExtensionFromUrl(file.getPdfFileUrl());
+                            if(StringUtils.isEmpty(fileExtensionFromUrl) || !strings.contains(fileExtensionFromUrl)){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子文件格式检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                fileType++;
+                            }
+
+                        }
+                        //3-2 放在最开始
+                        if(vo.getUsabilityList().contains("3")){
+
+                        }
+                        if(vo.getUsabilityList().contains("4")){
+
+                        }
+                        if(vo.getUsabilityList().contains("5")){
+                            //文件加密压缩认证
+                            FileAnalysisResult fileAnalysisResult = null;
+                            try {
+                                fileAnalysisResult = RemoteFileSecurityChecker.analyzeRemoteFile(file.getFileUrl());
+                            } catch (IOException e) {}
+                            //是否使用公共压缩算法 是否加密
+                            if(fileAnalysisResult == null){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据重复性检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                infoEncryption++;
+                            }
+                        }
+                        if(vo.getUsabilityList().contains("6")){}
+                        report.setReportDetailStatus(3);
+                        this.updateById(report);
+                    }
+                    //安全性
+                    log.info("{}正在检测安全性",file.getFileName());
+                    if (StringUtils.isNotBlank(vo.getSecurity()) && "1".equals(vo.getSecurity())) {
+                        if(vo.getSecurityList().contains("1")){
+                            if(!ClamAVClientScanner.checkHealth() || !ClamAVClientScanner.scanRemoteFile(file.getPdfFileUrl())){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子文件格式检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                virusDetection++;
+                            }
+                        }
+                        if(vo.getSecurityList().contains("2")){}
+                        if(vo.getSecurityList().contains("3")){
+                            if(!ClamAVClientScanner.checkHealth()){
+                                Map<String, String> map = new HashMap<>();
+                                map.put("examiningItem", ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子文件格式检测");
+                                map.put("unqualifiedObject", file.getFileName());
+                                mapList.add(map);
+                                virusInstall++;
+                            }
+                        }
+                        if(vo.getSecurityList().contains("4")){}
+                        report.setReportDetailStatus(4);
+                        this.updateById(report);
+                    }
+
+                    json.set("success",json.getInt("success") + 1);
+                    bladeRedis.setEx(REDIS_STR + id,json,300L);
+                    log.info("{}检测完成",file.getFileName());
                 }
             }
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测", unqualifiedCount, unqualifiedCount == 0 ? "无" : "详见附件", unqualifiedCount == 0 ? 0 : 1));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据长度检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据类型、格式检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对设定值域的元数据项值域符合度检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对档号规范性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对内容数据的电子属性一致性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据重复性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据是否关联内容数据检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包目录结构规范性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包一致性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子档案封装包规范性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子档案封装包电子前面有效性检测", 0, "无", 0));
-            report.setReportDetailStatus(1);
-            this.updateById(report);
-        }
-        if (StringUtils.isNotBlank(vo.getIntegrality()) && "1".equals(vo.getIntegrality())) {
-            Thread.sleep(5000L);
-            //完整性
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对总件数相符性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对总字节数相符性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项完整性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据必填著录项目检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对过程信息完整性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对连续性元数据项检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对内容数据完整性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对附件数据完整性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对归档范围检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包元数据完整性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包内容数据完整性检测", 0, "无", 0));
-            report.setReportDetailStatus(2);
-            this.updateById(report);
-        }
-        //可用性
-        if (StringUtils.isNotBlank(vo.getUsability()) && "1".equals(vo.getUsability())) {
-            Thread.sleep(5000L);
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包中元数据的可读性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对目标数据库中的元数据可访问下检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对内容数据格式检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对内容数据的可读性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对内容数据格式长期可用性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对软硬件环境合规性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包中包含的内容数据格式合规性检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对备份数据可恢复性检测", 0, "无", 0));
-            report.setReportDetailStatus(3);
+            JSONObject json = bladeRedis.get(REDIS_STR + id);
+            if(json == null){
+                json = new JSONObject();
+                json.set("number",files.size());
+                json.set("success",files.size());
+            }
+            json.set("status",3);
+            bladeRedis.setEx(REDIS_STR + id,json,300L);
+            //可用性
+            if(StringUtils.isNotBlank(vo.getUsability()) && "1".equals(vo.getUsability())){
+                if(vo.getUsabilityList().contains("1")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子文件格式检测", fileType, fileType == 0 ? "无" : "详见附件", fileType == 0 ? 0 : 1));
+                }
+                if(vo.getUsabilityList().contains("2")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子档案内容数据的可读性检测", fileIsAccess, fileIsAccess == 0 ? "无" : "详见附件", fileIsAccess == 0 ? 0 : 1));
+                }
+                if(vo.getUsabilityList().contains("3")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对目标数据库中的元数据可访问性检测", 0, "无", 0));
+                }
+                if(vo.getUsabilityList().contains("4")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包中元数据可读性检测", 0, "无", 0));
+                }
+                if(vo.getUsabilityList().contains("5")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_USABILITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包中包含的内容数据合规性检测", infoEncryption, infoEncryption == 0 ? "无" : "详见附件", infoEncryption == 0 ? 0 : 1));
+                }
+            }
+            //真实性
+            if (StringUtils.isNotBlank(vo.getAuthenticity()) && "1".equals(vo.getAuthenticity())) {
+                if(vo.getAuthenticityList().contains("1")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对固化信息有效性检测", unqualifiedCount, unqualifiedCount == 0 ? "无" : "详见附件", unqualifiedCount == 0 ? 0 : 1));
+                }
+                if(vo.getAuthenticityList().contains("2")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对电子文件内容一致性检测", consistency, consistency == 0 ? "无" : "详见附件", consistency == 0 ? 0 : 1));
+                }
+                if(vo.getAuthenticityList().contains("5")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对设定值域的元数据项值域符合度检测", metadataCompliance, metadataCompliance == 0 ? "无" : "详见附件", metadataCompliance == 0 ? 0 : 1));
+                }
+                if(vo.getAuthenticityList().contains("6")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项数据重复性检测", metadataRepeat, metadataRepeat == 0 ? "无" : "详见附件", metadataRepeat == 0 ? 0 : 1));
+                }
+                if(vo.getAuthenticityList().contains("8")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_AUTHENTICITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对信息包一致性检测", infoConsistency, infoConsistency == 0 ? "无" : "详见附件", infoConsistency == 0 ? 0 : 1));
+                }
+            }
+            //完整信
+            if (StringUtils.isNotBlank(vo.getIntegrality()) && "1".equals(vo.getIntegrality())) {
+                if(vo.getIntegralityList().contains("3")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据项完整性检测",  metadataComplete, metadataComplete == 0 ? "无" : "详见附件", metadataComplete == 0 ? 0 : 1));
+                }
+                if(vo.getIntegralityList().contains("4")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_INTEGRALITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对元数据必填项检测", metadataRequiredField, metadataRequiredField == 0 ? "无" : "详见附件", metadataRequiredField == 0 ? 0 : 1));
+                }
+            }
+            //安全性
+            if (StringUtils.isNotBlank(vo.getSecurity()) && "1".equals(vo.getSecurity())) {
+                if(vo.getSecurityList().contains("1")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_SECURITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对病毒感染检测", virusDetection, virusDetection == 0 ? "无" : "详见附件", virusDetection == 0 ? 0 : 1));
+                }
+                if(vo.getSecurityList().contains("3")){
+                    detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_SECURITY,
+                            ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对系统环境中是否安装杀毒软件检测", virusInstall, virusInstall == 0 ? "无" : "详见附件", virusInstall == 0 ? 0 : 1));
+                }
+            }
+
+            //生成报告,生成PDF
+            report.setStatus(3);
             this.updateById(report);
-        }
-        //安全性
-        if (StringUtils.isNotBlank(vo.getSecurity()) && "1".equals(vo.getSecurity())) {
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_SECURITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对系统环境中是否安装杀毒软件检测", 0, "无", 0));
-            detailService.save(new ArchiveExaminingReportDetail(vo.getProjectId(), id, ArchiveConstant.ARCHIVE_EXAMINING_SECURITY,
-                    ArchiveConstant.ARCHIVE_EXAMINING_STANDARD + "对病毒感染检测", 0, "无", 0));
-            report.setReportDetailStatus(4);
+            String url = this.generateReportPdf(id);
+            //生成附件PDF
+            if (mapList.size() > 0) {
+                String detailPdf = this.generateReportDetailPdf(mapList);
+                List<String> PdfUrls = new ArrayList<>();
+                PdfUrls.add(url);
+                PdfUrls.add(detailPdf);
+                String localUrl = FileUtils.getSysLocalFileUrl() + "/archiveExaminingPdf/123.pdf";
+                //合并pdf
+                FileUtils.mergePdfPublicMethods(PdfUrls, localUrl);
+                BladeFile bladeFile = iossClient.uploadFile("123.pdf", localUrl);
+                report.setReportPdfUrl(bladeFile.getLink());
+            } else {
+                report.setReportPdfUrl(url);
+            }
+            //完成
+            report.setStatus(4);
+
+            json = bladeRedis.get(REDIS_STR + id);
+            if(json == null){
+                json = new JSONObject();
+                json.set("number",files.size());
+                json.set("success",files.size());
+            }
+            json.set("status",4);
+            bladeRedis.setEx(REDIS_STR + id,json,300L);
             this.updateById(report);
+        } catch (Exception e) {
+            e.printStackTrace();
+            JSONObject json = bladeRedis.get(REDIS_STR + id);
+            if(json == null){
+                json = new JSONObject();
+            }
+            json.set("status",5);
+            json.set("error",e.getMessage());
+            bladeRedis.setEx(REDIS_STR + id,json,300L);
         }
-        Thread.sleep(3000L);
-        //生成报告,生成PDF
-        report.setStatus(3);
-        this.updateById(report);
-        String url = this.generateReportPdf(id);
-        //生成附件PDF
-        if (mapList.size() > 0) {
-            String detailPdf = this.generateReportDetailPdf(mapList);
-            List<String> PdfUrls = new ArrayList<>();
-            PdfUrls.add(url);
-            PdfUrls.add(detailPdf);
-            String localUrl = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveExaminingPdf/123.pdf";
-//            String localUrl = "D:\\develop\\test\\123.pdf";
-            //合并pdf
-            FileUtils.mergePdfPublicMethods(PdfUrls, localUrl);
-            BladeFile bladeFile = iossClient.uploadFile("123.pdf", localUrl);
-            report.setReportPdfUrl(bladeFile.getLink());
-        } else {
-            report.setReportPdfUrl(url);
-        }
-        Thread.sleep(5000L);
-        //完成
-        report.setStatus(4);
-        this.updateById(report);
     }
 
     /**
@@ -256,11 +625,11 @@ public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExamining
      * 生成检测报告PDF
      */
     private String generateReportPdf(Long id) throws DocumentException, IOException {
+
         int high = 20;
         int widthPercentage = 100;
         String uuid = StringUtil.randomUUID();
-        String localUrl = "/www/wwwroot/Users/hongchuangyanfa/Desktop/archiveExaminingPdf/";
-//        String localUrl = "D:\\develop\\test\\";
+        String localUrl = FileUtils.getSysLocalFileUrl() + "/archiveExaminingPdf/";
         //新建一个pdf文档对象,前一个参数是纸张大小,后四个为边距
         Document document = new Document(PageSize.A4, 5, 5, 30, 30);
         //建立一个书写器
@@ -422,4 +791,25 @@ public class ArchiveExaminingReportImpl extends BaseServiceImpl<ArchiveExamining
         }
         return pdfPCell;
     }
+
+    public static boolean validateWithHead(String fileUrl) {
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            HttpHead request = new HttpHead(fileUrl);
+
+            // 设置超时配置
+            RequestConfig config = RequestConfig.custom()
+                    .setConnectTimeout(5000)
+                    .setSocketTimeout(5000)
+                    .build();
+            request.setConfig(config);
+
+            try (var response = client.execute(request)) {
+                int statusCode = response.getStatusLine().getStatusCode();
+                return statusCode >= 200 && statusCode < 300;
+            }
+        } catch (Exception e) {
+            System.out.println("验证失败: " + e.getMessage());
+            return false;
+        }
+    }
 }

+ 2 - 2
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveOfflineVersionInfoServiceImpl.java

@@ -449,8 +449,8 @@ public class ArchiveOfflineVersionInfoServiceImpl extends BaseServiceImpl<Archiv
                     pstm.setString(12, auto.getAllFilePdf() == null ? "" : auto.getAllFilePdf());
                     pstm.setLong(13, auto.getFileSize() == null ? 0L : auto.getFileSize());
                     pstm.setString(14, auto.getTreeSort() == null ? "" : auto.getTreeSort());
-                    pstm.setString(15, auto.getStartDate() == null ? "" : auto.getStartDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
-                    pstm.setString(16, auto.getEndDate() == null ? "" : auto.getEndDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+                    pstm.setString(15, auto.getStartDate() == null ? "" : auto.getStartDate());
+                    pstm.setString(16, auto.getEndDate() == null ? "" : auto.getEndDate());
                     pstm.setString(17, auto.getSecretLevel());
 
                     //添加批处理

+ 193 - 32
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchivesAutoServiceImpl.java

@@ -39,6 +39,13 @@ import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
 
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.io.MemoryUsageSetting;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.PDResources;
+import org.apache.pdfbox.pdmodel.graphics.PDXObject;
+import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
 import org.springblade.archive.dto.*;
 import org.springblade.archive.entity.*;
 import org.springblade.archive.mapper.ArchiveConclusionMapper;
@@ -53,7 +60,6 @@ import org.springblade.business.feign.ArchiveFileClient;
 import org.springblade.business.feign.MetadataClassificationClient;
 import org.springblade.business.feign.TaskClient;
 import org.springblade.common.utils.CommonUtil;
-import org.springblade.common.utils.DeepSeekClient;
 import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.common.utils.SystemUtils;
 import org.springblade.core.log.exception.ServiceException;
@@ -65,8 +71,10 @@ import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.secure.utils.SecureUtil;
 import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.FileUtil;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.manager.entity.*;
 import org.springblade.manager.feign.ArchiveTreeContractClient;
 import org.springblade.manager.feign.ContractClient;
@@ -74,6 +82,7 @@ import org.springblade.manager.feign.ProjectClient;
 import org.springblade.manager.vo.ArchiveTreeContractVO2;
 import org.springblade.resource.feign.CommonFileClient;
 import org.springblade.resource.feign.NewIOSSClient;
+import org.springblade.system.cache.ParamCache;
 import org.springblade.system.entity.DictBiz;
 import org.springblade.system.feign.IDictBizClient;
 import org.springblade.system.user.entity.User;
@@ -89,18 +98,21 @@ import org.springframework.transaction.annotation.Transactional;
 
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.io.*;
 import java.math.BigDecimal;
 import java.net.URL;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.rmi.ServerException;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.List;
@@ -677,6 +689,8 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		List<DictBiz> sheetSourceList = this.iDictBizClient.getList("security_level", "notRoot").getData();
 		List<DictBiz> sheetSourceList1 = this.iDictBizClient.getList("storage_period", "notRoot").getData();
 
+		// 定义目标格式的 DateTimeFormatter
+		DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
 		pageVoList.forEach(vos -> {
 			if (StringUtils.isNotEmpty(vos.getSecretLevel())) {
 				sheetSourceList.forEach(source -> {
@@ -1717,6 +1731,8 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		archiveAutoPdfService.buildArchiveFrontPdfs(archivesAuto.getProjectId(), archivesAuto, waitArchiveFiles, true);
 		//生成页码
 		builtFilePageNo(archivesAuto, waitArchiveFiles);
+		archivesAuto.setColourStatus(0);
+		baseMapper.updateById(archivesAuto);
 		archiveFileClient.updateArchiveFile(waitArchiveFiles);
 	}
 
@@ -4858,7 +4874,8 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		for (ArchivesAutoVO4 v : value) {
 			ArchivesAuto auto = new ArchivesAuto();
 			if (config.getIndexType() == null || config.getIndexType() == 0) {
-				v.setFileNumber(v.getFileNumberPrefix() + "-" + i);
+				v.setFileNumber(v.getFileNumberPrefix() + "—" + i);
+
 			} else {
 				String prefix = v.getFileNumberPrefix();
 				int index = i; // 编号从 startNumber 开始
@@ -4871,7 +4888,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 				// 使用 %0xd 格式化数字,x 表示总长度
 				String formattedIndex = String.format("%0" + numLength + "d", index);
 				// 设置最终档号
-				v.setFileNumber(prefix + "-" + formattedIndex);
+				v.setFileNumber(prefix + "" + formattedIndex);
 			}
 			auto.setId(v.getId());
 			auto.setFileNumber(v.getFileNumber());
@@ -5361,12 +5378,12 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 				.sum();
 	}
 
+
 	@Override
 	@Async
 	public Boolean atuoOCR(List<Long> idsList) throws Exception {
 		String url="/mnt/sdc/AutoPdf/";
 		//String url="D:\\AutoPdf\\";
-		//List<Long> idsList=Func.toLongList(ids);
 		List<ArchivesAuto> archivesAutoList = this.list(new LambdaQueryWrapper<ArchivesAuto>().in(ArchivesAuto::getId, idsList));
 		this.update(Wrappers.<ArchivesAuto>lambdaUpdate().set(ArchivesAuto::getColourStatus, 2).in(ArchivesAuto::getId, idsList));
 		for (ArchivesAuto auto : archivesAutoList) {
@@ -5396,20 +5413,20 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 								String fileNum=result.replace("档号","").replace(":","").replace(":","");
 								auto.setFileNumber(fileNum);
 							}else if(result.contains("立卷单位")){
-								String unit=result.replace("立卷单位","").replace(":","").replace(":","").replaceAll("_","");
+								String unit=result.replace("立卷单位","").replace(":","").replace(":","").replaceAll("_","").replace("密级","").replace("级密","");
 								auto.setUnit(unit);
 							}else if (result.contains("起止日期")) {
 								String time=result.replace("起止日期","").replace(":","").replace(":","").replaceAll("_","");
 								if(result.contains("~")){
-									LocalDateTime[] localDateTimes = convertDateRange(time, "~");
+									String[] localDateTimes = time.split("~");
 									auto.setStartDate(localDateTimes[0]);
 									auto.setEndDate(localDateTimes[1]);
 								} else if (result.contains("-")) {
-									LocalDateTime[] localDateTimes = convertDateRange(time, "-");
+									String[] localDateTimes = time.split("-");
 									auto.setStartDate(localDateTimes[0]);
 									auto.setEndDate(localDateTimes[1]);
 								} else if (result.contains("~")) {
-									LocalDateTime[] localDateTimes = convertDateRange(time, "~");
+									String[] localDateTimes = time.split("~");
 									auto.setStartDate(localDateTimes[0]);
 									auto.setEndDate(localDateTimes[1]);
 								}
@@ -5451,26 +5468,6 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		this.updateBatchById(archivesAutoList);
 		return true;
 	}
-
-	public static LocalDateTime[] convertDateRange(String dateRange,String split) {
-		try {
-			String[] dates = dateRange.split(split);
-
-			DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
-
-			LocalDate startLocalDate = LocalDate.parse(dates[0], formatter);
-			LocalDate endLocalDate = LocalDate.parse(dates[1], formatter);
-
-			LocalDateTime startDateTime = startLocalDate.atStartOfDay(); // 00:00:00
-			LocalDateTime endDateTime = endLocalDate.atStartOfDay();; // 00:00:00
-
-			return new LocalDateTime[]{startDateTime, endDateTime};
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-		return new LocalDateTime[]{null, null};
-	}
-
 	public List<String> extractTextFromPDF(String pdfFilePath) throws IOException, InterruptedException {
 		//String PYTHON_SCRIPT_PATH = "C:\\Users\\hc01\\AppData\\Local\\Programs\\Python\\Python310\\Python\\pdfTextExtractorWindows.py";
 		//String PYTHON_INTERPRETER = "C:\\Users\\hc01\\AppData\\Local\\Programs\\Python\\Python310\\python.exe";
@@ -5529,9 +5526,7 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		}
 		System.out.println("进入识别9");
 		Type listType = new TypeToken<List<String>>(){}.getType();
-		List< String> result = gson.fromJson(gson.toJson(resultMap.get("lines")), listType);
-		System.out.println(result);
-		return result;
+		return gson.fromJson(gson.toJson(resultMap.get("lines")), listType);
 	}
 
 	@Scheduled(fixedDelay = 1000 * 60 * 10)
@@ -5565,7 +5560,6 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 			}
 		});
 	}
-
 	@Override
 	public String jointNomination(List<String> authId) {
 		StringBuilder sb = new StringBuilder();
@@ -5624,6 +5618,173 @@ public class ArchivesAutoServiceImpl extends BaseServiceImpl<ArchivesAutoMapper,
 		}
 		return sb.toString();
 	}
+
+	@Override
+	public Boolean updateArchivePage(List<Long> archiveIds) {
+		for (Long archiveId : archiveIds) {
+			ArchivesAuto archivesAuto = this.getById(archiveId);
+			List<ArchiveFile> files = archiveFileClient.getArchiveFileByArchivesId(archiveId+"", "");
+			if(!files.isEmpty()){
+				archivesAuto.setFileN(files.size());
+				archivesAuto.setPageN(files.stream().mapToInt(ArchiveFile::getFilePage).sum());
+				archivesAuto.setColourStatus(2);
+				baseMapper.updateById(archivesAuto);
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public String saveVolume(SaveVolumeDto dto) {
+		List<Long>updateIds=new ArrayList<>();
+		ArchivesAuto archivesAuto = this.getById(dto.getArchiveId());
+		archivesAuto.setName(dto.getArchiveName());
+		updateIds.add(archivesAuto.getId());
+		if(!dto.getList().isEmpty()){
+			archivesAuto.setIsVolume(1);
+			int x=1;
+			if(archivesAuto.getAutoFileSort()==null){
+				archivesAuto.setAutoFileSort(0);
+			}
+			for (SaveVolumeDto1 saveVolumeDto1 : dto.getList()) {
+				Long archiveId;
+				if(saveVolumeDto1.getId()==null){
+					ArchivesAuto auto = new ArchivesAuto();
+					BeanUtil.copy(archivesAuto,auto);
+					auto.setId(SnowFlakeUtil.getId());
+					auto.setName(saveVolumeDto1.getArchiveName());
+					auto.setIsVolume(2);
+					String treeSort = archivesAuto.getTreeSort();
+					if (treeSort == null) {
+						auto.setTreeSort("");
+					} else {
+						// 确保字符串至少有2位
+						if (treeSort.length() >= 2) {
+							// 截取最后两位
+							String lastTwoDigits = treeSort.substring(treeSort.length() - 2);
+							try {
+								// 转换为数字并加上x
+								int lastNumber = Integer.parseInt(lastTwoDigits);
+								int newNumber = lastNumber + x;
+								// 格式化为两位数字符串
+								String newLastDigits = String.format("%02d", newNumber);
+								// 替换最后两位
+								String newTreeSort = treeSort.substring(0, treeSort.length() - 2) + newLastDigits;
+								auto.setTreeSort(newTreeSort);
+							} catch (NumberFormatException e) {
+								// 如果转换失败,保持原值
+								auto.setTreeSort(treeSort);
+							}
+						} else {
+							// 如果字符串长度不足2位,直接加上x
+							try {
+								int number = Integer.parseInt(treeSort) + x;
+								auto.setTreeSort(String.valueOf(number));
+							} catch (NumberFormatException e) {
+								auto.setTreeSort(treeSort);
+							}
+						}
+					}
+					if(archivesAuto.getAutoFileSort()!=null){
+						auto.setAutoFileSort(archivesAuto.getAutoFileSort()+x);
+					}else {
+						auto.setAutoFileSort(x);
+					}
+					auto.setCreateTime(null);
+					x++;
+					archiveId= auto.getId();
+					baseMapper.insert(auto);
+				}else {
+					archiveId=saveVolumeDto1.getId();
+					ArchivesAuto auto = this.getById(archiveId);
+					auto.setName(saveVolumeDto1.getArchiveName());
+					baseMapper.updateById(auto);
+					String update="update u_archive_file set archive_id=old_archive_id,old_archive_id=null,is_volume = 0 where archive_id="+archiveId;
+					jdbcTemplate.update(update);
+				}
+				updateIds.add(archiveId);
+				String update = "UPDATE u_archive_file SET archive_id = ?, old_archive_id = ?, is_volume = 1 WHERE id IN (" + saveVolumeDto1.getFileIds() + ")";
+				jdbcTemplate.update(update, archiveId, dto.getArchiveId());
+				String volumeIds = archivesAuto.getVolumeIds();
+				if(StringUtils.isEmpty(volumeIds)){
+					volumeIds="";
+				}
+				archivesAuto.setVolumeIds(volumeIds+archiveId+",");
+				List<ArchiveFile> files1 = archiveFileClient.getArchiveFileByArchivesId(archiveId + "", "");
+				if(!files1.isEmpty()){
+					List<Integer> sorts = files1.stream()
+							.map(file -> Optional.ofNullable(file.getSort()).orElse(0))
+							.sorted()
+							.collect(Collectors.toList());
+					List<Integer> sorts1 = files1.stream()
+							.map(file -> Optional.ofNullable(file.getArchiveSort()).orElse(0))
+							.sorted()
+							.collect(Collectors.toList());
+					String[] fileIds = saveVolumeDto1.getFileIds().split(",");
+					if(sorts.size()==fileIds.length){
+						if(fileIds.length>0){
+							for (int i = 0; i < fileIds.length; i++) {
+								String updateSql = "UPDATE u_archive_file SET sort = ? ,archive_sort=? WHERE id = ?";
+								jdbcTemplate.update(updateSql, sorts.get(i), sorts1.get(i),fileIds[i]);
+							}
+						}
+					}
+				}
+			}
+			archivesAuto.setVolumeIds(archivesAuto.getVolumeIds().substring(0,archivesAuto.getVolumeIds().length()-1));
+		}
+		baseMapper.updateById(archivesAuto);
+		this.updateArchivePage(updateIds);
+		return "操作成功";
+	}
+
+	@Override
+	public boolean removeVolume(Long archiveId, Long volumeId) {
+		ArchivesAuto auto = this.getById(archiveId);
+		if(auto.getIsVolume()==1&&StringUtils.isNotEmpty(auto.getVolumeIds())){
+			String volumeIds = auto.getVolumeIds();
+			String[] archivesAutoIds = volumeIds.split(",");
+			if(archivesAutoIds.length<=1){
+				throw new ServiceException("不能删除所有的分卷");
+			}
+			if(auto.getVolumeIds().contains(volumeId+"")){
+				if(auto.getVolumeIds().endsWith(volumeId+"")){
+					auto.setVolumeIds(auto.getVolumeIds().replace(volumeId+"",""));
+				}else {
+					auto.setVolumeIds(auto.getVolumeIds().replace(volumeId+",",""));
+				}
+				if(auto.getVolumeIds().startsWith(",")){
+					auto.setVolumeIds(auto.getVolumeIds().substring(1));
+				}
+				if(auto.getVolumeIds().endsWith(",")){
+					auto.setVolumeIds(auto.getVolumeIds().substring(0,auto.getVolumeIds().length()-1));
+				}
+				String update = "UPDATE u_archive_file SET archive_id = ?, old_archive_id = null, is_volume = 0 WHERE archive_id = ?";
+				jdbcTemplate.update(update, archiveId, volumeId);
+			}
+			baseMapper.updateById(auto);
+			this.deleteLogic(Arrays.asList(volumeId));
+			this.updateArchivePage(Arrays.asList(archiveId,volumeId));
+		}
+		return true;
+	}
+
+	@Override
+	public boolean backVolume(Long fileId){
+		ArchiveFile archiveFile = archiveFileClient.getArchiveFileById(fileId);
+		Long id=archiveFile.getArchiveId();
+		List<ArchiveFile> files = archiveFileClient.getArchiveFileByArchivesId(archiveFile.getArchiveId()+"","");
+		if(files.size()<=1){
+			throw new ServiceException("回退失败");
+		}
+		if(archiveFile.getOldArchiveId()!=null){
+			archiveFile.setArchiveId(archiveFile.getOldArchiveId());
+			archiveFile.setIsVolume(0);
+			archiveFileClient.updateById2(archiveFile);
+			this.updateArchivePage(Arrays.asList(id,archiveFile.getArchiveId()));
+		}
+		return true;
+	}
 }
 
 

+ 49 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ClamAVClientScanner.java

@@ -0,0 +1,49 @@
+package org.springblade.archive.utils;
+
+import xyz.capybara.clamav.ClamavClient;
+import xyz.capybara.clamav.commands.scan.result.ScanResult;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * 病毒检测类
+ * @author LHB
+ */
+public class ClamAVClientScanner {
+    private final static String IP = "192.168.0.109";
+    private final static Integer PORT = 3310;
+
+    public static boolean scanRemoteFile(String fileUrl) throws IOException {
+        // 创建ClamAV客户端,默认连接本地3310端口
+        ClamavClient client = new ClamavClient(IP, PORT);
+        // 从远程URL下载文件
+        URL url = new URL(fileUrl);
+        try (InputStream inputStream = url.openStream()) {
+            // 扫描输入流
+            ScanResult result = client.scan(inputStream);
+
+            // 根据结果类型判断
+            if (result instanceof ScanResult.OK) {
+                return true;
+            } else if (result instanceof ScanResult.VirusFound) {
+                return false;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    public static boolean checkHealth() {
+        ClamavClient client = new ClamavClient(IP, PORT);
+        try {
+            client.ping();
+            System.out.println("ClamAV服务正常运行");
+            return true;
+        } catch (Exception e) {
+            System.err.println("ClamAV服务不可用: " + e.getMessage());
+            return false;
+        }
+    }
+}

+ 97 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileAnalysisResult.java

@@ -0,0 +1,97 @@
+package org.springblade.archive.utils;
+
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Data
+public class FileAnalysisResult {
+    private String filePath;
+    private String remoteUrl;
+    private String format;
+    private String compressionAlgorithm;
+    private Boolean standardCompression;
+    private Set<String> nonStandardAlgorithms = new HashSet<>();
+    private Boolean encrypted;
+    private String encryptionType;
+    private List<String> issues = new ArrayList<>();
+    private long fileSize;
+    private String downloadStatus;
+
+    public FileAnalysisResult(String filePath) {
+        this.filePath = filePath;
+    }
+
+    // Getter和Setter方法
+    public void setRemoteUrl(String remoteUrl) { this.remoteUrl = remoteUrl; }
+    public void setFormat(String format) { this.format = format; }
+    public void setCompressionAlgorithm(String algorithm) { this.compressionAlgorithm = algorithm; }
+    public void setStandardCompression(boolean standard) { this.standardCompression = standard; }
+    public void setNonStandardAlgorithms(Set<String> algorithms) { this.nonStandardAlgorithms = algorithms; }
+    public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; }
+    public void setEncryptionType(String encryptionType) { this.encryptionType = encryptionType; }
+    public void setFileSize(long fileSize) { this.fileSize = fileSize; }
+    public void setDownloadStatus(String status) { this.downloadStatus = status; }
+    public void addIssue(String issue) { this.issues.add(issue); }
+
+    public void printReport() {
+        System.out.println("=== 远程文件安全分析报告 ===");
+        if (remoteUrl != null) {
+            System.out.println("远程URL: " + remoteUrl);
+        }
+        System.out.println("本地临时文件: " + filePath);
+
+        if (downloadStatus != null) {
+            System.out.println("下载状态: " + downloadStatus);
+        }
+
+        if (fileSize > 0) {
+            System.out.println("文件大小: " + (fileSize / 1024) + " KB");
+        }
+
+        if (format != null) {
+            System.out.println("格式: " + format);
+            System.out.println("压缩算法: " + compressionAlgorithm);
+            System.out.println("使用标准压缩: " + (standardCompression ? "是" : "否"));
+
+            if (!standardCompression && !nonStandardAlgorithms.isEmpty()) {
+                System.out.println("非标准压缩算法:");
+                for (String algo : nonStandardAlgorithms) {
+                    System.out.println("  - " + algo);
+                }
+            }
+
+            System.out.println("是否加密: " + (encrypted ? "是" : "否"));
+            if (encrypted && encryptionType != null) {
+                System.out.println("加密类型: " + encryptionType);
+            }
+        }
+
+        if (!issues.isEmpty()) {
+            System.out.println("问题详情:");
+            for (String issue : issues) {
+                System.out.println("  ⚠️ " + issue);
+            }
+        }
+
+        System.out.println("\n安全评估: " + getSecurityAssessment());
+        System.out.println("=================================\n");
+    }
+
+    public String getSecurityAssessment() {
+        if (!standardCompression) {
+            return "❌ 高风险 - 发现非标准压缩算法";
+        }
+        if (encrypted) {
+            return "⚠️ 中风险 - 文件已加密";
+        }
+        if (!issues.isEmpty()) {
+            return "⚠️ 注意 - 存在潜在安全问题";
+        }
+        return "✅ 安全 - 文件使用标准压缩且未加密";
+    }
+}

+ 352 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileFormatSecurityChecker.java

@@ -0,0 +1,352 @@
+package org.springblade.archive.utils;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class FileFormatSecurityChecker {
+
+    // PDF相关常量
+    private static final byte[] PDF_HEADER = "%PDF".getBytes();
+    private static final String[] PDF_STANDARD_FILTERS = {
+            "FlateDecode", "LZWDecode", "ASCII85Decode", "ASCIIHexDecode",
+            "RunLengthDecode", "CCITTFaxDecode", "DCTDecode", "JBIG2Decode"
+    };
+
+    public static FileAnalysisResult analyzeFile(String filePath) throws IOException {
+        File file = new File(filePath);
+        if (!file.exists()) {
+            throw new FileNotFoundException("文件不存在: " + filePath);
+        }
+
+        String filename = file.getName().toLowerCase();
+        FileAnalysisResult result = new FileAnalysisResult(filePath);
+
+        if (filename.endsWith(".pdf")) {
+            return analyzePdf(file, result);
+        } else if (filename.endsWith(".xlsx")) {
+            return analyzeXlsx(file, result);
+        } else if (filename.endsWith(".png")) {
+            return analyzePng(file, result);
+        } else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg")) {
+            return analyzeJpeg(file, result);
+        } else {
+            result.addIssue("不支持的文件格式");
+            return result;
+        }
+    }
+
+    // PDF文件分析
+    private static FileAnalysisResult analyzePdf(File file, FileAnalysisResult result) throws IOException {
+        result.setFormat("PDF");
+
+        byte[] fileContent = Files.readAllBytes(file.toPath());
+        String content = new String(fileContent, "ISO-8859-1");
+
+        // 1. 检查加密
+        boolean isEncrypted = checkPdfEncryption(content);
+        result.setEncrypted(isEncrypted);
+
+        // 2. 检查压缩算法
+        CompressionAnalysis compressionAnalysis = checkPdfCompression(content);
+        result.setCompressionAlgorithm(compressionAnalysis.getAlgorithm());
+        result.setStandardCompression(compressionAnalysis.isStandard());
+        result.setNonStandardAlgorithms(compressionAnalysis.getNonStandardAlgorithms());
+
+        // 3. 检查其他安全问题
+        checkPdfSecurity(content, result);
+
+        return result;
+    }
+
+    private static boolean checkPdfEncryption(String pdfContent) {
+        // 检查加密字典
+        return pdfContent.contains("/Encrypt") ||
+                pdfContent.contains("/Filter/Standard") ||
+                pdfContent.contains("/CFM/") ||
+                pdfContent.contains("/StmF/") ||
+                pdfContent.contains("/StrF/");
+    }
+
+    private static CompressionAnalysis checkPdfCompression(String pdfContent) {
+        CompressionAnalysis analysis = new CompressionAnalysis();
+        Set<String> foundAlgorithms = new HashSet<>();
+        Set<String> nonStandardAlgorithms = new HashSet<>();
+
+        // 查找所有使用的压缩过滤器
+        String[] lines = pdfContent.split("\n");
+        for (String line : lines) {
+            if (line.contains("/Filter")) {
+                // 提取过滤器名称
+                for (String filter : PDF_STANDARD_FILTERS) {
+                    if (line.contains(filter)) {
+                        foundAlgorithms.add(filter);
+                    }
+                }
+
+                // 检查非标准过滤器
+                if (line.contains("/Filter") && !containsStandardFilter(line)) {
+                    // 提取可能的非标准过滤器名称
+                    String[] parts = line.split("/");
+                    for (String part : parts) {
+                        if (part.trim().length() > 3 && !part.trim().equals("Filter")) {
+                            boolean isStandard = false;
+                            for (String stdFilter : PDF_STANDARD_FILTERS) {
+                                if (part.contains(stdFilter.replace("Decode", ""))) {
+                                    isStandard = true;
+                                    break;
+                                }
+                            }
+                            if (!isStandard) {
+                                nonStandardAlgorithms.add(part.trim());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // 设置结果
+        if (!foundAlgorithms.isEmpty()) {
+            analysis.setAlgorithm(String.join(", ", foundAlgorithms));
+        } else {
+            analysis.setAlgorithm("未压缩或未知压缩");
+        }
+
+        analysis.setStandard(nonStandardAlgorithms.isEmpty());
+        analysis.setNonStandardAlgorithms(nonStandardAlgorithms);
+
+        return analysis;
+    }
+
+    private static boolean containsStandardFilter(String line) {
+        for (String filter : PDF_STANDARD_FILTERS) {
+            if (line.contains(filter)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void checkPdfSecurity(String pdfContent, FileAnalysisResult result) {
+        // 检查JavaScript(可能的安全风险)
+        if (pdfContent.contains("/JavaScript") || pdfContent.contains("/JS")) {
+            result.addIssue("包含JavaScript代码");
+        }
+
+        // 检查外部引用
+        if (pdfContent.contains("/Launch") || pdfContent.contains("/URI")) {
+            result.addIssue("包含外部引用或启动动作");
+        }
+    }
+
+    // XLSX文件分析
+    private static FileAnalysisResult analyzeXlsx(File file, FileAnalysisResult result) throws IOException {
+        result.setFormat("XLSX");
+
+        try (ZipFile zipFile = new ZipFile(file)) {
+            Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+            // 1. 检查加密
+            boolean isEncrypted = checkXlsxEncryption(zipFile);
+            result.setEncrypted(isEncrypted);
+
+            // 2. 检查压缩算法
+            CompressionAnalysis compressionAnalysis = checkXlsxCompression(zipFile);
+            result.setCompressionAlgorithm(compressionAnalysis.getAlgorithm());
+            result.setStandardCompression(compressionAnalysis.isStandard());
+            result.setNonStandardAlgorithms(compressionAnalysis.getNonStandardAlgorithms());
+
+            // 3. 检查其他安全问题
+            checkXlsxSecurity(zipFile, result);
+        }
+
+        return result;
+    }
+
+    private static boolean checkXlsxEncryption(ZipFile zipFile) {
+        return zipFile.getEntry("EncryptedPackage") != null ||
+                zipFile.getEntry("EncryptionInfo") != null ||
+                zipFile.getEntry("encryption") != null;
+    }
+
+    private static CompressionAnalysis checkXlsxCompression(ZipFile zipFile) {
+        CompressionAnalysis analysis = new CompressionAnalysis();
+        Set<String> nonStandardAlgorithms = new HashSet<>();
+
+        Enumeration<? extends ZipEntry> entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+            ZipEntry entry = entries.nextElement();
+            int method = entry.getMethod();
+
+            // ZIP标准压缩方法
+            if (method != ZipEntry.STORED && method != ZipEntry.DEFLATED) {
+                nonStandardAlgorithms.add("ZIP压缩方法: " + method);
+            }
+        }
+
+        analysis.setAlgorithm("DEFLATE (标准ZIP压缩)");
+        analysis.setStandard(nonStandardAlgorithms.isEmpty());
+        analysis.setNonStandardAlgorithms(nonStandardAlgorithms);
+
+        return analysis;
+    }
+
+    private static void checkXlsxSecurity(ZipFile zipFile, FileAnalysisResult result) {
+        // 检查宏
+        if (zipFile.getEntry("xl/vbaProject.bin") != null) {
+            result.addIssue("包含VBA宏");
+        }
+
+        // 检查外部链接
+        try {
+            ZipEntry externalLinks = zipFile.getEntry("xl/externalLinks/");
+            if (externalLinks != null) {
+                result.addIssue("包含外部链接");
+            }
+        } catch (Exception e) {
+            // 忽略
+        }
+    }
+
+    // PNG文件分析
+    private static FileAnalysisResult analyzePng(File file, FileAnalysisResult result) throws IOException {
+        result.setFormat("PNG");
+        result.setEncrypted(false); // PNG不支持加密
+
+        byte[] data = Files.readAllBytes(file.toPath());
+
+        // 1. 检查文件头
+        if (!isValidPngHeader(data)) {
+            result.addIssue("无效的PNG文件头");
+            return result;
+        }
+
+        // 2. 检查压缩算法
+        CompressionAnalysis compressionAnalysis = checkPngCompression(data);
+        result.setCompressionAlgorithm(compressionAnalysis.getAlgorithm());
+        result.setStandardCompression(compressionAnalysis.isStandard());
+        result.setNonStandardAlgorithms(compressionAnalysis.getNonStandardAlgorithms());
+
+        return result;
+    }
+
+    private static boolean isValidPngHeader(byte[] data) {
+        // PNG文件头: 89 50 4E 47 0D 0A 1A 0A
+        return data.length >= 8 &&
+                data[0] == (byte)0x89 &&
+                data[1] == 0x50 &&
+                data[2] == 0x4E &&
+                data[3] == 0x47 &&
+                data[4] == 0x0D &&
+                data[5] == 0x0A &&
+                data[6] == 0x1A &&
+                data[7] == 0x0A;
+    }
+
+    private static CompressionAnalysis checkPngCompression(byte[] data) {
+        CompressionAnalysis analysis = new CompressionAnalysis();
+
+        // 在IHDR块后查找压缩方法
+        // PNG压缩方法位于第12个字节(从0开始),但需要找到IDAT块
+        for (int i = 8; i < data.length - 4; i++) {
+            // 查找IDAT块
+            if (data[i] == 'I' && data[i+1] == 'D' && data[i+2] == 'A' && data[i+3] == 'T') {
+                // IDAT块中的压缩方法通常是DEFLATE
+                // PNG标准只允许使用DEFLATE压缩
+                analysis.setAlgorithm("DEFLATE");
+                analysis.setStandard(true);
+                return analysis;
+            }
+        }
+
+        // 如果找不到IDAT块,检查是否有非标准块
+        analysis.setAlgorithm("未知");
+        analysis.setStandard(false);
+        analysis.addNonStandardAlgorithm("无法识别标准压缩块");
+
+        return analysis;
+    }
+
+    // JPEG文件分析
+    private static FileAnalysisResult analyzeJpeg(File file, FileAnalysisResult result) throws IOException {
+        result.setFormat("JPEG");
+        result.setEncrypted(false); // JPEG不支持加密
+
+        byte[] data = Files.readAllBytes(file.toPath());
+
+        // 1. 检查文件头
+        if (!isValidJpegHeader(data)) {
+            result.addIssue("无效的JPEG文件头");
+            return result;
+        }
+
+        // 2. 检查压缩算法
+        CompressionAnalysis compressionAnalysis = checkJpegCompression(data);
+        result.setCompressionAlgorithm(compressionAnalysis.getAlgorithm());
+        result.setStandardCompression(compressionAnalysis.isStandard());
+        result.setNonStandardAlgorithms(compressionAnalysis.getNonStandardAlgorithms());
+
+        return result;
+    }
+
+    private static boolean isValidJpegHeader(byte[] data) {
+        // JPEG文件头: FF D8
+        return data.length >= 2 &&
+                data[0] == (byte)0xFF &&
+                data[1] == (byte)0xD8;
+    }
+
+    private static CompressionAnalysis checkJpegCompression(byte[] data) {
+        CompressionAnalysis analysis = new CompressionAnalysis();
+
+        // JPEG使用标准的DCT和Huffman编码
+        // 检查是否有非标准标记
+        Set<String> nonStandardAlgorithms = new HashSet<>();
+
+        for (int i = 0; i < data.length - 1; i++) {
+            if (data[i] == (byte)0xFF) {
+                int marker = data[i + 1] & 0xFF;
+
+                // 检查非标准或保留的标记
+                if (marker >= 0x02 && marker <= 0xBF && marker != 0x00) {
+                    // 这些是标准标记范围,但某些特定值可能表示非标准扩展
+                    if (marker == 0xFF || marker == 0x00) {
+                        // 无效标记
+                        nonStandardAlgorithms.add("无效JPEG标记: 0x" + Integer.toHexString(marker));
+                    }
+                }
+            }
+        }
+
+        analysis.setAlgorithm("DCT + Huffman (标准JPEG压缩)");
+        analysis.setStandard(nonStandardAlgorithms.isEmpty());
+        analysis.setNonStandardAlgorithms(nonStandardAlgorithms);
+
+        return analysis;
+    }
+}
+
+// 压缩分析结果类
+class CompressionAnalysis {
+    private String algorithm;
+    private boolean isStandard;
+    private Set<String> nonStandardAlgorithms = new HashSet<>();
+
+    // getter和setter方法
+    public String getAlgorithm() { return algorithm; }
+    public void setAlgorithm(String algorithm) { this.algorithm = algorithm; }
+
+    public boolean isStandard() { return isStandard; }
+    public void setStandard(boolean standard) { isStandard = standard; }
+
+    public Set<String> getNonStandardAlgorithms() { return nonStandardAlgorithms; }
+    public void setNonStandardAlgorithms(Set<String> nonStandardAlgorithms) {
+        this.nonStandardAlgorithms = nonStandardAlgorithms;
+    }
+    public void addNonStandardAlgorithm(String algorithm) {
+        this.nonStandardAlgorithms.add(algorithm);
+    }
+}

+ 33 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileExtension.java

@@ -0,0 +1,33 @@
+package org.springblade.archive.utils;
+
+import java.net.URL;
+import java.nio.file.Paths;
+
+/**
+ * @author LHB
+ */
+public class RemoteFileExtension {
+    public static String getFileExtensionFromUrl(String fileUrl) {
+        try {
+            URL url = new URL(fileUrl);
+            String path = url.getPath();
+
+            // 处理可能包含查询参数的情况
+            int questionMarkIndex = path.indexOf('?');
+            if (questionMarkIndex != -1) {
+                path = path.substring(0, questionMarkIndex);
+            }
+
+            // 使用 Paths 获取文件名并提取后缀
+            String fileName = Paths.get(path).getFileName().toString();
+
+            int lastDotIndex = fileName.lastIndexOf('.');
+            if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
+                return fileName.substring(lastDotIndex + 1).toLowerCase();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+}

+ 306 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileMD5Calculator.java

@@ -0,0 +1,306 @@
+package org.springblade.archive.utils;
+
+import lombok.Data;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class RemoteFileMD5Calculator {
+
+    private static final int BUFFER_SIZE = 64 * 1024; // 64KB
+    private static final int CONNECT_TIMEOUT = 10000; // 10秒
+    private static final int READ_TIMEOUT = 30000; // 30秒
+
+    /**
+     * 下载远程文件并计算MD5
+     */
+    public static MD5Result calculateRemoteFileMD5(String fileUrl) {
+        return calculateRemoteFileMD5(fileUrl, null);
+    }
+
+    public static MD5Result calculateRemoteFileMD5(String fileUrl, ProgressListener progressListener) {
+        HttpURLConnection connection = null;
+        InputStream inputStream = null;
+
+        try {
+            URL url = new URL(fileUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(CONNECT_TIMEOUT);
+            connection.setReadTimeout(READ_TIMEOUT);
+
+            // 设置请求头
+            connection.setRequestProperty("User-Agent",
+                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                return new MD5Result(null, false, "HTTP错误: " + responseCode + " - " + connection.getResponseMessage(),0);
+            }
+
+            long contentLength = connection.getContentLengthLong();
+            String contentType = connection.getContentType();
+
+            System.out.println("开始下载文件: " + fileUrl);
+            System.out.println("文件大小: " + formatFileSize(contentLength));
+            System.out.println("Content-Type: " + contentType);
+
+            inputStream = connection.getInputStream();
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int bytesRead;
+            long totalBytesRead = 0;
+            long lastProgressUpdate = 0;
+
+            if (progressListener != null) {
+                progressListener.onStart(contentLength);
+            }
+
+            while ((bytesRead = inputStream.read(buffer)) != -1) {
+                md.update(buffer, 0, bytesRead);
+                totalBytesRead += bytesRead;
+
+                // 更新进度
+                if (progressListener != null) {
+                    long currentTime = System.currentTimeMillis();
+                    if (totalBytesRead - lastProgressUpdate >= 1024 * 1024 || // 每1MB更新一次
+                            totalBytesRead == contentLength) {
+
+                        double progress = contentLength > 0 ?
+                                (double) totalBytesRead / contentLength * 100 : 0;
+                        long elapsedTime = currentTime - progressListener.getStartTime();
+                        double speed = elapsedTime > 0 ?
+                                (double) totalBytesRead / elapsedTime * 1000 : 0;
+
+                        progressListener.onProgress(totalBytesRead, contentLength, progress, speed);
+                        lastProgressUpdate = totalBytesRead;
+                    }
+                }
+            }
+
+            byte[] digest = md.digest();
+            String md5Hash = bytesToHex(digest);
+
+            if (progressListener != null) {
+                progressListener.onComplete(md5Hash, totalBytesRead);
+            }
+
+            return new MD5Result(md5Hash, true, "下载并计算成功", contentLength);
+
+        } catch (Exception e) {
+            return new MD5Result(null, false, "错误: " + e.getMessage(), 0);
+        } finally {
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    System.err.println("关闭输入流时出错: " + e.getMessage());
+                }
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 获取远程文件的MD5(如果服务器提供ETag或Content-MD5头部)
+     */
+    public static MD5Result getRemoteFileMD5FromHeaders(String fileUrl) {
+        HttpURLConnection connection = null;
+
+        try {
+            URL url = new URL(fileUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("HEAD"); // 只请求头部信息
+            connection.setConnectTimeout(CONNECT_TIMEOUT);
+            connection.setReadTimeout(READ_TIMEOUT);
+
+            connection.setRequestProperty("User-Agent",
+                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                return new MD5Result(null, false,
+                        "HTTP错误: " + responseCode + " - " + connection.getResponseMessage());
+            }
+
+            // 检查常见的MD5相关头部
+            String etag = connection.getHeaderField("ETag");
+            String contentMD5 = connection.getHeaderField("Content-MD5");
+            String contentLength = connection.getHeaderField("Content-Length");
+
+            System.out.println("ETag: " + etag);
+            System.out.println("Content-MD5: " + contentMD5);
+            System.out.println("Content-Length: " + contentLength);
+
+            // 尝试从ETag提取MD5(常见格式:"md5-hash" 或 "md5-hash-extra")
+            if (etag != null) {
+                // 移除引号
+                etag = etag.replace("\"", "");
+
+                // 检查是否是MD5格式(32位十六进制)
+                if (etag.matches("[a-fA-F0-9]{32}")) {
+                    return new MD5Result(etag.toLowerCase(), true,
+                            "从ETag头部获取", Long.parseLong(contentLength != null ? contentLength : "0"));
+                }
+
+                // 检查是否包含MD5(如:"5d41402abc4b2a76b9719d911017c592-gzip")
+                if (etag.length() >= 32) {
+                    String possibleMD5 = etag.substring(0, 32);
+                    if (possibleMD5.matches("[a-fA-F0-9]{32}")) {
+                        return new MD5Result(possibleMD5.toLowerCase(), true,
+                                "从ETag提取", Long.parseLong(contentLength != null ? contentLength : "0"));
+                    }
+                }
+            }
+
+            // 直接使用Content-MD5(需要Base64解码)
+            if (contentMD5 != null) {
+                try {
+                    byte[] md5Bytes = java.util.Base64.getDecoder().decode(contentMD5);
+                    String md5Hash = bytesToHex(md5Bytes);
+                    return new MD5Result(md5Hash, true,
+                            "从Content-MD5头部获取", Long.parseLong(contentLength != null ? contentLength : "0"));
+                } catch (IllegalArgumentException e) {
+                    System.out.println("Content-MD5 Base64解码失败: " + e.getMessage());
+                }
+            }
+
+            return new MD5Result(null, false,
+                    "服务器未提供MD5信息", Long.parseLong(contentLength != null ? contentLength : "0"));
+
+        } catch (Exception e) {
+            return new MD5Result(null, false, "错误: " + e.getMessage(), 0);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 字节数组转十六进制字符串
+     */
+    private static String bytesToHex(byte[] bytes) {
+        StringBuilder hexString = new StringBuilder(bytes.length * 2);
+        for (byte b : bytes) {
+            String hex = Integer.toHexString(0xff & b);
+            if (hex.length() == 1) {
+                hexString.append('0');
+            }
+            hexString.append(hex);
+        }
+        return hexString.toString();
+    }
+
+    /**
+     * 格式化文件大小
+     */
+    private static String formatFileSize(long size) {
+        if (size < 1024) return size + " B";
+        if (size < 1024 * 1024) return String.format("%.2f KB", size / 1024.0);
+        if (size < 1024 * 1024 * 1024) return String.format("%.2f MB", size / (1024.0 * 1024.0));
+        return String.format("%.2f GB", size / (1024.0 * 1024.0 * 1024.0));
+    }
+
+    /**
+     * 进度监听器接口
+     */
+    public interface ProgressListener {
+        void onStart(long totalSize);
+        void onProgress(long bytesRead, long totalSize, double progress, double speedBytesPerSec);
+        void onComplete(String md5Hash, long totalBytesProcessed);
+        long getStartTime();
+    }
+
+    /**
+     * 默认进度监听器
+     */
+    public static class DefaultProgressListener implements ProgressListener {
+        private long startTime;
+
+        @Override
+        public void onStart(long totalSize) {
+            this.startTime = System.currentTimeMillis();
+            System.out.println("开始下载,文件大小: " + formatFileSize(totalSize));
+        }
+
+        @Override
+        public void onProgress(long bytesRead, long totalSize, double progress, double speedBytesPerSec) {
+            String progressBar = createProgressBar(progress, 20);
+            System.out.printf("\r%s [%s] %.2f%% | 速度: %s/s",
+                    progressBar,
+                    formatFileSize(bytesRead) + "/" + formatFileSize(totalSize),
+                    progress,
+                    formatFileSize((long) speedBytesPerSec));
+        }
+
+        @Override
+        public void onComplete(String md5Hash, long totalBytesProcessed) {
+            long endTime = System.currentTimeMillis();
+            long elapsedTime = endTime - startTime;
+            System.out.printf("\n下载完成! 耗时: %.2f秒 | MD5: %s\n",
+                    elapsedTime / 1000.0, md5Hash);
+        }
+
+        @Override
+        public long getStartTime() {
+            return startTime;
+        }
+
+        private String createProgressBar(double progress, int length) {
+            int filledLength = (int) (progress / 100 * length);
+            StringBuilder bar = new StringBuilder();
+            for (int i = 0; i < length; i++) {
+                if (i < filledLength) {
+                    bar.append("=");
+                } else if (i == filledLength) {
+                    bar.append(">");
+                } else {
+                    bar.append(" ");
+                }
+            }
+            return bar.toString();
+        }
+    }
+
+    /**
+     * MD5计算结果封装
+     */
+    @Data
+    public static class MD5Result {
+        public final String md5Hash;
+        public final boolean success;
+        public final String message;
+        public final long fileSize;
+
+        public MD5Result(String md5Hash, boolean success, String message, long fileSize) {
+            this.md5Hash = md5Hash;
+            this.success = success;
+            this.message = message;
+            this.fileSize = fileSize;
+        }
+        public MD5Result(String md5Hash, boolean success, String message) {
+            this.md5Hash = md5Hash;
+            this.success = success;
+            this.message = message;
+            this.fileSize = 0;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("=== 远程文件MD5计算结果 ===\n");
+            sb.append("文件大小: ").append(formatFileSize(fileSize)).append("\n");
+            sb.append("计算状态: ").append(success ? "成功" : "失败").append("\n");
+            sb.append("MD5值: ").append(md5Hash != null ? md5Hash : "N/A").append("\n");
+            sb.append("信息: ").append(message);
+            return sb.toString();
+        }
+    }
+}

+ 114 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileSecurityChecker.java

@@ -0,0 +1,114 @@
+package org.springblade.archive.utils;
+
+import java.io.*;
+import java.net.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.zip.ZipFile;
+
+public class RemoteFileSecurityChecker {
+
+    // 临时文件目录
+    private static final String TEMP_DIR = System.getProperty("java.io.tmpdir") + "/file-security-check/";
+
+    static {
+        // 创建临时目录
+        new File(TEMP_DIR).mkdirs();
+    }
+
+    /**
+     * 分析远程文件
+     */
+    public static FileAnalysisResult analyzeRemoteFile(String fileUrl) throws IOException {
+        System.out.println("开始分析远程文件: " + fileUrl);
+        File tempFile = null;
+        // 下载文件到临时位置
+        if(fileUrl.contains("http")){
+            tempFile = downloadRemoteFile(fileUrl);
+        }else{
+            tempFile = new File(fileUrl);
+        }
+
+        try {
+            // 使用原有的分析逻辑
+            FileAnalysisResult result = FileFormatSecurityChecker.analyzeFile(tempFile.getAbsolutePath());
+            result.setRemoteUrl(fileUrl);
+            return result;
+        } finally {
+            // 清理临时文件
+            if (tempFile.exists()) {
+                tempFile.delete();
+            }
+        }
+    }
+
+    /**
+     * 下载远程文件到临时位置
+     */
+    private static File downloadRemoteFile(String fileUrl) throws IOException {
+        URL url = new URL(fileUrl);
+        String filename = extractFilenameFromUrl(fileUrl);
+        File tempFile = new File(TEMP_DIR + System.currentTimeMillis() + "_" + filename);
+
+        System.out.println("下载文件到: " + tempFile.getAbsolutePath());
+
+        try (InputStream in = url.openStream();
+             FileOutputStream out = new FileOutputStream(tempFile)) {
+
+            byte[] buffer = new byte[8192];
+            int bytesRead;
+            long totalBytes = 0;
+
+            while ((bytesRead = in.read(buffer)) != -1) {
+                out.write(buffer, 0, bytesRead);
+                totalBytes += bytesRead;
+
+                // 文件大小限制检查(可选)
+                if (totalBytes > 1 * 1024 * 1024 * 1024) { // 1024MB限制
+                    throw new IOException("文件大小超过限制(1024MB)");
+                }
+            }
+        }
+
+        return tempFile;
+    }
+
+    /**
+     * 从URL中提取文件名
+     */
+    private static String extractFilenameFromUrl(String fileUrl) {
+        try {
+            URL url = new URL(fileUrl);
+            String path = url.getPath();
+            if (path.contains("/")) {
+                return path.substring(path.lastIndexOf("/") + 1);
+            }
+            return path;
+        } catch (MalformedURLException e) {
+            // 如果URL解析失败,尝试直接从字符串提取
+            String[] parts = fileUrl.split("/");
+            return parts[parts.length - 1];
+        }
+    }
+
+    /**
+     * 批量分析远程文件
+     */
+    public static List<FileAnalysisResult> analyzeMultipleRemoteFiles(List<String> fileUrls) {
+        List<FileAnalysisResult> results = new ArrayList<>();
+
+        for (String fileUrl : fileUrls) {
+            try {
+                FileAnalysisResult result = analyzeRemoteFile(fileUrl);
+                results.add(result);
+            } catch (Exception e) {
+                FileAnalysisResult errorResult = new FileAnalysisResult(fileUrl);
+                errorResult.addIssue("下载或分析失败: " + e.getMessage());
+                errorResult.setRemoteUrl(fileUrl);
+                results.add(errorResult);
+            }
+        }
+
+        return results;
+    }
+}

+ 25 - 0
blade-service/blade-business/src/main/java/org/springblade/business/controller/ArchiveFileController.java

@@ -12,6 +12,8 @@ import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import lombok.AllArgsConstructor;
 
 import org.apache.commons.lang.StringUtils;
+import org.springblade.business.dto.VolumeDto1;
+import org.springblade.business.dto.VolumeDto3;
 import org.springblade.business.entity.MessageWarning;
 import org.springblade.business.entity.Task;
 import org.springblade.business.entity.TaskParallel;
@@ -42,6 +44,7 @@ import org.springblade.manager.vo.ArchiveTreeVO2;
 import org.springblade.system.entity.Role;
 import org.springblade.system.feign.ISysClient;
 import org.springframework.beans.BeanUtils;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.RequestParam;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -532,4 +535,26 @@ public class ArchiveFileController extends BladeController {
     }
 
 
+    //@Scheduled(cron = "0 04 10 * * ?")
+    @GetMapping("/flushArchiveFileSort")
+    public void flushArchiveFileSort(){
+        Long projectId=1935614356128206849L;
+        archiveFileService.flushArchiveFileSort(projectId);
+    }
+
+    @GetMapping("/selectVolumeAfter")
+    @ApiOperationSupport(order = 3)
+    @ApiOperation(value = "查询分卷之后的文件")
+    public R<List<VolumeDto1>> selectVolumeAfter(@RequestParam Long archiveId){
+        List<VolumeDto1> list = archiveFileService.selectVolumeAfter(archiveId);
+        return R.data(list);
+    }
+
+    @GetMapping("/selectVolumeBefore")
+    @ApiOperationSupport(order = 3)
+    @ApiOperation(value = "查询分卷之前的文件")
+    public R<VolumeDto3> selectVolumeBefore(@RequestParam Long archiveId,Integer type){
+        VolumeDto3 dto = archiveFileService.selectVolumeBefore(archiveId,type);
+        return R.data(dto);
+    }
 }

+ 6 - 1
blade-service/blade-business/src/main/java/org/springblade/business/controller/EVisaTaskCheckController.java

@@ -398,7 +398,12 @@ public class EVisaTaskCheckController {
                 flow.setDisabled(true);
                 List<String> names = new ArrayList<>();
                 for (Long id : flowUser) {
-                    names.add(userNamesMap.get(id).getName());
+                    User user = userNamesMap.get(id);
+                    if(user==null || Func.isNull(user)){
+                        names.add(id+"");
+                    }else{
+                        names.add(user.getName());
+                    }
                 }
                 flow.setTips("以下用户账号已经删除:"+String.join(",",names));
                 continue;

+ 8 - 16
blade-service/blade-business/src/main/java/org/springblade/business/controller/InformationWriteQueryController.java

@@ -380,24 +380,15 @@ public class InformationWriteQueryController extends BladeController {
                         WbsTreeContract::getNodeName,
                         (existing, replacement) -> replacement // 如果键重复,保留后者
                 ));
-        StringBuilder result = new StringBuilder("");
+        List<String> result = new ArrayList<>();
         for (Integer i : index) {
-            if(i==0){
-                if(map.containsKey(1)){
-                    result.append(map.get(1));
-                }
-            }
-            else if(i==1){
-                if(map.containsKey(18)){
-                    result.append(map.get(18));
-                }
-            }else {
-                if(map.containsKey(i)){
-                    result.append(map.get(i));
-                }
+            String title = map.get(i == 0 ? 1 : i == 1 && map.containsKey(18) ? 18 : i);
+            if(title == null || result.stream().anyMatch(f -> f.contains(title))){
+                continue;
             }
+            result.add(title);
         }
-        return result.toString();
+        return String.join("", result);
     }
 
     @PostMapping("/previewNodeName")
@@ -4395,9 +4386,10 @@ public R<Boolean> saveContractTreeNode(@RequestBody AddContractTreeNodeVO vo) {
                 //if (Optional.ofNullable(half.getNodeType()).orElse(7) <= 6) {
                     newData.setIsTypePrivatePid(half.getPKeyId());
                 //}
+                //2025年12月05日10:18更改需求,需要与项目级一致
                 if (half.getType() != null && new Integer("2").equals(half.getType())) {
                     //2023年8月1日14:41:03更改需求,isBussShow默认=1
-                    newData.setIsBussShow(1);
+                    newData.setIsBussShow(half.getDefaultConceal() + 1);
                 }
 
                 //获取当前所有复制的节点的最大sort

+ 15 - 0
blade-service/blade-business/src/main/java/org/springblade/business/controller/TrialDetectionController.java

@@ -45,6 +45,7 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -848,4 +849,18 @@ public class TrialDetectionController extends BladeController {
         iTrialSelfInspectionRecordService.updateSort(dto);
         return R.success("成功");
     }
+
+    /**
+     * pdf文件下载
+     * @param ids 试验记录ids
+     * @param classify 1 施工、2 监理、3业主
+     * @param type 1 整份下载,2 报告表下载,3 记录表下载,4 委托单下载
+     * @param response 响应
+     */
+    @PostMapping("/self/download")
+    @ApiOperationSupport(order = 31)
+    @ApiOperation(value = "pdf文件下载", notes = "传入记录ids,classify:1 施工、2 监理、3业主, type:1 整份下载、2 报告表下载、3 记录表下载、4 委托单下载")
+    public void download(@RequestParam String ids, @RequestParam Integer classify, @RequestParam Integer type, HttpServletResponse response) {
+        iTrialSelfInspectionRecordService.download(ids, classify, type, response);
+    }
 }

+ 37 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ArchiveFileClientImpl.java

@@ -17,9 +17,11 @@ import org.springblade.business.feign.ArchiveFileClient;
 import org.springblade.business.mapper.ArchiveFileMapper;
 import org.springblade.business.service.IArchiveFileService;
 import org.springblade.business.service.ITaskService;
+import org.springblade.business.utils.DigestUtil;
 import org.springblade.business.vo.ArchiveFileVO;
 import org.springblade.common.utils.FileUtils;
 import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.manager.entity.ContractInfo;
 import org.springblade.manager.enums.StorageTypeEnum;
 import org.springblade.manager.feign.ContractClient;
@@ -49,6 +51,9 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
 
     @Override
     public void saveArchiveFile(ArchiveFileVO vo) {
+        if (vo.getList() != null) {
+            vo.getList().forEach(ArchiveFileClientImpl::digestMd5);
+        }
         this.iArchiveFileService.saveArchiveFile(vo.getList());
     }
 
@@ -281,6 +286,9 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
 
     @Override
     public void addArchiveFile(List<ArchiveFile> files) {
+        for (ArchiveFile file : files) {
+            digestMd5(file);
+        }
         iArchiveFileService.saveBatch(files);
     }
 
@@ -310,6 +318,9 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
 
     @Override
     public void addArchiveFileEx(List<ArchiveFile> files) {
+        for (ArchiveFile file : files) {
+            digestMd5(file);
+        }
         iArchiveFileService.saveBatch(files);
     }
 
@@ -364,9 +375,27 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
 
     @Override
     public void saveArchiveFileByBIM(ArchiveFile archiveFile) {
+        digestMd5(archiveFile);
         iArchiveFileService.save(archiveFile);
     }
 
+    private static void digestMd5(ArchiveFile archiveFile) {
+        try {
+            if (StringUtil.isNotBlank(archiveFile.getFileUrl())) {
+                archiveFile.setFileMd5(DigestUtil.md5OfUrl(archiveFile.getFileUrl()));
+            }
+            if (StringUtil.isNotBlank(archiveFile.getPdfFileUrl())) {
+                if (Objects.equals(archiveFile.getPdfFileUrl(), archiveFile.getFileUrl())) {
+                    archiveFile.setPdfMd5(archiveFile.getFileMd5());
+                } else {
+                    archiveFile.setPdfMd5(DigestUtil.md5OfUrl(archiveFile.getPdfFileUrl()));
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
     @Override
     public List<ArchiveFile> getArchiveFileByArchiveIds(String archiveIds) {
         return iArchiveFileService.getArchiveFileByArchivesId(archiveIds,null);
@@ -471,9 +500,17 @@ public class ArchiveFileClientImpl implements ArchiveFileClient {
 
     @Override
     public void saveBatchArchiveFile(List<ArchiveFile> list) {
+        for (ArchiveFile file : list) {
+            digestMd5(file);
+        }
         iArchiveFileService.saveBatch(list);
     }
 
+    @Override
+    public ArchiveFile getArchiveFileById(Long fileId) {
+        return iArchiveFileService.getById(fileId);
+    }
+
     @Override
     public Integer selectMaxSortByContractId(Long contractId) {
         return iArchiveFileService.selectMaxSortByContractId(contractId);

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/ContractLogClientImpl.java

@@ -19,6 +19,7 @@ import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.Func;
 import org.springblade.manager.entity.WbsTreePrivate;
 import org.springblade.manager.feign.WbsTreePrivateClient;
+import org.springblade.resource.feign.NewIOSSClient;
 import org.springframework.beans.BeanUtils;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.web.bind.annotation.RestController;
@@ -39,6 +40,7 @@ public class ContractLogClientImpl implements ContractLogClient {
 
     private final WbsTreePrivateClient wbsTreePrivateClient;
     private final JdbcTemplate jdbcTemplate;
+    private final NewIOSSClient newIOSSClient;
 
     @Override
     public List<JSONObject> queryContractLogWbsByBusinessId(String businessId) {

+ 7 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/MetadataClassificationClientImpl.java

@@ -8,6 +8,8 @@ import org.springblade.business.vo.MetadataClassificationVO;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -42,4 +44,9 @@ public class MetadataClassificationClientImpl implements MetadataClassificationC
     public boolean createMetadataFiles(List<Long> fileIds){
         return iMetadataClassificationService.createMetadataFiles(fileIds, 0);
     }
+
+    @Override
+    public List<HashMap<String, Object>> getMetadaFileByFileId(Long id) {
+        return iMetadataClassificationService.getMetadaFileByFileId(id);
+    }
 }

+ 2 - 0
blade-service/blade-business/src/main/java/org/springblade/business/mapper/ArchiveFileMapper.java

@@ -124,4 +124,6 @@ public interface ArchiveFileMapper extends BaseMapper<ArchiveFile> {
     List<ArchiveFile> getAllArchiveFileByIds(@Param("strList") List<String> strList);
 
     Integer selectMaxSortByContractId(@Param("contractId") Long contractId);
+
+    List<ArchiveFile> selectArchiveFileByProjectId(@Param("projectId") Long projectId);
 }

+ 19 - 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`
@@ -575,4 +569,7 @@
     <select id="selectMaxSortByContractId" resultType="java.lang.Integer">
         select max(sort) from u_archive_file where contract_id = #{contractId}
     </select>
+    <select id="selectArchiveFileByProjectId" resultType="org.springblade.business.entity.ArchiveFile">
+        select id,node_id,sort from u_archive_file where project_id = #{projectId} and is_deleted = 0
+    </select>
 </mapper>

+ 2 - 2
blade-service/blade-business/src/main/java/org/springblade/business/mapper/InformationQueryMapper.xml

@@ -1022,13 +1022,13 @@
     </select>
 
     <update id="addCheckPdfInfoByNodeId" >
-        update u_information_query a set a.chek_status=1
+        update u_information_query a set a.chek_status=1, check_desc = ''
         where a.is_deleted = 0 and a.classify=#{classify} and a.wbs_id
         in( select b.p_key_id from m_wbs_tree_contract b where b.is_deleted = 0 and b.ancestors_p_id like CONCAT(CONCAT('%', #{ids}), '%') and b.is_deleted = 0) and a.status = 2
     </update>
 
     <update id="addCheckPdfInfoByIds">
-        update u_information_query set chek_status=1
+        update u_information_query set chek_status=1, check_desc = ''
         where status = 2 and is_deleted = 0 and classify=#{classify} and id in
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}

+ 8 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/IArchiveFileService.java

@@ -18,6 +18,8 @@ package org.springblade.business.service;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import org.springblade.business.dto.VolumeDto1;
+import org.springblade.business.dto.VolumeDto3;
 import org.springblade.business.entity.ArchiveFile;
 import org.springblade.business.vo.ArchiveFileVO;
 import org.springblade.core.mp.base.BaseService;
@@ -79,4 +81,10 @@ public interface IArchiveFileService extends BaseService<ArchiveFile> {
     boolean sortByFileTime(Long nodeId);
 
     Integer selectMaxSortByContractId(Long contractId);
+
+    void flushArchiveFileSort(Long projectId);
+
+    List<VolumeDto1> selectVolumeAfter(Long archiveId);
+
+    VolumeDto3 selectVolumeBefore(Long archiveId,Integer type);
 }

+ 3 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/IMetadataClassificationService.java

@@ -5,6 +5,7 @@ import org.springblade.business.entity.MetadataClassification;
 import org.springblade.business.vo.MetadataClassificationVO;
 import org.springblade.core.mp.base.BaseService;
 
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -28,4 +29,6 @@ public interface IMetadataClassificationService extends BaseService<MetadataClas
     List<MetadataClassification> getMetadataClassification();
 
     boolean createMetadataFiles(List<Long> fileIds, Integer type);
+
+    List<HashMap<String, Object>> getMetadaFileByFileId(Long id);
 }

+ 3 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/ITrialSelfInspectionRecordService.java

@@ -8,6 +8,7 @@ import org.springblade.core.mp.base.BaseService;
 import org.springblade.core.tool.api.R;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import java.io.FileNotFoundException;
 import java.util.List;
 
@@ -49,4 +50,6 @@ public interface ITrialSelfInspectionRecordService extends BaseService<TrialSelf
     Long saveBaseInfo(TrialSeleInspectionRecordInfoDTO vo);
 
     void updateSort(TrialSelfInspectionRecordPageDTO dto);
+
+    void download(String ids, Integer classify, Integer type, HttpServletResponse response);
 }

+ 129 - 1
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/ArchiveFileServiceImpl.java

@@ -2,12 +2,22 @@ package org.springblade.business.service.impl;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.apache.commons.lang.StringUtils;
+import org.springblade.archive.entity.ArchivesAuto;
+import org.springblade.archive.feign.ArchiveAutoClient;
 import org.springblade.archive.feign.ArchiveInspectionInfoClient;
+import org.springblade.business.dto.VolumeDto1;
+import org.springblade.business.dto.VolumeDto2;
+import org.springblade.business.dto.VolumeDto3;
+import org.springblade.business.dto.VolumeDto4;
 import org.springblade.business.entity.ArchiveFile;
 import org.springblade.business.entity.Task;
 import org.springblade.business.entity.TaskParallel;
+import org.springblade.business.utils.DigestUtil;
 import org.springblade.business.vo.ArchiveFileVO;
 import org.springblade.business.mapper.ArchiveFileMapper;
 import org.springblade.business.service.IArchiveFileService;
@@ -15,9 +25,12 @@ import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.manager.entity.ArchiveTreeContract;
+import org.springblade.manager.feign.ArchiveTreeContractClient;
 import org.springblade.resource.feign.NewIOSSClient;
 import org.springblade.system.entity.DictBiz;
 import org.springblade.system.feign.IDictBizClient;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -46,6 +59,8 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
 
     private final ArchiveInspectionInfoClient archiveInspectionInfoClient;
     private final JdbcTemplate jdbcTemplate;
+    private final ArchiveTreeContractClient archiveTreeContractClient;
+    private final ArchiveAutoClient archiveAutoClient;
 
 
     @Override
@@ -129,6 +144,7 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
         this.saveBatch(JSONArray.parseArray(JSONObject.toJSONString(list), ArchiveFile.class));
     }
 
+
     @Override
     public void updateArchiveFileSort(List<ArchiveFileVO> list) {
         List<Integer> listInt = new ArrayList<>();
@@ -149,6 +165,20 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
                 if (list.get(i).getRectification() != null && list.get(i).getRectification() == 1) {
                     list.get(i).setRectification(2);
                 }
+                try {
+                    if (list.get(i).getFileUrl() != null) {
+                        list.get(i).setFileMd5(DigestUtil.md5OfUrl(list.get(i).getFileUrl()));
+                    }
+                    if (list.get(i).getPdfFileUrl() != null) {
+                        if (Objects.equals(list.get(i).getPdfFileUrl(), list.get(i).getFileUrl())) {
+                            list.get(i).setPdfMd5(list.get(i).getFileMd5());
+                        } else {
+                            list.get(i).setPdfMd5(DigestUtil.md5OfUrl(list.get(i).getPdfFileUrl()));
+                        }
+                    }
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
             }
         }
         // 删除oss文件
@@ -223,7 +253,6 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
         }
         this.updateBatchById(JSONArray.parseArray(JSONObject.toJSONString(list), ArchiveFile.class));
     }
-
     @Override
     public boolean updateArchiveFileByBoxName(Map<String, Object> jsons) {
         List<Object> list = (List<Object>) jsons.get("list");
@@ -369,4 +398,103 @@ public class ArchiveFileServiceImpl extends BaseServiceImpl<ArchiveFileMapper, A
     public Integer selectMaxSortByContractId(Long contractId) {
         return baseMapper.selectMaxSortByContractId(contractId);
     }
+
+    @Override
+    public void flushArchiveFileSort(Long projectId) {
+        List<ArchiveFile>archileFileList=baseMapper.selectArchiveFileByProjectId(projectId);
+        Map<String, List<ArchiveFile>> archiveMap = archileFileList.stream().collect(Collectors.groupingBy(ArchiveFile::getNodeId));
+        List<Long> NodeIds = archileFileList.stream()
+                .map(ArchiveFile::getNodeId)
+                .map(Long::parseLong)
+                .distinct()
+                .collect(Collectors.toList());
+        List<ArchiveTreeContract> sortIds = archiveTreeContractClient.getArchiveTreeContractListByListOrderByTreeSort(NodeIds);
+        int sort=10100001;
+        for (ArchiveTreeContract nodeId : sortIds) {
+            if(archiveMap.containsKey(nodeId.getId().toString())){
+                List<ArchiveFile> archiveFiles = archiveMap.get(nodeId.getId().toString());
+                if (archiveFiles != null) {
+                    archiveFiles = archiveFiles.stream()
+                            .sorted(Comparator.comparing(ArchiveFile::getSort, Comparator.nullsLast(Integer::compareTo)))
+                            .collect(Collectors.toList());
+                    for (ArchiveFile archiveFile : archiveFiles) {
+                        archiveFile.setSort(sort++);
+                    }
+                }
+            }
+        }
+        int i=1;
+        for (ArchiveFile file : archileFileList) {
+            this.update(Wrappers.<ArchiveFile>lambdaUpdate()
+                    .set(ArchiveFile::getSort, file.getSort())
+                    .eq(ArchiveFile::getId, file.getId()));
+            System.out.println("归档文件排序进度:" + i + "/" + archileFileList.size());
+            i++;
+        }
+        System.out.println("归档文件排序成功");
+    }
+
+    @Override
+    public List<VolumeDto1> selectVolumeAfter(Long archiveId) {
+        List<VolumeDto1> list = new ArrayList<>();
+        ArchivesAuto archivesAuto = archiveAutoClient.getArchiveById(archiveId);
+        if(archivesAuto.getIsVolume()==1&&StringUtils.isNotEmpty(archivesAuto.getVolumeIds())){
+            String volumeIds = archivesAuto.getVolumeIds();
+            String[] archivesAutoIds = volumeIds.split(",");
+            for (String autoId : archivesAutoIds) {
+                VolumeDto1 dto = new VolumeDto1();
+                ArchivesAuto auto = archiveAutoClient.getArchiveById(Long.parseLong(autoId));
+                if (auto != null) {
+                    dto.setId(auto.getId());
+                    dto.setName(auto.getName());
+                    String sql="select id,file_number,file_name,file_time,duty_user,file_page from u_archive_file where archive_id="+auto.getId()+" and is_deleted=0 order by archive_sort,sort,sort_num,create_time";
+                    List<VolumeDto2> dto2s = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(VolumeDto2.class));
+                    dto.setList(dto2s);
+                    if(!dto2s.isEmpty()){
+                        dto.setTotal(dto2s.stream().mapToInt(dto2 -> dto2.getFilePage() != null ? dto2.getFilePage() : 0).sum());
+                    }else {
+                        dto.setTotal(0);
+                    }
+                }
+                list.add(dto);
+            }
+            return list;
+        }else {
+            return list;
+        }
+    }
+
+    @Override
+    public VolumeDto3 selectVolumeBefore(Long archiveId,Integer type) {
+        ArchivesAuto auto = archiveAutoClient.getArchiveById(archiveId);
+        VolumeDto3 dto=new VolumeDto3();
+        //查询原案卷
+        if(type==1){
+            String sql="select id,file_number,file_name,file_time,duty_user,file_page from u_archive_file where archive_id="+auto.getId()+" and is_deleted=0 order by archive_sort,sort,sort_num,create_time";
+            List<VolumeDto4> dto2s = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(VolumeDto4.class));
+            dto.setId(auto.getId());
+            dto.setName(auto.getName());
+            dto.setTotal(auto.getPageN());
+            dto.setList(dto2s);
+        }else {
+            String archiveIds=auto.getId().toString();
+            //查询所有案卷
+            if(StringUtils.isNotEmpty(auto.getVolumeIds())){
+                archiveIds=archiveIds+","+auto.getVolumeIds();
+            }
+            if(archiveIds.startsWith(",")){
+                archiveIds=archiveIds.substring(1);
+            }
+            if(archiveIds.endsWith( ",")){
+                archiveIds=archiveIds.substring(0,archiveIds.length()-1);
+            }
+            String sql="select id,file_number,file_name,file_time,duty_user,file_page,archive_id from u_archive_file where archive_id in ("+archiveIds+") and is_deleted=0 order by archive_sort,sort,sort_num,create_time";
+            List<VolumeDto4> dto2s = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(VolumeDto4.class));
+            if(StringUtils.isNotEmpty(auto.getVolumeIds())){
+                dto2s.stream().filter(item ->auto.getVolumeIds().contains(item.getArchiveId().toString())).forEach(item ->item.setIsCheck(1));
+            }
+           dto.setList(dto2s);
+        }
+        return dto;
+    }
 }

+ 4 - 0
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/MetadataClassificationServiceImpl.java

@@ -1178,4 +1178,8 @@ public class MetadataClassificationServiceImpl
     }
 
 
+    @Override
+    public List<HashMap<String, Object>> getMetadaFileByFileId(Long id) {
+        return baseMapper.getMetadaFileByFileId(id);
+    }
 }

+ 138 - 4
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TrialSelfInspectionRecordServiceImpl.java

@@ -6,7 +6,6 @@ import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
@@ -31,6 +30,7 @@ import org.springblade.business.mapper.TrialSelfInspectionRecordMapper;
 import org.springblade.business.service.*;
 import org.springblade.business.utils.FileUtils;
 import org.springblade.business.utils.FileUtils2;
+import org.springblade.business.utils.PDFUtil;
 import org.springblade.business.utils.StringSPUtils;
 import org.springblade.business.vo.*;
 import org.springblade.business.wrapper.TrialSelfInspectionRecordWarpper;
@@ -41,8 +41,6 @@ import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.oss.model.BladeFile;
-import org.springblade.core.redis.cache.BladeRedis;
-import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.*;
@@ -71,6 +69,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -312,7 +311,7 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
      * @throws FileNotFoundException
      */
     private String getMergePdfToTrialNew(Long contractId, Long nodeId, Integer type) throws FileNotFoundException {
-        String sql = "select pdf_url,e_visa_pdf_url from u_information_query where wbs_id='" + nodeId + "' and status in(0,1,2) and contract_id ='" + contractId + "' and classify = '" + type + "'";
+        String sql = "select pdf_url,e_visa_pdf_url from u_information_query where wbs_id='" + nodeId + "' and status in(0,1,2) and contract_id ='" + contractId + "' and classify = '" + type + "' order by status desc";
         List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
         if (maps.size() >= 1) {
             Map<String, Object> stringObjectMap = maps.get(0);
@@ -2540,4 +2539,139 @@ public class TrialSelfInspectionRecordServiceImpl extends BaseServiceImpl<TrialS
         }
     }
 
+    @Override
+    public void download(String ids, Integer classify, Integer type, HttpServletResponse response) {
+        List<TrialSelfInspectionRecord> recordList = baseMapper.selectList(Wrappers.<TrialSelfInspectionRecord>lambdaQuery().in(TrialSelfInspectionRecord::getId, Func.toLongList(ids)));
+//                .eq(TrialSelfInspectionRecord::getTaskStatus, "已审批"));
+        if (recordList.isEmpty()) {
+            throw new ServiceException("请选择已审批的记录");
+        }
+        List<String> pdfList = null;
+        List<String> deletedFileUrls = new ArrayList<>();
+        //打包下载
+        try {
+            if (type == 1) {
+                // 整份下载
+                List<String> tempUrls = new ArrayList<>();
+                recordList.forEach(record -> {
+                    //合并的pdfUrl
+                    try {
+                        String pdf = this.getMergePdfToTrialNew(record.getContractId(), record.getId(), classify);
+                        if (StringUtil.hasText(pdf)) {
+
+                            String name = "";
+                            if(StringUtils.isNotEmpty(record.getReportNo())){
+                                name = "[" + record.getReportNo() + "]";
+                            }
+                            if(StringUtils.isNotEmpty(record.getTrialProjectName())){
+                                name = name + record.getTrialProjectName();
+                            }
+                            name = name + "试验检测报告及附件";
+                            tempUrls.add(pdf + "@@@" + name.replaceAll("\\\\", "_").replaceAll("/", "_"));
+                            deletedFileUrls.add(pdf);
+                        }
+                    } catch (FileNotFoundException e) {
+                        e.printStackTrace();
+                    }
+                });
+                pdfList = tempUrls;
+            } else if (type == 2) {
+                // 报告表下载
+                pdfList = recordList.stream().map(item -> {
+                    boolean flag = !StringUtil.hasText(item.getReportPdfUrl()) && StringUtil.hasText(item.getPdfUrl()) && StringUtil.hasText(item.getReportNo());
+                    String[] strArr = new String[] {"报告编号", item.getReportNo()};
+                    String url = item.getPdfUrl();
+                    if ("已审批".equals(item.getTaskStatus())) {
+                        String sql = "select e_visa_pdf_url from u_information_query where wbs_id='" + item.getId() + "' and status in(0,1,2) and contract_id ='" + item.getContractId() + "' and classify = '" + classify + "' order by status desc limit  1";
+                        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
+                        if (!maps.isEmpty()) {
+                            Map<String, Object> stringObjectMap = maps.get(0);
+                            if (stringObjectMap.get("e_visa_pdf_url") != null) {
+                                url = (String)stringObjectMap.get("e_visa_pdf_url");
+                                if ( flag) {
+                                    return FileUtils.extractPdfPages(url, strArr) + "@@@" + item.getReportNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                                } else {
+                                    return FileUtils.extractPdfPages(url, item.getReportPdfUrl()) + "@@@" + item.getReportNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                                }
+                            }
+                        }
+                    }
+                    if (StringUtil.hasText(item.getReportPdfUrl())) {
+                        return item.getReportPdfUrl() + "@@@" + item.getReportNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                    } else if (flag) {
+                        return FileUtils.extractPdfPages(url, strArr) + "@@@" + item.getReportNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                    }
+                    return "";
+                }).filter(StringUtil::hasText).collect(Collectors.toList());
+            } else if (type == 3) {
+                // 记录表下载
+                pdfList = recordList.stream().map(item -> {
+                    boolean flag = !StringUtil.hasText(item.getRecordPdfUrl()) && StringUtil.hasText(item.getPdfUrl()) && StringUtil.hasText(item.getRecordNo());
+                    String[] strArr = new String[] {"记录编号", item.getRecordNo()};
+                    String url = item.getPdfUrl();
+                    if ("已审批".equals(item.getTaskStatus())) {
+                        String sql = "select e_visa_pdf_url from u_information_query where wbs_id='" + item.getId() + "' and status in(0,1,2) and contract_id ='" + item.getContractId() + "' and classify = '" + classify + "' order by status desc limit  1";
+                        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
+                        if (!maps.isEmpty()) {
+                            Map<String, Object> stringObjectMap = maps.get(0);
+                            if (stringObjectMap.get("e_visa_pdf_url") != null) {
+                                url = (String)stringObjectMap.get("e_visa_pdf_url");
+                                if ( flag) {
+                                    return FileUtils.extractPdfPages(url, strArr) + "@@@" + item.getRecordNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                                } else {
+                                    return FileUtils.extractPdfPages(url, item.getRecordPdfUrl()) + "@@@" + item.getRecordNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                                }
+                            }
+                        }
+                    }
+                    if (StringUtil.hasText(item.getRecordPdfUrl())) {
+                        return item.getRecordPdfUrl() + "@@@" + item.getRecordNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                    } else if (flag) {
+                        return FileUtils.extractPdfPages(url, strArr) + "@@@" + item.getRecordNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                    }
+                    return "";
+                }).filter(StringUtil::hasText).collect(Collectors.toList());
+            } else if (type == 4) {
+                // 委托单下载
+                Set<Long> entrustIds = recordList.stream().filter(record -> record.getEntrustId() != null && record.getEntrustId() > 0).map(TrialSelfInspectionRecord::getEntrustId).collect(Collectors.toSet());
+                if (entrustIds.isEmpty()) {
+                    throw new ServiceException("未找到委托单记录");
+                }
+                List<EntrustInfo> entrustInfoList = entrustInfoService.listByIds(entrustIds);
+                pdfList = entrustInfoList.stream()
+                        .map(item -> {
+                            String url = "";
+                            if (item.getEntrustEPdf() != null && StringUtil.hasText(item.getEntrustEPdf())) {
+                                url = item.getEntrustEPdf();
+                            } else if (item.getEntrustPdf() != null && StringUtil.hasText(item.getEntrustPdf())) {
+                                url = item.getEntrustPdf();
+                            } else {
+                                return  "";
+                            }
+                            return url + "@@@" + item.getEntrustNo().replaceAll("\\\\", "_").replaceAll("/", "_");
+                        }).filter(StringUtil::hasText).collect(Collectors.toList());
+            }
+            if (pdfList == null || pdfList.isEmpty()) {
+                if (type == 1) {
+                    throw new ServiceException("未找到试验自检记录的pdf");
+                } else if (type == 2) {
+                    throw new ServiceException("未找到报告表pdf");
+                } else if (type == 3) {
+                    throw new ServiceException("未找到记录表pdf");
+                } else if (type == 4) {
+                    throw new ServiceException("未找到委托单pdf");
+                } else {
+                    throw new ServiceException("未找到pdf");
+                }
+            }
+            FileUtils.batchDownloadFileToZip(pdfList, response);
+        } catch (Exception e)  {
+            e.printStackTrace();
+        } finally {
+            if (!deletedFileUrls.isEmpty()) {
+                newIOSSClient.removeFiles(deletedFileUrls);
+            }
+        }
+    }
+
 }

+ 194 - 3
blade-service/blade-business/src/main/java/org/springblade/business/utils/FileUtils.java

@@ -10,6 +10,8 @@ import com.itextpdf.text.pdf.PdfCopy;
 import com.itextpdf.text.pdf.PdfReader;
 import net.coobird.thumbnailator.Thumbnails;
 import org.apache.commons.lang.StringUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.text.PDFTextStripper;
 import org.apache.poi.ss.usermodel.ClientAnchor;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.util.CellRangeAddress;
@@ -91,9 +93,12 @@ public class FileUtils {
                             fileName = array[1];
                             symbol = url.substring(url.lastIndexOf("."));
                         }
-
-                        //获取文件流
-                        inputStream = CommonUtil.getOSSInputStream(url);
+                        if (!url.contains("http://") && !url.contains("https://") && Files.exists(Paths.get(url))) {
+                            inputStream = Files.newInputStream(Paths.get(url));
+                        } else {
+                            //获取文件流
+                            inputStream = CommonUtil.getOSSInputStream(url);
+                        }
                         //转换
                         byte[] bytes = CommonUtil.InputStreamToBytes(inputStream);
 
@@ -740,4 +745,190 @@ public class FileUtils {
             throw new ServiceException("IO错误");
         }
     }
+
+
+    /**
+     * 从电签pdf中提取指定未电签pdf的页面,并生成新的pdf
+     */
+    public static String extractPdfPages(String eVisaPdfUrl, String pdfUrl){
+        if (eVisaPdfUrl == null || eVisaPdfUrl.trim().isEmpty() || eVisaPdfUrl.equals(pdfUrl)) {
+            return pdfUrl;
+        }
+        try (
+                InputStream eVisaIs = CommonUtil.getOSSInputStream(eVisaPdfUrl);
+                InputStream pdfIs = CommonUtil.getOSSInputStream(pdfUrl);
+                // 使用pdfbox读取电签pdf
+                PDDocument eVisaDocument = PDDocument.load(eVisaIs);
+                PDDocument pdfDocument = PDDocument.load(pdfIs);
+                PDDocument newDocument = new PDDocument();
+        ) {
+            int page = eVisaDocument.getNumberOfPages();
+            int page1 = pdfDocument.getNumberOfPages();
+            int j = 0;
+            for (int i = 0; i < page; i++) {
+                String eVisaText = getPdfContent(eVisaDocument, i + 1, i + 1);
+                if (j >= page1) {
+                    break;
+                }
+                String pdfText = getPdfContent(pdfDocument, j + 1, j + 1);
+                if (!StringUtils.isBlank(eVisaText) && calculateDiffRatio(eVisaText,pdfText) < 0.10) {
+                    newDocument.addPage(eVisaDocument.getPage(i));
+                    j++;
+                }
+            }
+            // 判断新的pdf是否为空,不为空则保存新的pdf
+            if (newDocument.getNumberOfPages() > 0) {
+                // 获取临时文件目录
+                String tempDir = System.getProperty("java.io.tmpdir");
+                String tempFile = tempDir + File.separator + "new_" + System.currentTimeMillis() + ".pdf";
+                File file = new File(tempFile);
+                try {
+                    newDocument.save(file);
+                    return tempFile;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return pdfUrl;
+    }
+
+    public static String extractPdfPages(String eVisaPdfUrl, String[] strList){
+        if (eVisaPdfUrl == null || eVisaPdfUrl.trim().isEmpty()) {
+            return eVisaPdfUrl;
+        }
+        try (
+                InputStream eVisaIs = CommonUtil.getOSSInputStream(eVisaPdfUrl);
+                // 使用pdfbox读取电签pdf
+                PDDocument eVisaDocument = PDDocument.load(eVisaIs);
+                PDDocument newDocument = new PDDocument();
+        ) {
+            int page = eVisaDocument.getNumberOfPages();
+            for (int i = 0; i < page; i++) {
+                String eVisaText = getPdfContent(eVisaDocument, i + 1, i + 1);
+                if (!StringUtils.isBlank(eVisaText)) {
+                    boolean flag = true;
+                    for (String s : strList) {
+                        flag = eVisaText.contains(s);
+                        if (!flag) {
+                            break;
+                        }
+                    }
+                    if (flag) {
+                        newDocument.addPage(eVisaDocument.getPage(i));
+                    }
+                }
+            }
+            // 判断新的pdf是否为空,不为空则保存新的pdf
+            if (newDocument.getNumberOfPages() > 0) {
+                // 获取临时文件目录
+                String tempDir = System.getProperty("java.io.tmpdir");
+                String tempFile = tempDir + File.separator + "new_" + System.currentTimeMillis() + ".pdf";
+                File file = new File(tempFile);
+                try {
+                    newDocument.save(file);
+                    return tempFile;
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return eVisaPdfUrl;
+    }
+
+    /**
+     * 获取pdf 指定页面的内容
+     */
+    private static String getPdfContent(PDDocument doc,  int startPage, int endPage) {
+        try {
+            PDFTextStripper stripper = new PDFTextStripper();
+            stripper.setStartPage(startPage);
+            stripper.setEndPage(endPage);
+            return stripper.getText(doc);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "";
+        }
+    }
+    /**
+     * 计算两个文本之间的差异比例
+     * @param original 原始文本
+     * @param modified 修改后的文本
+     * @return 差异比例 (0.0 - 1.0),0表示完全相同,1表示完全不同
+     */
+    public static double calculateDiffRatio(String original, String modified) {
+        if (original == null && modified == null) {
+            return 0.0;
+        }
+
+        if (original == null || modified == null) {
+            return 1.0;
+        }
+
+        if (original.equals(modified)) {
+            return 0.0;
+        }
+
+        // 按行分割文本
+        String[] originalLines = original.split("\n", -1);
+        String[] modifiedLines = modified.split("\n", -1);
+
+        // 使用动态规划计算编辑距离
+        int editDistance = computeEditDistance(originalLines, modifiedLines);
+
+        // 计算最大可能的编辑距离
+        int maxLength = Math.max(originalLines.length, modifiedLines.length);
+
+        if (maxLength == 0) {
+            return 0.0;
+        }
+
+        // 返回差异比例
+        return (double) editDistance / maxLength;
+    }
+    /**
+     * 使用动态规划算法计算两个字符串数组之间的编辑距离(Levenshtein距离)
+     * @param original 原始字符串数组
+     * @param modified 修改后的字符串数组
+     * @return 编辑距离
+     */
+    private static int computeEditDistance(String[] original, String[] modified) {
+        int m = original.length;
+        int n = modified.length;
+
+        // 创建DP表
+        int[][] dp = new int[m + 1][n + 1];
+
+        // 初始化边界条件
+        for (int i = 0; i <= m; i++) {
+            dp[i][0] = i;
+        }
+
+        for (int j = 0; j <= n; j++) {
+            dp[0][j] = j;
+        }
+
+        // 填充DP表
+        for (int i = 1; i <= m; i++) {
+            for (int j = 1; j <= n; j++) {
+                if (original[i - 1].equals(modified[j - 1])) {
+                    dp[i][j] = dp[i - 1][j - 1]; // 字符相同,无需操作
+                } else {
+                    // 取三种操作的最小值+1
+                    dp[i][j] = 1 + Math.min(
+                            Math.min(dp[i - 1][j],     // 删除
+                                    dp[i][j - 1]),    // 插入
+                            dp[i - 1][j - 1]           // 替换
+                    );
+                }
+            }
+        }
+
+        return dp[m][n];
+    }
+
 }

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

@@ -59,13 +59,16 @@ public class Archive2Controller {
     @Resource(name = "archivePoolExecutor")
     private ThreadPoolExecutor archExecutor;
 
-    @Scheduled(cron = "0/30 * * * * ?")
+    @Scheduled(cron = "0/10 * * * * ?")
     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 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));
+      //  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=2 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";
+       // String sql = "SELECT distinct b.id,b.archive_id as archiveId ,'/Users/hongchuangyanfa/Downloads/ab13344e4943222efffa89f6db3604a6.pdf'  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.id=1989148760491425792 LIMIT 20";
+       //String sql = "SELECT distinct b.id,b.archive_id as archiveId ,'/Users/hongchuangyanfa/Downloads/1a39d7b4610904104cc65739cddf24c6.pdf'  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.id=1989154852734763008 LIMIT 20";
+       String sql = "SELECT distinct b.id,b.archive_id as archiveId ,'/Users/hongchuangyanfa/Downloads/293ec019215ba23e78ff11ba1f3e6361.pdf'  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.id=1989148775205044224 LIMIT 20";
+       List<TaskArchiveSplitVO> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(TaskArchiveSplitVO.class));
 
         if (query != null && query.size() >= 1) {
             for (TaskArchiveSplitVO dataInfo : query) {
@@ -126,6 +129,7 @@ public class Archive2Controller {
             }
 
             String filePath = startPage + "--" + (listPdf.size() + 1);
+            System.out.println("filePath="+filePath);
             //
             ArchivesSplitInfoVO data =new ArchivesSplitInfoVO();
             data.setId(taskSign.getId());
@@ -147,6 +151,7 @@ public class Archive2Controller {
             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]);
@@ -155,6 +160,7 @@ public class Archive2Controller {
             //将imagePath 的数据转成一个可解析的html
             String htmlUrl = pngToHtml(firstPage, archiveId, taskSign.getFirstFileUrl());
             System.out.println("分解002=" + htmlUrl);
+           int pdfPage = FileUtils.getPdfNum(fileUlr);
 
             if (htmlUrl.indexOf("_001.html") >= 0 && htmlUrl.indexOf("archiveSplit") >= 0) {
                 String htmlString = IoUtil.readToString(new FileInputStream(htmlUrl));
@@ -164,7 +170,9 @@ public class Archive2Controller {
                 //由于解析已经成功,可能数据已经分解过,需要删除
                 if (trs != null && trs.size() >= 1) {
                     String sql = "delete from u_archive_file where id<>'" + taskSign.getId() + "' and archive_id='" + archiveId + "'";
+                    String sql2 = "update  u_archive_file set file_name='整份pdf'  where id='" + taskSign.getId() + "' and archive_id='" + archiveId + "'";
                     jdbcTemplate.execute(sql);
+                    jdbcTemplate.execute(sql2);
                 }
 
                 for (int i = 0; i <= trs.size() - 1; i++) {
@@ -179,7 +187,11 @@ public class Archive2Controller {
                     int startYm = 0;
                     int endYm = 0;
                     if (i < trs.size() - 1) {
-                        startYm = Func.toInt(ym);
+                        if(i==1){
+                            startYm=1;
+                        }else{
+                            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();
@@ -210,6 +222,7 @@ public class Archive2Controller {
                     bkb = endYm;
                 }
             } else {
+                RedisTemplate.delete("splithtml-" + archiveId);
                 return;
             }
 
@@ -223,30 +236,36 @@ public class Archive2Controller {
             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, "");
+            // 卷内备考表
+            for (int i = 0; i < 3; i++) {
+                int pageNo = pdfPage -i;
+                // 背脊表
+                String bjbUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_beiji_001.pdf";
+                String bjbUrlPng = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + archiveId + "_beiji_001.png";
+
+                int pdfByPage = getPdfByPage(pageNo, pageNo, fileUlr, bjbUrl);
+                if (pdfByPage == 0) {
+                    File bgImgFile = new File(bjbUrlPng);
+                    if (!bgImgFile.exists()) {
+                        int dataNum = savePdfAsImage(1, bjbUrl, bjbUrlPng);
+                    }
+                    //卷内备考表
+                    String state = OcrTitle(bjbUrlPng, "2");
+                    if (state.equals("1")) {
+                        saveDataToMysql(bjbUrl, "卷内备考表", taskSign.getId(), 1, 100, dutyUser, "");
+                    }
+                    String state2 = OcrTitle(bjbUrlPng, "3");
+                    if (state2.equals("1")) {
+                        saveDataToMysql(bjbUrl, "背脊表", taskSign.getId(), 1, 101, dutyUser, "");
+                    }
+                    bgImgFile.delete();
+                }else{
+                    File bgImgFile = new File(bjbUrl);
+                    if (!bgImgFile.exists()) {
+                        bgImgFile.delete();
+                    }
                 }
-                bgImgFile.delete();
             }
             // 修改任务状态
             String updateSql = "update u_archives_split_info set status=3 where id=" + taskSign.getId();
@@ -286,7 +305,7 @@ public class Archive2Controller {
                     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文件路径", "");
                 }
@@ -305,12 +324,13 @@ public class Archive2Controller {
     }
 
     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);
+          //  ProcessBuilder pb = new ProcessBuilder("conda", "run", "-n", "paddle_env","python3", pythonScript, fileUrl, type);
+
             Process process = pb.start();
 
             // 读取Python脚本输出
@@ -326,7 +346,7 @@ public class Archive2Controller {
             // 等待进程结束
             int exitCode = process.waitFor();
             if (exitCode == 0) {
-                return lasHhtmlUrl;
+                return "0";
             } else {
                 return "1";
             }
@@ -339,7 +359,8 @@ public class Archive2Controller {
 
     public static int getPdfByPage(int startPage, int endPage, String filePath, String savePath) {
         try {
-            InputStream inputStreamByUrl = CommonUtil.getOSSInputStream(filePath);
+           // InputStream inputStreamByUrl = CommonUtil.getOSSInputStream(filePath);
+            InputStream inputStreamByUrl = new FileInputStream( new File(filePath));// CommonUtil.getOSSInputStream(filePath);
             // 加载PDF文件
             PDDocument document = PDDocument.load(inputStreamByUrl);
             // 创建新文档
@@ -366,7 +387,10 @@ public class Archive2Controller {
     }
 
     public static int savePdfAsImage(int pageNum, String filePath, String outputPath) {
-        try (InputStream inputStream = FileUtils.getInputStreamByUrl(filePath);
+        try (
+
+              //  InputStream inputStream = FileUtils.getInputStreamByUrl(filePath);
+             InputStream inputStream = new FileInputStream(new File(filePath));
              PDDocument document = PDDocument.load(inputStream)) {
 
             // 验证页码范围
@@ -431,16 +455,16 @@ public class Archive2Controller {
         return 200;
     }
 
-/*    public static void main(String[] args) {
+    public static void main11(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);
+        String fileUrl = "/Users/hongchuangyanfa/Downloads/e4a7bbe7ae34444206cb989364314f12.pdf";
+        String firstUrl = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + 123 + "first__" + 2 + "__.pdf";
+        int pdfByPage = getPdfByPage(2, 2, fileUrl, firstUrl);
         File file = new File(firstUrl);
 
         // 保存第一页为300DPI图片
-        String imagePath = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + 123 + "first__" + 1 + "__.png";
+        String imagePath = FileUtils.getSysLocalFileUrl() + "archiveSplit/" + 123 + "first__" + 2 + "__.png";
         File imgfile = new File(imagePath);
-        int dataNum = savePdfAsImage(1, fileUrl, imagePath);
-    }*/
+        int dataNum = savePdfAsImage(1, firstUrl, imagePath);
+    }
 }

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

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

@@ -59,7 +59,7 @@ public class EVController {
     @Resource(name = "taskExecutor1")
     private ThreadPoolExecutor executor;
 
-    @Scheduled(cron = "0/10 * * * * ?")
+    //@Scheduled(cron = "0/10 * * * * ?")
     public void SignInfo() {
         //执行代码
 

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

@@ -96,8 +96,12 @@ public class EVDataServiceImpl implements EVDataService {
             List<String> eVisaConfigList = PDFUtils.getPdfSignIds(fileUrl, taskApp);
             if (eVisaConfigList == null || eVisaConfigList.size() == 0) {
                 //没有电签配置,默认当前任务为不签字审批,返回成功
-                taskApp.setSigState(2);
-                taskApp.setSignSmg("pdf未获取到关键字Id");
+                if(taskApp.getSigState() == 10) {
+                    taskApp.setSigState(2);
+                } else {
+                    taskApp.setSigState(2);
+                    taskApp.setSignSmg("pdf未获取到关键字Id");
+                }
                 SignBackPdfInfo(taskApp);
                 return;
             }
@@ -232,7 +236,7 @@ public class EVDataServiceImpl implements EVDataService {
                 if (taskApp.getApprovalType() == 1 || taskApp.getApprovalType() == 9) { //
                     //
                     String sys_isonline = ParamCache.getValue(CommonConstant.SYS_ISONLINE);
-                    String pdfPage = "0";
+                    int pdfPage = 0;
                     Long pdfSize = 0L;
                     String nodePdfUrl = "";
 
@@ -338,7 +342,7 @@ public class EVDataServiceImpl implements EVDataService {
                     //电签成功后需要将check_status改为1,进行电签检测
                     String updateCheckStatus="update  u_information_query set chek_status=1 where id='" + taskApp.getFormDataId() + "' and chek_status!=0 ";
                     jdbcTemplate.execute(updateCheckStatus);
-                    updateSql = "update u_information_query set pdf_trial_url_position='" + pdfTrialUrlPosition + "',business_time='" + taskApp.getPdfDate() + "',node_pdf_url='" + nodePdfUrl + "',e_visa_pdf_page=" + pdfPage + ",e_visa_pdf_size=" + pdfSize + ",e_visa_pdf_url='" + taskApp.getLastFilePdfUrl() + "',status='" + taskApp.getSigType() + "',update_time=SYSDATE() where id='" + taskApp.getFormDataId() + "' ";
+                    updateSql = "update u_information_query set pdf_trial_url_position='" + pdfTrialUrlPosition + "',business_time='" + taskApp.getPdfDate() + "',node_pdf_url='" + nodePdfUrl + "',e_visa_pdf_page='" + pdfPage + "',e_visa_pdf_size=" + pdfSize + ",e_visa_pdf_url='" + taskApp.getLastFilePdfUrl() + "',status='" + taskApp.getSigType() + "',update_time=SYSDATE() where id='" + taskApp.getFormDataId() + "' ";
                     //修改 计量 需要引用的 附件信息
                     String updataFile = "update s_attachment_form set file_url='"+nodePdfUrl+"' ,file_pdf_url='"+nodePdfUrl+"' where select_id='"+taskApp.getFormDataId()+"' ";
                     jdbcTemplate.execute(updataFile);
@@ -407,7 +411,12 @@ public class EVDataServiceImpl implements EVDataService {
                     }
                     this.jdbcTemplate.execute("delete from u_task_batch where id in(" + taskApp.getId()+")");
                 }else{
-                    this.jdbcTemplate.execute("update u_task_parallel set exe_count=(exe_count+1), e_visa_status=99,e_visa_content='" + taskApp.getSignSmg() + "' ,update_time=SYSDATE() where parallel_process_instance_id in (" + taskApp.getParallelProcessInstanceId() + ")");
+                    if (taskApp.getSigType() == 1) {
+                        this.jdbcTemplate.execute("update u_task_parallel set exe_count=(exe_count+1), e_visa_status=99,e_visa_content='" + taskApp.getSignSmg() + "' ,update_time=SYSDATE() where parallel_process_instance_id in (" + taskApp.getParallelProcessInstanceId() + ")");
+                    } else {
+                        // 签章失败
+                        this.jdbcTemplate.execute("update u_task_parallel set exe_count=(exe_count+1), meter_task_repeal_desc='" + taskApp.getSignSmg() + "' ,update_time=SYSDATE() where parallel_process_instance_id in (" + taskApp.getParallelProcessInstanceId() + ")");
+                    }
                     this.jdbcTemplate.execute("update u_task set status=1 ,update_time=SYSDATE() where id='" + taskApp.getTaskId() + "'");
                     if (totalCount >= 3) {
                         this.jdbcTemplate.execute("delete from u_task_batch where id in(" + taskApp.getId()+")");
@@ -574,7 +583,7 @@ public class EVDataServiceImpl implements EVDataService {
                 return;
             }
 
-            List<Map<String, Object>> projectList = jdbcTemplate.queryForList("select * from u_project_info where  id = " + taskApp.getProjectId());
+            List<Map<String, Object>> projectList = jdbcTemplate.queryForList("select * from m_project_info where  id = " + taskApp.getProjectId());
             if (projectList == null || Func.isEmpty(projectList)) {
                 taskApp.setSigState(2);
                 taskApp.setSignSmg("未获取项目信息");
@@ -582,7 +591,7 @@ public class EVDataServiceImpl implements EVDataService {
                 return;
             } else {
                 Map<String, Object> projectInfo = projectList.get(0);
-                String remarkType = projectInfo.get("remarkType")+"";//.getRemarkType();
+                String remarkType = projectInfo.get("remark_type")+"";//.getRemarkType();
                 if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType.equals("2")) {
                     taskApp.setRemarkType("2");
                 }else if (remarkType != null && Func.isNotEmpty(remarkType) && remarkType .equals("3")) {

+ 4 - 4
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/FileUtils.java

@@ -103,10 +103,10 @@ public class FileUtils {
     }
 
 
-    public static String getPdfNum(String url) {
+    public static Integer getPdfNum(String url) {
         try {
             if (url.isEmpty() || url.equals("")) {
-                return "0";
+                return 0;
             }
             File file1 = new File(url);
             InputStream pdfInputStream;
@@ -119,11 +119,11 @@ public class FileUtils {
             //获取PDF文件
             PDDocument document = PDDocument.load(pdfInputStream);
             int page = document.getPages().getCount();
-            return page + "";
+            return page;
         } catch (Exception e) {
             e.printStackTrace();
         }
-        return "0";
+        return 0;
     }
 
 }

+ 2 - 1
blade-service/blade-e-visa/src/main/java/org/springblade/evisa/utils/PDFUtils.java

@@ -116,7 +116,8 @@ public class PDFUtils {
             return unique;
         }catch (Exception e){
             e.printStackTrace();
-            System.out.println("pdf大小为0");
+            taskApp.setSigState(10);
+            taskApp.setSignSmg("解析pdf异常");
             return eVisaConfigList;
         }
     }

+ 1 - 1
blade-service/blade-land/src/main/java/org/springblade/land/mapper/CompensationInfoMapper.xml

@@ -27,7 +27,7 @@
     <select id="getTables" resultType="org.springblade.manager.entity.WbsTreePrivate">
         SELECT * FROM m_wbs_tree_private
         WHERE project_id = #{projectId} and parent_id = (select id from m_wbs_tree_private
-                where project_id = #{projectId} and wbs_type = 5 and parent_id != 0 and `type` = 1 and node_type = #{nodeType})
+                where project_id = #{projectId} and wbs_type = 5 and parent_id != 0 and `type` = 1 and node_type = #{})
         order by sort
     </select>
     <select id="getWbsPrivateTable" resultType="org.springblade.manager.entity.WbsTreePrivate">

+ 10 - 14
blade-service/blade-manager/src/main/java/com/mixsmart/utils/CustomFunction.java

@@ -1690,11 +1690,16 @@ public class CustomFunction {
             List<String> result = new ArrayList<>();
             param = param.trim().replaceAll("(?i:c)", "");
             List<String> list = Arrays.asList(param.split("[^.\\d]"));
-            List<Integer> index = list.stream().map(Integer::parseInt).collect(Collectors.toList());
+            List<Integer> index = list.stream().map(Integer::parseInt).sorted().collect(Collectors.toList());
             Integer type = RandomNumberHolder.getRandomTemplateType();
             if(type==null||type==1){
                 for (Integer i : index) {
                     if (i < nodes.size()) {
+                        //获取当前节点的名称
+                        String title = nodes.get(i);
+                        if(title == null || result.stream().anyMatch(f -> f.contains(title))){
+                            continue;
+                        }
                         result.add(nodes.get(i));
                     }
                 }
@@ -1708,20 +1713,11 @@ public class CustomFunction {
                         (existing, replacement) -> replacement // 如果键重复,保留后者
                     ));
                 for (Integer i : index) {
-                    if(i==0){
-                        if(map.containsKey(1)){
-                            result.add(map.get(1));
-                        }
-                    }
-                    else if(i==1){
-                        if(map.containsKey(18)){
-                            result.add(map.get(18));
-                        }
-                    }else {
-                        if(map.containsKey(i)){
-                            result.add(map.get(i));
-                        }
+                    String title = map.get(i == 0 ? 1 : i == 1 && map.containsKey(18) ? 18 : i);
+                    if(title == null || result.stream().anyMatch(f -> f.contains(title))){
+                        continue;
                     }
+                    result.add(title);
                 }
                 return String.join("", result);
             }

+ 13 - 0
blade-service/blade-manager/src/main/java/com/mixsmart/utils/FormulaUtils.java

@@ -295,6 +295,19 @@ public class FormulaUtils {
             fd.getValues().forEach(t->t.setValue(null));
             if(data instanceof List){
                 List<Object> values = (List<Object>) data;
+                //中间元素根据数据动态扩容
+                ElementData elementData = fd.getValues().get(0);
+                //中间元素初始坐标
+                if(elementData.getX() == 0 && elementData.getY() == 0){
+                    for (int i = 1; i < values.size(); i++) {
+                        ElementData elementData1 = new ElementData();
+                        elementData1.setIndex(0);
+                        elementData1.setX(-i);
+                        elementData1.setY(0);
+                        elementData1.setGroupId(0);
+                        fd.getValues().add(elementData1);
+                    }
+                }
                 if(!retainEmpty){
                     /*不包含空白内容*/
                     values=values.stream().filter(StringUtils::isNotEmpty).collect(Collectors.toList());

+ 141 - 19
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;
@@ -106,6 +109,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import static java.util.stream.Collectors.groupingBy;
+
 
 /**
  * 清表基础数据表 控制器
@@ -1042,6 +1047,50 @@ public class ExcelTabController extends BladeController {
 
         // 远程搜索配置
         Document doc = Jsoup.parse(htmlString);
+
+        //获取所有input标签
+        String[] tagNames = {"el-input", "el-date-picker", "el-time-picker", "hc-form-select-search",
+                "hc-table-form-upload", "hc-form-checkbox-group", "el-radio-group", "el-select"};
+        Elements inputs = new Elements();
+        for (String tagName : tagNames) {
+            inputs.addAll(doc.select(tagName));
+        }
+
+        //获取元素表数据
+        List<WbsFormElement> wbsFormElements = wbsFormElementService.getBaseMapper().selectList(Wrappers.<WbsFormElement>lambdaQuery()
+                .eq(WbsFormElement::getFId, wbsTreePrivate.getInitTableId())
+                .eq(WbsFormElement::getIsDeleted, 0)
+        );
+        List<String> keys = wbsFormElements.stream().map(WbsFormElement::getEKey).collect(Collectors.toList());
+
+        //判断标签是否存在id属性
+        inputs.forEach(input -> {
+            String id = input.attr("id");
+            if(com.mixsmart.utils.StringUtils.isEmpty(id)){
+                input.attr("htmlElementError", "1");
+            } else {
+                /**
+                 * 判断当前元素是否符合格式
+                 * 1、是否存在key_
+                 * 2、是否存在__
+                 * 3、key_后面是否为数字
+                 * 4、__后面是否为坐标
+                 * 5、key是否在元素库中存在
+                 */
+                if(!id.contains("key_")
+                        || !id.contains("__")
+                        || !com.mixsmart.utils.StringUtils.isNumber(id.split("__")[0].replace("key_",""))
+                        || !id.split("__")[1].contains("_")
+                        || !com.mixsmart.utils.StringUtils.isNumber(id.split("__")[1].split("_")[0])
+                        || !com.mixsmart.utils.StringUtils.isNumber(id.split("__")[1].split("_")[1])
+                        || !keys.contains(id.split("__")[0])
+                ){
+                    input.attr("htmlElementError", "1");
+                }
+            }
+        });
+
+
         Element table = doc.select("table").first();
         Elements col = doc.select("Col");
         doc.select("Col").remove();
@@ -2010,6 +2059,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 +2112,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 = "隐藏表单")
@@ -4855,6 +4942,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);
 
@@ -5163,32 +5268,39 @@ public class ExcelTabController extends BladeController {
         return R.data(query);
     }
 
+    @Scheduled(cron = "0 0 23 * * ?")
     @GetMapping("/checkAllNodeDate")
-    @ApiOperationSupport(order = 43)
-    @ApiOperation(value = "检查所有表单是否填写完毕日期")
-    public R checkAllNodeDate(Long projectId,Long contractId) throws Exception {
-        //查询出当前项目或合同段下所有的表单
-        List<WbsTreeContract>list= wbsTreeContractMapper.selectListForcheckAllNodeDate(projectId,contractId);
-        //将表单通过pid分组
-        Map<Long, List<WbsTreeContract>> map = list.stream().collect(Collectors.groupingBy(WbsTreeContract::getPId));
-        int i=map.size();
-        System.out.println("当前项目合同段有:"+i+"个节点");
+    public void checkAllNodeDate() throws Exception {
+        String sys_isonline = ParamCache.getValue(CommonConstant.SYS_ISONLINE);
+        if (sys_isonline.equals("20")||!SystemUtils.isLinux()) {
+            return;
+        }
+        String deleteSql="delete from m_is_data_complete";
+        jdbcTemplate.execute(deleteSql);
+        String insertSql="INSERT INTO m_is_data_complete (p_key_id) SELECT DISTINCT p_key_id FROM m_wbs_tree_contract WHERE date_is_complete = 2";
+        jdbcTemplate.execute(insertSql);
+        String updateSql="update m_wbs_tree_contract set date_is_complete=1 where is_deleted=0 and type=1";
+        jdbcTemplate.update(updateSql);
+        String sql="select * from m_wbs_tree_contract where is_deleted=0 and type=2 and date_is_complete=2 AND html_url is not null and p_id is not null and is_buss_show!=2";
+        List<WbsTreeContract> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        Map<Long, List<WbsTreeContract>> map = list.stream().collect(groupingBy(WbsTreeContract::getPId));
+        int count=map.size();
+        int i=1;
+        System.out.println("开始检查"+count+"个节点");
         for (Map.Entry<Long, List<WbsTreeContract>> entry : map.entrySet()) {
+            System.out.println("检查节点:"+entry.getKey()+"已检查:"+i++);
             Long pId = entry.getKey();
             WbsTreeContract parent = wbsTreeContractMapper.selectOne(new LambdaQueryWrapper<>(WbsTreeContract.class).eq(WbsTreeContract::getPKeyId, pId));
             if(parent==null){
-                i--;
-                System.out.println("剩余:"+i+"个节点未检查");
                 continue;
             }
-            String informationSql="select * from u_information_query where wbs_id="+parent.getPKeyId()+" and contract_id="+contractId+" and is_deleted=0";
+            String informationSql="select * from u_information_query where wbs_id="+parent.getPKeyId()+" and contract_id="+parent.getContractId()+" and is_deleted=0";
             List<InformationQuery> query = jdbcTemplate.query(informationSql, new BeanPropertyRowMapper<>(InformationQuery.class));
-            if(query.isEmpty()||query.get(0).getStatus()!=0){
-                i--;
-                System.out.println("剩余:"+i+"个节点未检查");
+            if(query.isEmpty()){
                 continue;
             }
             List<WbsTreeContract> wbsTreeContractList = entry.getValue();
+            boolean flag=true;
             for (WbsTreeContract contract : wbsTreeContractList) {
                 Map<String, Object> dataInfo = excelTabService.getBussDataInfo(contract.getPKeyId(), 0, true);
                 if(dataInfo!=null){
@@ -5207,7 +5319,10 @@ public class ExcelTabController extends BladeController {
                                 }
                             }
                             if(!dateFlag){
+                                flag=false;
                                 wbsTreeContractMapper.update(null,new LambdaUpdateWrapper<WbsTreeContract>().eq(WbsTreeContract::getPKeyId,contract.getPKeyId()).set(WbsTreeContract::getDateIsComplete,2));
+                            }else {
+                                wbsTreeContractMapper.update(null,new LambdaUpdateWrapper<WbsTreeContract>().eq(WbsTreeContract::getPKeyId,contract.getPKeyId()).set(WbsTreeContract::getDateIsComplete,1));
                             }
                         }
                     }catch (Exception e){
@@ -5215,11 +5330,18 @@ public class ExcelTabController extends BladeController {
                     }
                 }
             }
-            wbsTreeContractService.checkNodeAllDate(parent);
-            i--;
-            System.out.println("剩余:"+i+"个节点未检查");
+            if(!flag){
+                List<Long> longList = new ArrayList<>(
+                        parent.getAncestorsPId() != null ? Func.toLongList(parent.getAncestorsPId()) : new ArrayList<>()
+                );
+                if (parent.getPKeyId() != null) {
+                    longList.add(parent.getPKeyId());
+                }
+                if(!longList.isEmpty()){
+                    wbsTreeContractMapper.update(null,new LambdaUpdateWrapper<WbsTreeContract>().in(WbsTreeContract::getPKeyId,longList).set(WbsTreeContract::getDateIsComplete,2));
+                }
+            }
         }
         System.out.println("检查完毕");
-        return R.status(true);
     }
 }

+ 773 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/InformationImportRecordController.java

@@ -0,0 +1,773 @@
+package org.springblade.manager.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import com.spire.xls.CellRange;
+import io.swagger.annotations.*;
+import lombok.AllArgsConstructor;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.poi.ss.usermodel.Comment;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.ss.usermodel.Shape;
+import org.apache.poi.ss.util.CellAddress;
+import org.apache.poi.xssf.usermodel.XSSFComment;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.jetbrains.annotations.NotNull;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springblade.business.entity.InformationQuery;
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.common.utils.SystemUtils;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.secure.utils.AuthUtil;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.IoUtil;
+import org.springblade.core.tool.utils.ResourceUtil;
+import org.springblade.core.tool.utils.StringUtil;
+import org.springblade.manager.dto.ServiceUserDto;
+import org.springblade.manager.entity.*;
+import org.springblade.manager.feign.ExcelTabClient;
+import org.springblade.manager.feign.ExcelTabClientImpl;
+import org.springblade.manager.service.IExcelTabService;
+import org.springblade.manager.service.IWbsTreeContractService;
+import org.springblade.manager.service.InformationImportRecordService;
+import org.springblade.manager.service.impl.WbsTreeContractServiceImpl;
+import org.springblade.manager.util.DataStructureFormatUtils;
+import org.springblade.manager.utils.DuplicateSheetRecognizer;
+import org.springblade.manager.utils.FileUtils;
+import org.springblade.manager.vo.InformationImportRecordVO;
+import org.springblade.system.cache.ParamCache;
+import org.springblade.system.user.feign.IUserClient;
+import org.springframework.beans.BeanUtils;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static java.util.stream.Collectors.toMap;
+
+/**
+ * 资料导入及记录查询控制器
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/informationImportRecord")
+@Api(tags = "资料导入及记录查询接口")
+public class InformationImportRecordController extends BladeController {
+
+    private final IExcelTabService excelTabService;
+
+    private final IWbsTreeContractService wbsTreeContractService;
+    private final WbsTreeContractServiceImpl wbsTreeContractServiceImpl;
+    private final ExcelTabClient excelTabClient;
+    private final InformationImportRecordService informationImportRecordService;
+    private final IUserClient userClient;
+    private final JdbcTemplate jdbcTemplate;
+
+    private static final Logger logger = LoggerFactory.getLogger(InformationImportRecordController.class);
+    private static final java.util.concurrent.atomic.AtomicBoolean RUNNING = new java.util.concurrent.atomic.AtomicBoolean(false);
+    private static final java.util.concurrent.ExecutorService IMPORT_EXECUTOR = new java.util.concurrent.ThreadPoolExecutor(
+            Math.max(2, Runtime.getRuntime().availableProcessors()),
+            Math.max(4, Runtime.getRuntime().availableProcessors() * 2),
+            60L,
+            java.util.concurrent.TimeUnit.SECONDS,
+            new java.util.concurrent.LinkedBlockingQueue<>(200),
+            new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy()
+    );
+
+
+    @GetMapping("/page")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "获取资料导入记录")
+    public R<IPage<InformationImportRecord>> page(InformationImportRecordVO record, Query query) {
+        LambdaQueryWrapper<InformationImportRecord> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(InformationImportRecord::getContractId, record.getContractId());
+        if (StringUtil.hasText(record.getFileName())) {
+            wrapper.like(InformationImportRecord::getFileName, record.getFileName());
+        }
+        if (record.getStatus() != null && (record.getStatus() >= 0 && record.getStatus() <= 3)) {
+            wrapper.eq(InformationImportRecord::getStatus, record.getStatus());
+        }
+        if (record.getUserId() != null && record.getUserId() > 0) {
+            wrapper.eq(InformationImportRecord::getCreateUser, record.getUserId());
+        }
+        if (record.getClassify() != null && (record.getClassify() == 1 || record.getClassify() == 2)) {
+            wrapper.eq(InformationImportRecord::getClassify, record.getClassify());
+        }
+        if (record.getStartTime() != null && record.getEndTime() != null) {
+            if (record.getStartTime().trim().matches("^\\d{4}-\\d{2}-\\d{2}$") && record.getEndTime().trim().matches("^\\d{4}-\\d{2}-\\d{2}$")) {
+                wrapper.between(InformationImportRecord::getCreateTime, record.getStartTime() + " 00:00:00", record.getEndTime() + " 23:59:59");
+            }
+        }
+        wrapper.orderByDesc(InformationImportRecord::getCreateTime);
+        IPage<InformationImportRecord> page = informationImportRecordService.page(Condition.getPage(query), wrapper);
+        List<InformationImportRecord> records = page.getRecords();
+        if (records != null && !records.isEmpty()) {
+            String userIds = records.stream().filter(item -> item.getCreateUser() != null && item.getCreateUser() > 0).map(item -> item.getCreateUser() + "").collect(Collectors.joining(","));
+            List<ServiceUserDto> userList = jdbcTemplate.query("select id as userId, name as userName from blade_user where id in (" + userIds + ")", new BeanPropertyRowMapper<>(ServiceUserDto.class));
+            Map<String, String> map = userList.stream().collect(Collectors.toMap(ServiceUserDto::getUserId, ServiceUserDto::getUserName, (k1, k2) -> k1));
+            records.forEach(item -> {
+                String username = map.get(item.getCreateUser() + "");
+                item.setCreateUserName(username);
+                item.setRemark(item.getRemark() == null ? "" : item.getRemark().split("::")[0]);
+            });
+        }
+        return R.data(page);
+    }
+
+    @GetMapping("/users")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "获取资料导入操作人列表", notes = "传入合同段id,返回 id, name")
+    public R<List<ServiceUserDto>> page(@RequestParam Long contractId) {
+        List<ServiceUserDto> query = jdbcTemplate.query("select distinct b.id as userId, b.name as userName from u_information_import_record a left join blade_user b on a.create_user = b.id where contract_id = " + contractId,
+                new BeanPropertyRowMapper<>(ServiceUserDto.class));
+        return R.data(query);
+    }
+
+    @PostMapping("/import")
+    @ApiOperationSupport(order = 2)
+    @ApiOperation(value = "客户端-导入多sheet excel到对应节点下的表单", notes = "传入节点ID、分类和多sheet excel文件")
+    public R<Object> importNodeExcel(@RequestPart List<MultipartFile> files, @RequestParam Long nodeId, @RequestParam Integer classify) throws Exception {
+        if (files.isEmpty()) {
+            return R.fail("请选择文件");
+        }
+        if (nodeId == null) {
+            return R.fail("请选择节点");
+        }
+        List<WbsTreeContract> query = jdbcTemplate.query("select * from m_wbs_tree_contract where is_deleted = 0 and p_key_id = " + nodeId, new BeanPropertyRowMapper<>(WbsTreeContract.class));
+        if (query.isEmpty()) {
+            return R.fail("请选择节点");
+        }
+        WbsTreeContract wbsTreeContract = query.get(0);
+        long contractId;
+        try {
+            contractId = Long.parseLong(wbsTreeContract.getContractId());
+        } catch (NumberFormatException e) {
+            return R.fail("未找到对应的合同段id");
+        }
+        if (classify == null || classify < 0 || classify > 2) {
+            return R.fail("请选择所属方");
+        }
+        String localPath = ParamCache.getValue(CommonConstant.SYS_LOCAL_URL);
+        String base = localPath + File.separator + "import" + File.separator;
+        {
+            // 如果文件夹不存在则创建
+            File file1 = new File(base);
+            if (!file1.exists()) {
+                boolean mkdirs = file1.mkdirs();
+                if (!mkdirs) {
+                    return R.fail("创建文件夹失败");
+                }
+            }
+        }
+        LocalDateTime now = LocalDateTime.now();
+        List<InformationImportRecord> records = new ArrayList<>();
+        for (MultipartFile file : files) {
+            // 将file保存到本地
+            Long id = SnowFlakeUtil.getId();
+            String filename = file.getOriginalFilename();
+            if (filename == null) {
+                continue;
+            }
+            InputStream is = file.getInputStream();
+            // 避免重名以及文件名过长的问题
+            String path = base + id + filename.substring(filename.lastIndexOf("."));
+
+            Files.write(Paths.get(path), IOUtils.toByteArray(is));
+            String name = getFileNameBySheet(path);
+
+            // 创建记录
+            InformationImportRecord record = new InformationImportRecord();
+            record.setId(id);
+            record.setNodeId(nodeId);
+            record.setContractId(contractId);
+            record.setClassify(classify);
+            record.setFileName(name == null ? filename.substring(0, filename.lastIndexOf(".")) : name.replace(".xlsx", "").replace(".xls", ""));
+            record.setFilePath(path);
+            record.setCreateTime(now);
+            record.setCreateUser(AuthUtil.getUserId());
+            record.setRemark("::" + filename);
+            records.add(record);
+        }
+        informationImportRecordService.saveBatch(records);
+        importInformationData();
+        return R.data(true);
+    }
+
+    private static String getFileNameBySheet(String excelPath) {
+        // 解析excel, 读取excel中的批注
+        try (InputStream is = Files.newInputStream(Paths.get(excelPath));XSSFWorkbook workbook = new XSSFWorkbook(is);) {
+            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
+                XSSFSheet sheet = workbook.getSheetAt(i);
+                Map<CellAddress, XSSFComment> map = sheet.getCellComments();
+                for (Map.Entry<CellAddress, XSSFComment> entry : map.entrySet()) {
+                    XSSFComment value = entry.getValue();
+                    if (value != null && value.getString() != null) {
+                        String string = value.getString().getString();
+                        if (string != null && string.contains("》")) {
+                            return string.replaceAll("\r\n", "");
+                        }
+                    }
+                }
+            }
+            return null;
+        } catch (Exception e) {
+            logger.error("获取文件名失败", e);
+            return null;
+        }
+    }
+
+    /**
+     * 初始化: 匹配节点,创建子节点
+     * @param id 记录ID
+     * @param node 节点
+     */
+    public void init(@NotNull Long id, @NotNull WbsTreeContract node) {
+        InformationImportRecord record = informationImportRecordService.getById(id);
+        if (record == null || record.getStatus() != 0) {
+            logger.info("记录[{}]已经初始化,已跳过", id);
+            return;
+        }
+        String name = record.getFileName();
+        String[] split = name.split("》");
+        String lastName = split[split.length - 1];
+        String newName = "";
+        String[] split1 = lastName.split("【");
+        if (split1.length  > 1) {
+            lastName = split1[0];
+            newName = split1[1].replace("】", "");
+        }
+        WbsTreeContract target = null;
+        if (split.length > 1) {
+            // 获取节点下的所有子节点
+            Long pId = node.getPKeyId();
+            int i = 0;
+            for (; i < split.length - 1; i++) {
+                // 使用 like 是因为Windows 和 excel 不支持 ‘/’ 等特殊字符,下载模板时将特殊字符转为了 '_', 而 '_' 在 MySQL like中表示一个任意字符, 所以这里使用 like
+                List<WbsTreeContract> query = jdbcTemplate.query("select * from m_wbs_tree_contract where is_deleted = 0 and p_id = " + pId + " and full_name like '" + split[i] + "'",
+                        new BeanPropertyRowMapper<>(WbsTreeContract.class));
+                if ((query.isEmpty() || query.get(0) == null)) {
+                    if (target != null) {
+                        break;
+                    }
+                    if (i == split.length - 2 ) {
+                        if (node.getFullName() != null && node.getFullName().replaceAll("[\\\\/:*?\"<>|]", "_").equals(split[i]) ||
+                            node.getNodeName() != null && node.getNodeName().replaceAll("[\\\\/:*?\"<>|]", "_").equals(split[i])) {
+                            target = node;
+                        }
+                    }
+                    continue;
+                }
+                target = query.get(0);
+                pId = target.getPKeyId();
+                if (pId == null ) {
+                    break;
+                }
+            }
+            if (target == null || i < split.length - 2) {
+                informationImportRecordService.updateProcess(record.getId(), null, 3, "节点未找到-客户端::" + split[i - 1], record.getUpdateTime());
+                return;
+            }
+        } else {
+            target = node;
+        }
+
+        {
+            // todo 判断target节点下是否有该节点,如果有是否有资料,没有的 监理和施工
+            List<WbsTreeContract> query = jdbcTemplate.query("select * from m_wbs_tree_contract where is_deleted = 0 and p_id = " + target.getPKeyId() + " and full_name = '" + (StringUtil.hasText(newName) ? newName : lastName) + "'",
+                    new BeanPropertyRowMapper<>(WbsTreeContract.class));
+            if (!query.isEmpty()) {
+                for (WbsTreeContract wbsTreeContract : query) {
+                    List<InformationQuery> informationQueries = jdbcTemplate.query("select * from u_information_query where is_deleted = 0 and contract_id = " + record.getContractId() + " and wbs_id = " + wbsTreeContract.getPKeyId()
+                                    + " and classify = " + record.getClassify(), new BeanPropertyRowMapper<>(InformationQuery.class));
+                    if (informationQueries.isEmpty()) {
+                        record.setTargetId(wbsTreeContract.getPKeyId());
+                    }
+                }
+            }
+            if (record.getTargetId() != null && record.getTargetId() > 0) {
+                informationImportRecordService.update(Wrappers.<InformationImportRecord>lambdaUpdate().eq(InformationImportRecord::getId, record.getId()).set(InformationImportRecord::getStatus, 1)
+                        .set(InformationImportRecord::getTargetId, record.getTargetId()).set(InformationImportRecord::getProcess, 20));
+                return;
+            }
+        }
+
+        List<WbsTreePrivate> query = jdbcTemplate.query("select * from m_wbs_tree_private where is_deleted = 0 and project_id = " + target.getProjectId() + " and node_name like '" + lastName + "'",
+                new BeanPropertyRowMapper<>(WbsTreePrivate.class));
+        if (query.isEmpty()) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, "节点未找到-后管::" + target.getFullName(), record.getUpdateTime());
+            return;
+        }
+        WbsTreePrivate wbsTreePrivate = null;
+        List<WbsTreePrivate> tables = null;
+        for (WbsTreePrivate treePrivate : query) {
+            tables = jdbcTemplate.query("select * from m_wbs_tree_private where is_deleted = 0 and type in (2,10) and p_id = " + treePrivate.getPKeyId(), new BeanPropertyRowMapper<>(WbsTreePrivate.class));
+            if (tables.isEmpty()) {
+                tables = jdbcTemplate.query("select * from m_wbs_tree_private where is_deleted = 0 and type in (2,10) and parent_id = " + treePrivate.getId() + " and project_id = " + target.getProjectId(), new BeanPropertyRowMapper<>(WbsTreePrivate.class));
+            }
+            if (!tables.isEmpty()) {
+                wbsTreePrivate = treePrivate;
+                break;
+            }
+            if (treePrivate.getNodeType() != null && treePrivate.getNodeType() == 6) {
+                wbsTreePrivate = treePrivate;
+            }
+        }
+        if (wbsTreePrivate == null) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, "节点未找到-后管::" + target.getFullName(), record.getUpdateTime());
+            return;
+        }
+        if (tables.isEmpty()) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, "表单未找到-后管::" + wbsTreePrivate.getNodeName(), record.getUpdateTime());
+            return;
+        }
+        informationImportRecordService.updateProcess(record.getId(), 7, 1, null, record.getUpdateTime());
+
+        // 在target节点下创建子节点及表单
+        Long parentPKeyId = SnowFlakeUtil.getId();
+        Long parentId = SnowFlakeUtil.getId();
+        String ancestors = target.getAncestors() == null ? target.getId() + "" : target.getAncestors() + "," + target.getId();
+        String ancestorsPId = target.getAncestorsPId() == null ? target.getPKeyId() + "" : target.getAncestorsPId() + "," + target.getPKeyId();
+
+        List<WbsTreeContract> saveList = new ArrayList<>();
+        {
+            WbsTreeContract newData = new WbsTreeContract();
+            BeanUtils.copyProperties(wbsTreePrivate, newData);
+
+            //重塑pKeyId、id和parentId
+            newData.setPKeyId(parentPKeyId);
+            newData.setId(parentId);
+            newData.setParentId(target.getId());
+            newData.setPId(target.getPKeyId());
+            newData.setAncestors(ancestors);
+            newData.setAncestorsPId(ancestorsPId);
+
+            //设置合同段等信息
+            newData.setWbsType(target.getWbsType());
+            newData.setContractId(target.getContractId());
+            newData.setContractIdRelation(target.getContractIdRelation());
+            newData.setContractType(target.getContractType());
+            newData.setCreateTime(new Date());
+            newData.setIsTypePrivatePid(wbsTreePrivate.getPKeyId());
+            if (wbsTreePrivate.getType() != null && new Integer("2").equals(wbsTreePrivate.getType())) {
+                newData.setIsBussShow(1);
+            }
+            //获取当前所有复制的节点的最大sort
+            newData.setSort(ObjectUtils.isNotEmpty(wbsTreePrivate.getSort()) ? wbsTreePrivate.getSort() : 0);
+
+            //记录旧ID
+            newData.setOldId(wbsTreePrivate.getId().toString());
+
+            newData.setFullName(StringUtil.hasText(newName) ? newName : lastName);
+
+            saveList.add(newData);
+        }
+
+        //处理数据
+        for (WbsTreePrivate half : tables) {
+            //处理合同段数据
+            WbsTreeContract newData = new WbsTreeContract();
+            BeanUtils.copyProperties(half, newData);
+
+            //重塑pKeyId、id和parentId
+            newData.setPKeyId(SnowFlakeUtil.getId());
+            newData.setId(SnowFlakeUtil.getId());
+            newData.setParentId(parentId);
+            newData.setPId(parentPKeyId);
+            newData.setAncestors(ancestors + "," + parentId);
+            newData.setAncestorsPId(ancestorsPId + "," + parentPKeyId);
+
+            //记录旧ID
+            newData.setOldId(half.getId().toString());
+            //设置合同段等信息
+            newData.setWbsType(target.getWbsType());
+            newData.setContractId(target.getContractId());
+            newData.setContractIdRelation(target.getContractIdRelation());
+            newData.setContractType(target.getContractType());
+            newData.setCreateTime(new Date());
+            newData.setIsTypePrivatePid(half.getPKeyId());
+            if (half.getType() != null && new Integer("2").equals(half.getType())) {
+                if (half.getDefaultConceal() != null &&  half.getDefaultConceal() == 1) {
+                    // 后续如果此表格中有数据再修改成 1
+                    newData.setIsBussShow(2);
+                } else {
+                    //2023年8月1日14:41:03更改需求,isBussShow默认=1
+                    newData.setIsBussShow(1);
+                }
+            }
+            //获取当前所有复制的节点的最大sort
+            newData.setSort(ObjectUtils.isNotEmpty(half.getSort()) ? half.getSort() : 0);
+
+            //设置名称, 后续根据sheet 名称进行设置
+            newData.setFullName(half.getFullName());
+
+            //设置到保存集合中
+            saveList.add(newData);
+        }
+        wbsTreeContractService.saveBatch(saveList);
+        informationImportRecordService.update(Wrappers.<InformationImportRecord>lambdaUpdate().eq(InformationImportRecord::getId, record.getId())
+                .set(InformationImportRecord::getTargetId, parentPKeyId).set(InformationImportRecord::getProcess, 20));
+    }
+
+    public void importData(@NotNull Long id) {
+        InformationImportRecord record = informationImportRecordService.getById(id);
+        if (record == null || record.getStatus() != 1) {
+            logger.info("记录[{}]已经不存在或者状态错误,已跳过", id);
+            return;
+        }
+        if (record.getTargetId() == null || record.getTargetId() <= 0) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, record.getRemark() + "::找不到目标节点", record.getUpdateTime());
+            return;
+        }
+
+        Path path1 = Paths.get(record.getFilePath());
+        if (!Files.exists(path1)) {
+            logger.info("文件[{}]不存在,已跳过", record.getFilePath());
+            informationImportRecordService.updateProcess(record.getId(), null, 3,  "文件不存在", record.getUpdateTime());
+            return;
+        }
+        try (InputStream is = Files.newInputStream(path1)) {
+            importNodeExcel(is, record);
+            informationImportRecordService.updateProcess(record.getId(), 65, null, null, record.getUpdateTime());
+        } catch (Exception e) {
+            logger.error("文件[{}]读取异常,已跳过", record.getFilePath(), e);
+            informationImportRecordService.updateProcess(record.getId(), null, 3, "文件读取异常", record.getUpdateTime());
+        }
+    }
+
+    public void saveAgain(Long  id, String projectId) {
+        InformationImportRecord record = informationImportRecordService.getById(id);
+        if (record == null || record.getStatus() != 1) {
+            logger.info("记录[{}]已经不存在或者状态错误,已跳过", id);
+            return;
+        }
+        if (record.getTargetId() == null || record.getTargetId() <= 0) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, record.getRemark() + "::找不到目标节点", record.getUpdateTime());
+            return;
+        }
+        try {
+            excelTabClient.synPDFInfo(record.getContractId() + "", record.getTargetId() + "", record.getClassify() + "", projectId, userClient.getTokenByUser("admin"));
+        } catch (Exception e) {
+            logger.error("生成pdf失败", e);
+            informationImportRecordService.updateProcess(record.getId(), null, 3, record.getRemark() + "::生成pdf失败", record.getUpdateTime());
+            return;
+        }
+        // 检查是否成功
+        List<InformationQuery> query = jdbcTemplate.query("select * from u_information_query where contract_id = " + record.getContractId() + " and wbs_id = " + record.getTargetId() + " and classify = " + record.getClassify(),
+                new BeanPropertyRowMapper<>(InformationQuery.class));
+        if (!query.isEmpty()) {
+            informationImportRecordService.updateProcess(record.getId(), 100, 2, null, record.getUpdateTime());
+        }
+    }
+
+    public void importNodeExcel(InputStream is, InformationImportRecord record) throws Exception {
+
+        // 1. 获取节点下所有表单,并建立 sheet名 -> WbsTreeContract 的映射(处理特殊字符)
+        Object[] params;
+        if (record.getClassify() == 1) {
+            params = new Object[]{record.getTargetId(), 1, 2, 3};
+        } else {
+            params = new Object[]{record.getTargetId(), 4, 5, 6};
+        }
+        List<WbsTreeContract> wbsTreeContracts = jdbcTemplate.query(
+                "select * from m_wbs_tree_contract where p_id = ? and is_deleted = 0 and status!=0 and type = 2 and table_owner in (?,?,?) order by sort",
+                new BeanPropertyRowMapper<>(WbsTreeContract.class),
+                params);
+
+        if (wbsTreeContracts.isEmpty()) {
+            informationImportRecordService.updateProcess(record.getId(), null, 3, record.getRemark() + "::没有找到对应的表单数据", record.getUpdateTime());
+            return;
+        }
+        // 2. 加载上传的多sheet Excel文件
+        com.spire.xls.Workbook mainWorkbook = new com.spire.xls.Workbook();
+        try {
+            mainWorkbook.loadFromStream(is);
+        } catch (Exception e) {
+            logger.error("加载Excel文件失败", e);
+            informationImportRecordService.updateProcess(record.getId(), null, 3, record.getRemark() + "::Excel文件解析失败", record.getUpdateTime());
+            return ;
+        }
+
+        // 3. 遍历所有sheet,逐个处理
+        int sheetCount = mainWorkbook.getWorksheets().getCount();
+        // 处理表单名称(与下载时的sheet名处理逻辑一致,确保匹配)
+        Map<String, WbsTreeContract> nodeNameToContractMap = new HashMap<>();
+        for (WbsTreeContract contract : wbsTreeContracts) {
+            String processedNodeName = contract.getNodeName().replaceAll("[\\\\/:*?\"<>|]", "_").trim();
+            nodeNameToContractMap.put(processedNodeName, contract);
+        }
+        if(wbsTreeContracts.size()<sheetCount){
+            for (int i = 0; i < sheetCount; i++) {
+                com.spire.xls.Worksheet sheet = mainWorkbook.getWorksheets().get(i);
+                String sheetName = sheet.getName();
+                String processedSheetName = sheetName.replaceAll("[\\\\/:*?\"<>|]", "_").trim(); // 处理sheet名特殊字符
+                // 1. 识别当前sheet是否为复制表
+                DuplicateSheetRecognizer.DuplicateSheetResult result =
+                        DuplicateSheetRecognizer.recognize(processedSheetName);
+                if (result.isDuplicate()) {
+                    String originalName = result.getOriginalName();
+                    if(nodeNameToContractMap.containsKey(originalName)){
+                        WbsTreeContract contract = nodeNameToContractMap.get(originalName);
+                        R r = excelTabClient.copeBussTab(contract.getPKeyId(), userClient.getTokenByUser("admin"));
+                        if(r.isSuccess()){
+                            WbsTreeContract data = (WbsTreeContract) r.getData();
+                            nodeNameToContractMap.put(data.getNodeName(), data);
+                            sheet.setName(data.getNodeName());
+                        }
+                    }
+
+                }
+            }
+        }
+        for (int i = 0; i < sheetCount; i++) {
+            com.spire.xls.Worksheet sheet = mainWorkbook.getWorksheets().get(i);
+            String sheetName = sheet.getName();
+            String processedSheetName = sheetName.replaceAll("[\\\\/:*?\"<>|]", "_").trim(); // 处理sheet名特殊字符
+
+            // 匹配对应的表单
+            WbsTreeContract matchedContract = nodeNameToContractMap.get(processedSheetName);
+            if (matchedContract == null) {
+                logger.warn("sheet名[{}]未匹配到任何表单,已跳过", sheetName);
+                continue;
+            }
+
+            // 获取当前表单的pkeyId
+            String pkeyId = matchedContract.getPKeyId() + "";
+            logger.info("开始处理sheet[{}],对应表单pkeyId[{}]", sheetName, pkeyId);
+
+            // 4. 将当前sheet保存为临时Excel文件(模拟单个文件上传)
+            File tempFile = null;
+            InputStream tempInputStream = null;
+            try {
+                // 创建临时文件
+                tempFile = File.createTempFile("sheet_", ".xlsx");
+                // 创建仅包含当前sheet的新工作簿
+                com.spire.xls.Workbook singleSheetWorkbook = new com.spire.xls.Workbook();
+                singleSheetWorkbook.getWorksheets().clear();
+                singleSheetWorkbook.getWorksheets().addCopy(sheet); // 复制当前sheet到新工作簿
+                singleSheetWorkbook.saveToFile(tempFile.getAbsolutePath(), com.spire.xls.FileFormat.Version2016);
+                singleSheetWorkbook.dispose();
+
+                // 读取临时文件作为输入流,调用单表单导入逻辑
+                tempInputStream = new FileInputStream(tempFile);
+                Map<String, Object> sheetResult = processSingleSheetImport(pkeyId, tempInputStream);
+                if(sheetResult.isEmpty()){
+                    continue;
+                }
+                Map<String, String> dataMap = WbsTreeContractController.getDataMap(sheetResult);
+                for (Map.Entry<String, String> entry : dataMap.entrySet()) {
+                    String value = entry.getValue();
+                    if (value != null) {
+                        value = value.replace("'", "''");
+                        entry.setValue(value);
+                    }
+                }
+                String delSql = "delete from " + matchedContract.getInitTableName() + " where p_key_id=" + matchedContract.getPKeyId();
+                dataMap.put("p_key_id", matchedContract.getPKeyId()+"");
+                String  sqlInfo = excelTabService.buildMTableInsertSql(matchedContract.getInitTableName(), dataMap, SnowFlakeUtil.getId(), null, null).toString();
+                jdbcTemplate.execute(delSql);
+                jdbcTemplate.execute(sqlInfo);
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new ServiceException("处理sheet[" + sheetName + "]失败:" + e.getMessage());
+            } finally {
+                // 关闭流并删除临时文件
+                if (tempInputStream != null) {
+                    tempInputStream.close();
+                }
+                if (tempFile != null && !tempFile.delete()) {
+                    logger.warn("临时文件[{}]删除失败", tempFile.getAbsolutePath());
+                }
+            }
+        }
+        mainWorkbook.dispose();
+    }
+
+    /**
+     * 复用importExcel的核心逻辑,处理单个sheet的导入
+     * 抽取自原importExcel方法,参数改为pkeyId和输入流
+     */
+    private Map<String, Object> processSingleSheetImport(String pkeyId, InputStream inputStream) throws Exception {
+        // 获取当前表htmlString(模板)
+        String htmlString_1 = wbsTreeContractServiceImpl.getHtmlString(pkeyId);
+        if (StringUtils.isEmpty(htmlString_1)) {
+            throw new ServiceException("获取表单[" + pkeyId + "]的html模板失败");
+        }
+
+        // 结果集
+        Map<String, Object> resultMap = new HashMap<>();
+
+        // 日期格式正则(复用原逻辑)
+        String doubleSlashRegex_XG = ".*\\/[^\\/]*\\/.*";
+        String dateFormatRegex_yyyyMdd = "\\d{4}/\\d{1,2}/\\d{1,2}";
+        String dateFormatRegex_yyyyMMdd = "\\d{4}/\\d{2}/\\d{2}";
+        String dateFormatRegex_chinese = "(\\d{4}年\\d{1,2}月\\d{1,2}日|\\d{4}年\\d{2}月\\d{2}日)";
+        SimpleDateFormat inputDateFormat = new SimpleDateFormat("yyyy/M/dd");
+        SimpleDateFormat outputDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
+
+        // 临时文件路径(复用原逻辑)
+        Long id = SnowFlakeUtil.getId();
+        String importExcelFilePath = FileUtils.getSysLocalFileUrl();
+        String importExcelTOHtmlPath = importExcelFilePath + "/pdf//" + id + ".html";
+
+        com.spire.xls.Workbook workbook = null;
+        try {
+
+            // 导入的excel转换为html(复用原逻辑)
+            workbook = new com.spire.xls.Workbook();
+            workbook.loadFromHtml(inputStream); // 加载单个sheet的输入流
+            workbook.saveToFile(importExcelTOHtmlPath, com.spire.xls.FileFormat.HTML);
+            com.spire.xls.Worksheet sheet = workbook.getWorksheets().get(0);
+
+            // 获取转换后的html路径
+            String url_1 = importExcelTOHtmlPath.split("pdf//")[0];
+            String excelToHtmlFileUrl = url_1 + "/pdf/" + id + "_files/" + sheet.getName() + ".html";
+            String htmlString_2 = IoUtil.readToString(new FileInputStream(ResourceUtil.getFile(excelToHtmlFileUrl)));
+
+            // 解析两张html的tr、td(复用原逻辑)
+            Document doc_1 = Jsoup.parse(htmlString_1); // 模板html
+            Document doc_2 = Jsoup.parse(htmlString_2); // 导入的excel转换的html
+            Elements trElements1 = doc_1.select("table tbody tr");
+            Elements trElements2 = doc_2.select("table tbody tr");
+
+            for (int i = 0; i < trElements1.size(); i++) {
+                Element tr1 = trElements1.get(i);
+                Element tr2 = trElements2.size() > i ? trElements2.get(i) : null;
+                if (tr2 == null) break;
+
+                Elements tdElements1 = tr1.select("td");
+                Elements tdElements2 = tr2.select("td");
+
+                for (int j = 0; j < tdElements1.size(); j++) {
+                    Element td1 = tdElements1.get(j);
+                    if (td1.attr("dqid").length() > 0) { // 跳过包含dqid的td
+                        continue;
+                    }
+                    // 跳过包含hc-table-form-upload子元素的td
+                    if (!td1.select("hc-table-form-upload").isEmpty()) {
+                        continue;
+                    }
+
+                    Element td2 = tdElements2.size() > j ? tdElements2.get(j) : null;
+                    if (td2 == null) break;
+
+                    String keyName = WbsTreeContractController.getKeyNameFromChildElement(td1); // 复用原方法获取key
+                    if (StringUtils.isNotEmpty(keyName)) {
+                        String divValue = td2.text();
+                        if (StringUtils.isNotEmpty(divValue)) {
+                            // 日期范围处理
+                            if (WbsTreeContractController.parseDateRange(divValue).size() == 2) {
+                                resultMap.put(keyName, WbsTreeContractController.parseDateRange(divValue));
+                                continue;
+                            }
+
+                            // 日期格式转换(复用原逻辑)
+                            Pattern pattern_XG = Pattern.compile(doubleSlashRegex_XG);
+                            Matcher matcher_XG = pattern_XG.matcher(divValue);
+                            if (matcher_XG.matches()) {
+                                Pattern pattern_yyyyMdd = Pattern.compile(dateFormatRegex_yyyyMdd);
+                                Pattern pattern_yyyyMMdd = Pattern.compile(dateFormatRegex_yyyyMMdd);
+                                Matcher matcher_yyyyMdd = pattern_yyyyMdd.matcher(divValue);
+                                Matcher matcher_yyyyMMdd = pattern_yyyyMMdd.matcher(divValue);
+
+                                if (matcher_yyyyMdd.matches() || matcher_yyyyMMdd.matches()) {
+                                    Date date = inputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            } else if (divValue.contains("年") && divValue.contains("月") && divValue.contains("日")) {
+                                Pattern pattern_chinese = Pattern.compile(dateFormatRegex_chinese);
+                                Matcher matcher_chinese = pattern_chinese.matcher(divValue);
+                                if (matcher_chinese.matches()) {
+                                    Date date = outputDateFormat.parse(divValue);
+                                    divValue = outputDateFormat.format(date);
+                                }
+                            }
+
+                            resultMap.put(keyName, divValue);
+                        }
+                    }
+                }
+            }
+        } finally {
+            if (workbook != null) {
+                workbook.dispose();
+            }
+        }
+        return resultMap;
+    }
+
+
+    @Scheduled(fixedDelay = 1000L * 60 * 10)
+    @Async
+    public void importInformationData() {
+        if (!SystemUtils.isLinux() || !RUNNING.compareAndSet(false, true)) {
+            return;
+        }
+        try {
+            List<InformationImportRecord> records = informationImportRecordService.list(Wrappers.<InformationImportRecord>lambdaQuery()
+                            .select(InformationImportRecord::getId, InformationImportRecord::getNodeId, InformationImportRecord::getUpdateTime, InformationImportRecord::getProcess)
+                    .in(InformationImportRecord::getStatus, 0, 1).last(" order by create_time  limit 100"));
+            if (!records.isEmpty()) {
+                for (InformationImportRecord record : records) {
+                    IMPORT_EXECUTOR.submit(() -> {
+                        try {
+                            WbsTreeContract node = wbsTreeContractService.getOne(Wrappers.<WbsTreeContract>lambdaQuery().eq(WbsTreeContract::getPKeyId, record.getNodeId()));
+                            if (node == null) {
+                                informationImportRecordService.updateProcess(record.getId(), null, 3, "节点未找到-客户端", record.getUpdateTime());
+                                return;
+                            }
+                            if (record.getProcess() <= 15) {
+                                init(record.getId(), node);
+                            }
+                            if (record.getProcess() < 60) {
+                                importData(record.getId());
+                            }
+                            if (record.getProcess() < 100) {
+                                saveAgain(record.getId(), node.getProjectId());
+                            }
+                        } catch (Exception e) {
+                            logger.error("导入数据失败", e);
+                        }
+                    });
+                }
+            }
+            List<InformationImportRecord> list = informationImportRecordService.list(Wrappers.<InformationImportRecord>lambdaQuery().select(InformationImportRecord::getId, InformationImportRecord::getFilePath)
+                    .eq(InformationImportRecord::getStatus, 2).apply("update_time < DATE_SUB(NOW(), INTERVAL 3 DAY)"));
+            for (InformationImportRecord record : list) {
+                try {
+                    Files.deleteIfExists(Paths.get(record.getFilePath()));
+                } catch (Exception e) {
+                    logger.error("删除文件失败", e);
+                }
+            }
+        } finally {
+            RUNNING.set(false);
+        }
+    }
+}

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/LinkdataInfoController.java

@@ -309,6 +309,8 @@ public class LinkdataInfoController extends BladeController {
                     element1.removeAttr("weighing");
                     element1.removeAttr("v-model");
                     element1.removeAttr("@focus");
+                    //删除html错误标识
+                    element1.removeAttr("htmlElementError");
                     keyId = element1.attr("keyName");
                     if (element.html().indexOf("hc-form-checkbox-group") >= 0) {
                         element1.removeAttr(":val");

+ 8 - 16
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/NodeBaseInfoController.java

@@ -191,31 +191,23 @@ public class NodeBaseInfoController extends BladeController {
             List<WbsTreeContract> nodeNames = jdbcTemplate.query(sql1, new BeanPropertyRowMapper<>(WbsTreeContract.class));
              nameRule = nameRule.trim().replaceAll("(?i:c)", "");
             List<String> list = Arrays.asList(nameRule.split("[^.\\d]"));
-            List<Integer> index = list.stream().map(Integer::parseInt).collect(Collectors.toList());
+            //添加排序规则,永远都是0-1-2-3-4-5-6的顺序去组装数据
+            List<Integer> index = list.stream().map(Integer::parseInt).sorted().collect(Collectors.toList());
             Map<Integer, String> map = nodeNames.stream()
                     .collect(Collectors.toMap(
                             WbsTreeContract::getNodeType,
                             WbsTreeContract::getNodeName,
                             (existing, replacement) -> replacement // 如果键重复,保留后者
                     ));
-            StringBuilder result = new StringBuilder("");
+            List<String> result = new ArrayList<>();
             for (Integer i : index) {
-                if(i==0){
-                    if(map.containsKey(1)){
-                        result.append(map.get(1));
-                    }
-                }
-                else if(i==1){
-                    if(map.containsKey(18)){
-                        result.append(map.get(18));
-                    }
-                }else {
-                    if(map.containsKey(i)){
-                        result.append(map.get(i));
-                    }
+                String title = map.get(i == 0 ? 1 : i == 1 && map.containsKey(18) ? 18 : i);
+                if(title == null || result.stream().anyMatch(f -> f.contains(title))){
+                    continue;
                 }
+                result.add(title);
             }
-        return R.data(result.toString());
+        return R.data(String.join("", result));
     }
 
     public R synPDFInfo(String contractId, String nodeId, String classify, String projectId,Map<Long,Map<String,Object>>dataMap) {

+ 335 - 24
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java

@@ -15,7 +15,7 @@ import lombok.SneakyThrows;
 import org.apache.commons.lang.StringUtils;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
-import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.xssf.usermodel.*;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
@@ -52,6 +52,7 @@ import org.springframework.dao.DataAccessException;
 import org.springframework.http.ResponseEntity;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.SingleColumnRowMapper;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
@@ -455,7 +456,7 @@ public class WbsTreeContractController extends BladeController {
         Map<String, List<WbsTreeContract>> mainCopyGroupMap = new LinkedHashMap<>(); // 保持主表原始顺序
         List<WbsTreeContract> copyTableList = new ArrayList<>(); // 临时存储所有复制表
 
-            // 2.1 第一次遍历:分离主表和复制表
+        // 2.1 第一次遍历:分离主表和复制表
         for (WbsTreeContract contract : formList) {
             String nodeName = contract.getNodeName();
             boolean isCopy = nodeName.contains("__");
@@ -477,7 +478,7 @@ public class WbsTreeContractController extends BladeController {
             }
         }
 
-            // 3. 第二步:批量处理所有复制表,匹配主表分组
+        // 3. 第二步:批量处理所有复制表,匹配主表分组
         List<WbsTreeContract> orphanCopyList = new ArrayList<>();
         for (WbsTreeContract copyContract : copyTableList) {
             String nodeName = copyContract.getNodeName();
@@ -506,7 +507,7 @@ public class WbsTreeContractController extends BladeController {
             }
         }
 
-            // 4. 第三步:处理每个主表分组,对复制表排序(保持主表原始顺序)
+        // 4. 第三步:处理每个主表分组,对复制表排序(保持主表原始顺序)
         List<WbsTreeContract> sortedList = new ArrayList<>();
         for (Map.Entry<String, List<WbsTreeContract>> entry : mainCopyGroupMap.entrySet()) {
             List<WbsTreeContract> groupList = entry.getValue();
@@ -545,15 +546,30 @@ public class WbsTreeContractController extends BladeController {
         // 获取节点及祖先节点信息用于构建文件名
         WbsTreeContract node = wbsTreeContractServiceImpl.getById(nodeId);
         List<WbsTreeContract> ancestorsList = wbsTreeContractServiceImpl.getAncestorsList(node.getAncestorsPId());
+        // 按照指定的nodeType顺序排序: 1, 18, 2, 3, 4, 5, 6
+        ancestorsList.sort((a, b) -> {
+            List<Integer> order = Arrays.asList(1, 18, 2, 3, 4, 5, 6);
+            Integer typeA = a.getNodeType();
+            Integer typeB = b.getNodeType();
+            return Integer.compare(
+                    order.indexOf(typeA != null ? typeA : -1),
+                    order.indexOf(typeB != null ? typeB : -1)
+            );
+        });
         for (WbsTreeContract ancestor : ancestorsList) {
-            if (2 == ancestor.getNodeType()) {
+            if (18 == ancestor.getNodeType()) {
                 excelName.append(ancestor.getNodeName());
-            } else if (4 == ancestor.getNodeType()) {
-                excelName.append("-" + ancestor.getNodeName());
+            } else if (2 == ancestor.getNodeType()) {
+                excelName.append("》" + ancestor.getNodeName());
+            } else if (3 == ancestor.getNodeType()) {
+                excelName.append("》" + ancestor.getNodeName());
+            }else if (4 == ancestor.getNodeType()) {
+                excelName.append("》" + ancestor.getNodeName());
+            } else if (5 == ancestor.getNodeType()) {
+                excelName.append("》" + ancestor.getNodeName());
             }
         }
-        excelName.append("-" + node.getNodeName());
-
+        excelName.append("》" + node.getNodeName());
         // 创建主工作簿(用于合并多sheet)
         XSSFWorkbook mainWorkbook = new XSSFWorkbook();
 
@@ -594,6 +610,8 @@ public class WbsTreeContractController extends BladeController {
                 InputStream htmlStream = FileUtils.getInputStreamByUrl(htmlUrl);
                 String htmlContent = IoUtil.readToString(htmlStream);
                 org.apache.poi.ss.usermodel.Workbook singleSheetWorkbook = HtmlTableToExcelConverter.convertHtmlTableToExcel(htmlContent);
+                //给excel添加默认值
+                addDefaultValue(singleSheetWorkbook,form.getInitTableName(),htmlContent,form.getPId());
 
                 // 3. 将单个sheet复制到主工作簿
                 if (singleSheetWorkbook.getNumberOfSheets() > 0) {
@@ -610,10 +628,19 @@ public class WbsTreeContractController extends BladeController {
             if (mainWorkbook.getNumberOfSheets() == 0) {
                 throw new ServiceException("所有表单均无法生成有效Excel内容");
             }
-
             String originalFileName = excelName + ".xlsx";
-
+            if(originalFileName.startsWith("》")){
+                originalFileName=originalFileName.substring(1);
+            }
+            XSSFSheet sheetAt = mainWorkbook.getSheetAt(0);
+            if (sheetAt != null) {
+                XSSFRow row = sheetAt.createRow(0);
+                XSSFCell cell = row.createCell(0);
+                // 设置批注
+                setCommentToFirstCell(mainWorkbook, sheetAt, cell, originalFileName);
+            }
             try {
+                originalFileName = originalFileName.replaceAll("#", "号");
                 // 1. 先编码所有字符
                 String fullyEncoded = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name());
 
@@ -623,25 +650,19 @@ public class WbsTreeContractController extends BladeController {
                         .replaceAll("%2B", "+")       // 解码+号
                         .replaceAll("%2F", "/")       // 解码/号
                         .replaceAll("%23", "#")       // 解码#号
-                        .replaceAll("%7E", "~")       // 解码~号
-                        // - 号不需要处理,URL编码不会编码-
-                        ;
+                        .replaceAll("%7E", "~");      // 解码~号
 
                 // 3. 设置响应头
                 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
                 response.setHeader("Content-Disposition",
-                        "attachment; filename=\"" + partiallyDecoded + "\"; ");
+                        "attachment; filename=\"" + partiallyDecoded + "\"; filename*=UTF-8''" + fullyEncoded);
 
             } catch (Exception e) {
-                // 备用方案:简单清理
-                String safeFileName = originalFileName
-                        .replaceAll("[\\\\:*?\"<>|]", "_")
-                        .trim();
+                // 备用方案
+                String safeFileName = originalFileName.replaceAll("[\\\\:*?\"<>|]", "_").trim();
                 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
-                response.setHeader("Content-Disposition",
-                        "attachment; filename=\"" + safeFileName + "\"");
+                response.setHeader("Content-Disposition", "attachment; filename=\"" + safeFileName + "\"");
             }
-
             // 写入输出流
             try (ServletOutputStream outputStream = response.getOutputStream()) {
                 mainWorkbook.write(outputStream);
@@ -657,6 +678,292 @@ public class WbsTreeContractController extends BladeController {
             }
         }
     }
+    public static void setCommentToFirstCell(XSSFWorkbook workbook, XSSFSheet sheet,
+                                             XSSFCell cell, String commentText) {
+        // 创建绘图对象
+        XSSFDrawing drawing = sheet.createDrawingPatriarch();
+
+        // 创建锚点,定位批注框
+        ClientAnchor anchor = workbook.getCreationHelper().createClientAnchor();
+        anchor.setCol1(cell.getColumnIndex());      // 起始列
+        anchor.setCol2(cell.getColumnIndex() + 3);  // 结束列(控制宽度)
+        anchor.setRow1(cell.getRowIndex());         // 起始行
+        anchor.setRow2(cell.getRowIndex() + 2);     // 结束行(控制高度)
+
+        // 创建批注
+        Comment comment = drawing.createCellComment(anchor);
+
+        // 设置批注内容
+        comment.setString(new XSSFRichTextString(commentText));
+
+        // 将批注关联到单元格
+        cell.setCellComment(comment);
+    }
+    private void addDefaultValue(org.apache.poi.ss.usermodel.Workbook singleSheetWorkbook, String initTableName,String htmlContent,Long pId) {
+        if (singleSheetWorkbook == null || initTableName == null) {
+            return;
+        }
+        Document doc = Jsoup.parse(htmlContent);
+        Sheet sourceSheet = singleSheetWorkbook.getSheetAt(0);
+        //字段查询、获取公式字段
+        String colKeys = "SELECT e_key from m_table_info a ,m_wbs_form_element b WHERE a.tab_en_name = '" + initTableName + "' and a.id=b.f_id and b.id  in(SELECT element_id from m_element_formula_mapping c where c.is_deleted=0) ";
+        List<Map<String, Object>> maps = jdbcTemplate.queryForList(colKeys);
+        String sql="select formula_config from m_wbs_tree_contract_extend where p_key_id="+pId;
+        List<String> query = jdbcTemplate.query(sql, new SingleColumnRowMapper<>(String.class));
+        Map<String,Object>list=new HashMap<>();
+        if (!query.isEmpty()) {
+            String formulaConfig = query.get(0);
+            String[] strings = formulaConfig.split(",");
+            for (String string : strings) {
+                String[] strings1 = string.split(":");
+                if (strings1.length >= 2) {
+                    if(strings1[0].equals(initTableName)){
+                        list.put(strings1[1],strings1[0]);
+                    }
+                }
+            }
+        }
+        if (maps.size() > 0) {
+            //1.对公式字段添加默认值
+            for (Map<String, Object> keys : maps) {
+                if(!list.isEmpty()){
+                    if(list.containsKey(keys.get("e_key"))){
+                        continue;
+                    }
+                }
+                String key = keys.get("e_key") + "__";
+                Elements elements = doc.select("[keyname~=^" + key + "]");
+                for (Element element : elements) {
+                    Elements x = element.getElementsByAttribute("x1");
+                    Elements y = element.getElementsByAttribute("y1");
+                    Cell cell = getCellAt(sourceSheet, x.attr("x1"), y.attr("y1"));
+                    // 给单元格添加默认值并设置背景色为灰色
+                    if (cell != null) {
+                        // 设置默认值
+                        cell.setCellValue("公式配置");
+                        // 创建灰色背景样式
+                        CellStyle grayStyle = singleSheetWorkbook.createCellStyle();
+                        grayStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                        grayStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+                        grayStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+                        // 设置字体居中
+                        grayStyle.setAlignment(HorizontalAlignment.CENTER);
+                        grayStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+                        // 应用样式到单元格
+                        cell.setCellStyle(grayStyle);
+                    }
+                }
+
+            }
+        }
+        //2.对日期字段添加默认值
+        Elements dateElements = doc.select("el-date-picker");
+        if(!dateElements.isEmpty()){
+            for (Element element : dateElements) {
+                Elements elements1 = element.select("[type=date]");
+                if(!elements1.isEmpty()){
+                    for (Element element1 : elements1) {
+                        Elements x1 = element1.getElementsByAttribute("x1");
+                        Elements y1 = element1.getElementsByAttribute("y1");
+                        Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                        if (cell != null) {
+                            if(cell.getCellTypeEnum()==CellType.STRING){
+                                if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                                    if(cell.getStringCellValue().equals("公式配置")){
+                                        // 设置单元格值为"日期框"
+                                        cell.setCellValue("公式配置,日期框");
+                                        continue;
+                                    }
+                                }
+                            }
+                            // 设置单元格值为"日期框"
+                            cell.setCellValue("日期框");
+                            // 创建或获取单元格样式并设置居中
+                            CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                            centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                            centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                            centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                            // 应用样式到单元格
+                            cell.setCellStyle(centerStyle);
+                        }
+
+                    }
+                }
+                Elements elements2 = element.select("[type=datetimerange]");
+                if(!elements2.isEmpty()){
+                    for (Element element2 : elements2) {
+                        Elements x1 = element2.getElementsByAttribute("x1");
+                        Elements y1 = element2.getElementsByAttribute("y1");
+                        Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                        if (cell != null) {
+                            if(cell.getCellTypeEnum()==CellType.STRING){
+                                if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                                    if(cell.getStringCellValue().equals("公式配置")){
+                                        cell.setCellValue("公式配置,日期范围框,两日期用-分割");
+                                        continue;
+                                    }
+                                }
+                            }
+                            // 设置单元格值为"日期框"
+                            cell.setCellValue("日期范围框,两日期用-分割");
+                            // 创建或获取单元格样式并设置居中
+                            CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                            centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                            centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                            centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                            // 应用样式到单元格
+                            cell.setCellStyle(centerStyle);
+                        }
+
+                    }
+                }
+            }
+        }
+        //3.对下拉框字段添加默认值
+        Elements selectElements = doc.select("el-select");
+        if(!selectElements.isEmpty()){
+            for (Element selectElement : selectElements) {
+                Elements elements = selectElement.select("[class=is-multiple]");
+                if(!elements.isEmpty()){
+                    for (Element element : elements) {
+                        Elements x1 = element.getElementsByAttribute("x1");
+                        Elements y1 = element.getElementsByAttribute("y1");
+                        Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                        if (cell != null) {
+                            if(cell.getCellTypeEnum()==CellType.STRING){
+                                if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                                    if(cell.getStringCellValue().equals("公式配置")){
+                                        continue;
+                                    }
+                                }
+                            }
+                            // 设置单元格值为"多选下拉框"
+                            cell.setCellValue("多选下拉框,如需填写选项内容,并用、分割");
+                            // 创建或获取单元格样式并设置居中
+                            CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                            centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                            centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                            centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                            // 应用样式到单元格
+                            cell.setCellStyle(centerStyle);
+                        }
+                    }
+                }else {
+                    Elements x1 = selectElement.getElementsByAttribute("x1");
+                    Elements y1 = selectElement.getElementsByAttribute("y1");
+                    Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                    if (cell != null) {
+                        if(cell.getCellTypeEnum()==CellType.STRING){
+                            if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                                if(cell.getStringCellValue().equals("公式配置")){
+                                    continue;
+                                }
+                            }
+                        }
+                        // 设置单元格值为"单选下拉框"
+                        cell.setCellValue("单选下拉框,如需填写选项内容,否则无法导入");
+                        // 创建或获取单元格样式并设置居中
+                        CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                        centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                        centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                        centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                        // 应用样式到单元格
+                        cell.setCellStyle(centerStyle);
+                    }
+
+                }
+            }
+        }
+        //4.对电签字段添加默认值
+        Elements dqlist = doc.getElementsByAttribute("dqid");
+        if(!dqlist.isEmpty()){
+            for (Element element : dqlist) {
+                Elements x1 = element.getElementsByAttribute("x1");
+                Elements y1 = element.getElementsByAttribute("y1");
+                Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                if (cell != null) {
+                    if(cell.getCellTypeEnum()==CellType.STRING){
+                        if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                            if(cell.getStringCellValue().equals("公式配置")){
+                                continue;
+                            }
+                        }
+                    }
+                    // 设置单元格值为"电签配置,请勿填写数据"
+                    cell.setCellValue("电签配置,请勿填写数据");
+                    // 创建或获取单元格样式并设置居中
+                    CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                    centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                    centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                    centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                    // 应用样式到单元格
+                    cell.setCellStyle(centerStyle);
+                }
+            }
+        }
+        //5.对图片上传框添加默认值
+        // 跳过包含hc-table-form-upload子元素的td
+        Elements uploadElements = doc.select("hc-table-form-upload");
+        if (!uploadElements.isEmpty()) {
+            for (Element element : uploadElements) {
+                Elements x1 = element.getElementsByAttribute("x1");
+                Elements y1 = element.getElementsByAttribute("y1");
+                Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                if (cell != null) {
+                    if(cell.getCellTypeEnum()==CellType.STRING){
+                        if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                            if(cell.getStringCellValue().equals("公式配置")){
+                                continue;
+                            }
+                        }
+                    }
+                    // 设置单元格值为"电签配置,请勿填写数据"
+                    cell.setCellValue("图片框,请到客户端上传");
+                    // 创建或获取单元格样式并设置居中
+                    CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                    centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                    centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                    centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                    // 应用样式到单元格
+                    cell.setCellStyle(centerStyle);
+                }
+            }
+        }
+        //6.对默认值字段添加默认值
+        Elements deflist = doc.getElementsByAttribute("defText");
+        if(!deflist.isEmpty()){
+            for (Element element : deflist) {
+                Elements x1 = element.getElementsByAttribute("x1");
+                Elements y1 = element.getElementsByAttribute("y1");
+                Cell cell = getCellAt(sourceSheet, x1.attr("x1"), y1.attr("y1"));
+                if (cell != null) {
+                    if(cell.getCellTypeEnum()==CellType.STRING){
+                        if(StringUtils.isNotEmpty(cell.getStringCellValue())){
+                            if(cell.getStringCellValue().equals("公式配置")){
+                                continue;
+                            }
+                        }
+                    }
+                    // 设置单元格值为"电签配置,请勿填写数据"
+                    cell.setCellValue("默认值配置");
+                    // 创建或获取单元格样式并设置居中
+                    CellStyle centerStyle = singleSheetWorkbook.createCellStyle();
+                    centerStyle.cloneStyleFrom(cell.getCellStyle()); // 保留原有样式
+                    centerStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中
+                    centerStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
+                    // 应用样式到单元格
+                    cell.setCellStyle(centerStyle);
+                }
+            }
+        }
+    }
+    Cell getCellAt(Sheet sheet, String x, String y) {
+        Row row = sheet.getRow(Integer.parseInt(y)-1);
+        if (row != null) {
+            return row.getCell(Integer.parseInt(x)-1);
+        }
+        return null;
+    }
 
     /**
      * 复制sheet内容(包括样式、合并区域、行高列宽)
@@ -1035,7 +1342,7 @@ public class WbsTreeContractController extends BladeController {
         return sql;
     }
 
-    public Map<String,String> getDataMap(Map<String, Object> originalMap){
+    public static Map<String,String> getDataMap(Map<String, Object> originalMap){
         // 用于存储合并后的结果
         Map<String, String> mergedMap = new HashMap<>();
 
@@ -1045,6 +1352,10 @@ public class WbsTreeContractController extends BladeController {
         for (Map.Entry<String, Object> entry : originalMap.entrySet()) {
             String key = entry.getKey();
             String value = entry.getValue()+"";
+            value = value.replaceAll("\\s+", "");
+            if(StringUtils.isNotEmpty(value)&&(value.equals("公式配置")||value.equals("日期框")||value.equals("日期范围框,两日期用-分割")||value.equals("多选下拉框,如需填写选项内容,并用、分割")||value.equals("单选下拉框,如需填写选项内容,否则无法导入")||value.equals("电签配置,请勿填写数据")||value.equals("图片框,请到客户端上传")||value.equals("默认值配置")||value.equals("公式配置,日期框")||value.equals("公式配置,日期范围框,两日期用-分割"))){
+                continue;
+            }
             Matcher matcher = pattern.matcher(key);
 
             if (matcher.matches()) {
@@ -1736,7 +2047,7 @@ public class WbsTreeContractController extends BladeController {
         return false;
     }
 
-    private static String getKeyNameFromChildElement(Element element) {
+    public static String getKeyNameFromChildElement(Element element) {
         //TODO Element UI的时间标签待补全
         String[] tagNames = {"el-input", "el-date-picker", "el-time-picker", "hc-form-select-search", "hc-table-form-upload", "hc-form-checkbox-group", "el-radio-group", "el-select"};
         for (String tagName : tagNames) {

+ 142 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreePrivateController.java

@@ -46,6 +46,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -66,6 +67,7 @@ public class WbsTreePrivateController extends BladeController {
     private final IExcelTabService iExcelTabService;
     private final JdbcTemplate jdbcTemplate;
     private final IWbsParamService iWbsParamService;
+    private final ExcelTabController excelTabController;
 
     /**
      * 保存项目日志划分
@@ -403,6 +405,72 @@ public class WbsTreePrivateController extends BladeController {
                                                             @RequestParam("projectId") String projectId) {
         R<List<WbsNodeTableVO>> r = findNodeTableByCondition(parentId, wbsId, projectId);
         List<WbsNodeTableVO> data = r.getData();
+        //解析html
+        data.forEach(f -> {
+            String htmlUrl = f.getHtmlUrl();
+            String initTableId = f.getInitTableId();
+            if(StringUtil.isNotBlank(htmlUrl) && StringUtils.isNotEmpty(initTableId)){
+                //获取元素表数据
+                List<WbsFormElement> wbsFormElements = wbsFormElementService.getBaseMapper().selectList(Wrappers.<WbsFormElement>lambdaQuery()
+                        .eq(WbsFormElement::getFId, initTableId)
+                        .eq(WbsFormElement::getIsDeleted, 0)
+                );
+
+                List<String> keys = wbsFormElements.stream().map(WbsFormElement::getEKey).collect(Collectors.toList());
+
+
+                // 读取html页面信息
+                String htmlString = "";
+                //解析html
+                try {
+                    R excelHtml = excelTabController.getExcelHtml(f.getPKeyId());
+                    htmlString = excelHtml.getData().toString();
+                } catch (Exception e) {
+                }
+                if(StringUtil.isEmpty(htmlString)){
+                    return;
+                }
+                // 样式集合
+                Document doc = Jsoup.parse(htmlString);
+                //获取所有input标签
+                //获取所有input标签
+                String[] tagNames = {"el-input", "el-date-picker", "el-time-picker", "hc-form-select-search",
+                        "hc-table-form-upload", "hc-form-checkbox-group", "el-radio-group", "el-select"};
+                Elements inputs = new Elements();
+                for (String tagName : tagNames) {
+                    inputs.addAll(doc.select(tagName));
+                }
+                //判断标签是否存在id属性
+                inputs.forEach(input -> {
+                    String id = input.attr("id");
+                    if(StringUtils.isEmpty(id)){
+                        f.setHtmlElementError(1);
+                        input.attr("htmlElementError", "1");
+                    } else {
+                        /**
+                         * 判断当前元素是否符合格式
+                         * 1、是否存在key_
+                         * 2、是否存在__
+                         * 3、key_后面是否为数字
+                         * 4、__后面是否为坐标
+                         * 5、key是否在元素库中存在
+                         */
+                        if(!id.contains("key_")
+                                || !id.contains("__")
+                                || !StringUtils.isNumber(id.split("__")[0].replace("key_",""))
+                                || !id.split("__")[1].contains("_")
+                                || !StringUtils.isNumber(id.split("__")[1].split("_")[0])
+                                || !StringUtils.isNumber(id.split("__")[1].split("_")[1])
+                                || !keys.contains(id.split("__")[0])
+                        ){
+                            f.setHtmlElementError(1);
+                            input.attr("htmlElementError", "1");
+                        }
+                    }
+                });
+            }
+        });
+
         List<WbsTreePrivateTableVO> list = new ArrayList<>();
         if (data != null && !data.isEmpty()) {
             Integer wbsType = data.get(0).getWbsType();
@@ -449,6 +517,80 @@ public class WbsTreePrivateController extends BladeController {
         return R.fail(200, "未查询到数据");
     }
 
+    /**
+     * 根据pkeyid检验html是否存在错误细腻些
+     * @param pKeyId
+     * @return
+     */
+    @GetMapping("/getHtmlErrorByPKeyId")
+    @ApiOperationSupport(order = 3)
+    @ApiOperation(value = "根据pkeyid检验html是否存在错误细腻些", notes = "传入pKeyId")
+    public R<Boolean> getHtmlErrorByPKeyId(Long pKeyId){
+        AtomicBoolean htmlError = new AtomicBoolean(false);
+
+        WbsTreePrivate f = wbsTreePrivateService.getById(pKeyId);
+        String htmlUrl = f.getHtmlUrl();
+        String initTableId = f.getInitTableId();
+        if(StringUtil.isNotBlank(htmlUrl) && StringUtils.isNotEmpty(initTableId)){
+            //获取元素表数据
+            List<WbsFormElement> wbsFormElements = wbsFormElementService.getBaseMapper().selectList(Wrappers.<WbsFormElement>lambdaQuery()
+                    .eq(WbsFormElement::getFId, initTableId)
+                    .eq(WbsFormElement::getIsDeleted, 0)
+            );
+
+            List<String> keys = wbsFormElements.stream().map(WbsFormElement::getEKey).collect(Collectors.toList());
+
+
+            // 读取html页面信息
+            String htmlString = "";
+            //解析html
+            try {
+                R excelHtml = excelTabController.getExcelHtml(f.getPKeyId());
+                htmlString = excelHtml.getData().toString();
+            } catch (Exception e) {
+            }
+            if(StringUtil.isEmpty(htmlString)){
+                return R.data(htmlError.get());
+            }
+            // 样式集合
+            Document doc = Jsoup.parse(htmlString);
+            //获取所有input标签
+            String[] tagNames = {"el-input", "el-date-picker", "el-time-picker", "hc-form-select-search",
+                    "hc-table-form-upload", "hc-form-checkbox-group", "el-radio-group", "el-select"};
+            Elements inputs = new Elements();
+            for (String tagName : tagNames) {
+                inputs.addAll(doc.select(tagName));
+            }
+            //判断标签是否存在id属性
+            inputs.forEach(input -> {
+                String id = input.attr("id");
+                if(StringUtils.isEmpty(id)){
+                    htmlError.set(true);
+                } else {
+                    /**
+                     * 判断当前元素是否符合格式
+                     * 1、是否存在key_
+                     * 2、是否存在__
+                     * 3、key_后面是否为数字
+                     * 4、__后面是否为坐标
+                     * 5、key是否在元素库中存在
+                     */
+                    if(!id.contains("key_")
+                            || !id.contains("__")
+                            || !StringUtils.isNumber(id.split("__")[0].replace("key_",""))
+                            || !id.split("__")[1].contains("_")
+                            || !StringUtils.isNumber(id.split("__")[1].split("_")[0])
+                            || !StringUtils.isNumber(id.split("__")[1].split("_")[1])
+                            || !keys.contains(id.split("__")[0])
+                    ){
+                        htmlError.set(true);
+                    }
+                }
+            });
+        }
+        return R.data(htmlError.get());
+    }
+
     @GetMapping("/remove-table")
     @ApiOperationSupport(order = 4)
     @ApiOperation(value = "删除节点下的元素表", notes = "传入表单id、wbsId、projectId")

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ArchiveTreeContractImpl.java

@@ -49,6 +49,11 @@ public class ArchiveTreeContractImpl implements ArchiveTreeContractClient {
         return this.archiveTreeContractService.getAuthCode(contractId);
     }
 
+    @Override
+    public List<ArchiveTreeContract> getArchiveTreeContractListByListOrderByTreeSort(List<Long> nodeIds) {
+        return this.archiveTreeContractService.getArchiveTreeContractListByListOrderByTreeSort(nodeIds);
+    }
+
     /**
      * 获取项目下存在未组卷文件的归档树节点
      *

+ 20 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ContractClientImpl.java

@@ -8,14 +8,18 @@ import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
 import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.ProjectInfo;
 import org.springblade.manager.mapper.SaveUserInfoByProjectMapper;
 import org.springblade.manager.service.IContractInfoService;
+import org.springblade.manager.service.IProjectInfoService;
 import org.springblade.manager.service.impl.SaveUserInfoByProjectServiceImpl;
 import org.springblade.manager.vo.RoleSignPfxUserVO3;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @RestController
 @AllArgsConstructor
@@ -24,6 +28,7 @@ public class ContractClientImpl implements ContractClient {
     private final IContractInfoService contractInfoService;
     private final SaveUserInfoByProjectMapper saveUserInfoByProjectMapper;
     private final SaveUserInfoByProjectServiceImpl saveUserInfoByProject;
+    private final IProjectInfoService projectInfoService;
 
     @Override
     public List<String> getProcessContractByJLContractId(String contractId) {
@@ -107,5 +112,20 @@ public class ContractClientImpl implements ContractClient {
         return contractInfoService.findAllUserAndRoleList(contractId,roleId);
     }
 
+    @Override
+    public List<ProjectInfo> queryProjectList(Set<String> projectIds) {
+        return projectInfoService.selectProjectList(projectIds);
+    }
+
+    @Override
+    public List<String> queryProjectIds(Set<String> projectId) {
+        return projectInfoService.queryProjectIds(projectId);
+    }
+
+    @Override
+    public List<ContractInfo> queryContractNamesListByProjectId(String projectId) {
+        return contractInfoService.queryContractNamesListByProjectId(projectId);
+    }
+
 
 }

+ 27 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/ExcelTabClientImpl.java

@@ -20,6 +20,7 @@ import org.springblade.business.feign.ContractLogClient;
 import org.springblade.business.feign.EntrustInfoServiceClient;
 import org.springblade.business.feign.InformationQueryClient;
 import org.springblade.common.utils.CommonUtil;
+import org.springblade.core.log.exception.ServiceException;
 import org.springblade.manager.util.DataStructureFormatUtils;
 import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.oss.model.BladeFile;
@@ -470,18 +471,37 @@ public class ExcelTabClientImpl implements ExcelTabClient {
     @Override
     public R saveReEntrustTabData(ReSigningEntrustDto dto, String header) {
         WbsTreePrivate wbsTreePrivate = jdbcTemplate.queryForObject("select * from m_wbs_tree_private where p_key_id=" + dto.getNodeId(), new BeanPropertyRowMapper<>(WbsTreePrivate.class));
+        if (wbsTreePrivate == null) {
+            throw new ServiceException("找不到节点信息");
+        }
+        // 合同段信息
+        ContractInfo contractInfo = contractInfoService.getById(dto.getContractId());
+        if (contractInfo == null) {
+            throw new ServiceException("合同段信息为null");
+        }
+        Long pkeyId = 0L;
+        if (contractInfo.getContractType() == 2) { //3 监理
+            pkeyId = wbsTreePrivate.getJlerTreeId() ;
+        } else if (contractInfo.getContractType() == 3 || contractInfo.getContractType() == 8) { //业主
+            pkeyId = wbsTreePrivate.getYzerTreeId();
+        } else {
+            return R.fail("该合同段没有委托单权限业务");
+        }
         Boolean isRemove=true;
-        List<Map<String, Object>> list = excelTabService.getBussDataInfoTrialentrust(Long.parseLong(dto.getEntrustId()), wbsTreePrivate.getJlerTreeId(), Long.parseLong(dto.getContractId()), null, null,isRemove);
+        List<Map<String, Object>> list = excelTabService.getBussDataInfoTrialentrust(Long.parseLong(dto.getEntrustId()), pkeyId, Long.parseLong(dto.getContractId()), null, null,isRemove);
         Map<String, Object> map = list.get(0);
         if(!map.containsKey("contractId")){
             map.put("contractId",dto.getContractId());
         }
         if(!map.containsKey("nodeErTreeId")){
-            map.put("nodeErTreeId",wbsTreePrivate.getJlerTreeId());
+            map.put("nodeErTreeId",pkeyId);
         }
         if(!map.containsKey("nodeId")){
             map.put("nodeId",dto.getNodeId());
         }
+        if(!map.containsKey("id")){
+            map.put("id",dto.getEntrustId());
+        }
         JSONObject jsonObject = new JSONObject(map);
         return  entrustInfoServiceClient.saventrustData(jsonObject);
     }
@@ -893,4 +913,9 @@ public class ExcelTabClientImpl implements ExcelTabClient {
         return R.success("成功");
     }
 
+    @Override
+    public R copeBussTab(Long pKeyId, String header) throws Exception {
+        return excelTabController.copeBussTab(pKeyId);
+    }
+
 }

+ 29 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/feign/SaveUserInfoByProjectClientImpl.java

@@ -9,6 +9,8 @@ import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
 import org.springblade.manager.entity.SaveUserInfoByProject;
 import org.springblade.manager.service.SaveUserInfoByProjectService;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.SingleColumnRowMapper;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
@@ -19,6 +21,7 @@ import java.util.List;
 public class SaveUserInfoByProjectClientImpl implements SaveUserInfoByProjectClient {
 
     private final SaveUserInfoByProjectService saveUserInfoByProjectService;
+    private final JdbcTemplate jdbcTemplate;
 
     @Override
     public List<JSONObject> queryUserContractRole(List<Long> userIds, String contractId) {
@@ -65,5 +68,31 @@ public class SaveUserInfoByProjectClientImpl implements SaveUserInfoByProjectCli
         saveUserInfoByProjectService.save(obj);
     }
 
+    @Override
+    public void insertUserInfoBatch(List<SaveUserInfoByProjectDTO> list) {
+        for (SaveUserInfoByProjectDTO dto : list) {
+            Long jlzyContractId = selectIsRecordJlId(dto.getContractId());
+            if(jlzyContractId!=null){
+                dto.setIsRecordJlId(jlzyContractId);
+            }
+        }
+        saveUserInfoByProjectService.saveBatch(list);
+    }
+
+    @Override
+    public void updateUserInfoBatch(List<SaveUserInfoByProjectDTO> updateUserInfoByProjectList) {
+        saveUserInfoByProjectService.updateBatchById(updateUserInfoByProjectList);
+    }
+
+    public Long selectIsRecordJlId(String contractId){
+        String sql="select contract_id_jlyz from m_contract_relation_jlyz where contract_id_sg="+contractId;
+        List<Long> list = jdbcTemplate.query(sql, new SingleColumnRowMapper<>(Long.class));
+        if(list.isEmpty()){
+            return null;
+        }else {
+            return list.get(0);
+        }
+    }
+
 
 }

+ 4 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/KeyMapper.java

@@ -46,6 +46,10 @@ public class KeyMapper {
      * 公式id
      */
     private Long formulaId;
+    /**
+     * 是否为辅助字段
+     */
+    private Integer assist;
 
     public String getCode() {
         return this.tableName + StringPool.COLON + this.field;

+ 11 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/SubTable.java

@@ -213,15 +213,16 @@ public class SubTable {
         if(this.design!=null) {
              designs = this.design.getValues().stream().map(ElementData::stringValue).collect(Collectors.toList());
         }
-        for(FormData item:this.mainList){
 
+
+        for(FormData item:this.mainList){
             String key = FormulaUtils.parseItemName(item.getEName()).trim();
             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){
+                if(this.getPlStr()!=null && this.getPlStr().indexOf(key1)>=0 && this.getPlStr().indexOf(key2)>=0){
                     System.out.println("123");
                 }else{
                     keyMap.put(key, "1");
@@ -230,6 +231,14 @@ public class SubTable {
                 keyMap.put(key, "1");
             }
         }
+        // 这里处理公式类型
+        if(this.group!=null && this.group.size()>0){
+            for (String key : this.group.keySet()){
+                if(keyMap.containsKey(key)){
+                    keyMap.remove(key);
+                }
+            }
+        }
 
         // 计算元素是否需要覆盖
         List<Object> data = this.data.getRawValue();

+ 3 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.java

@@ -34,6 +34,7 @@ import org.springblade.manager.vo.*;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.system.entity.DictBiz;
+import org.springframework.web.bind.annotation.RequestBody;
 
 import java.util.List;
 import java.util.Map;
@@ -190,4 +191,6 @@ public interface ArchiveTreeContractMapper extends BaseMapper<ArchiveTreeContrac
 
     Integer selectExtTypeByParentId(@Param("projectId")Long projectId,
                                     @Param("parentId") Long parentId);
+
+    List<ArchiveTreeContract> getArchiveTreeContractListByListOrderByTreeSort(@RequestBody List<Long> nodeIds);
 }

+ 13 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ArchiveTreeContractMapper.xml

@@ -1028,4 +1028,17 @@
     <select id="selectExtTypeByParentId" resultType="java.lang.Integer">
         select count(0) from m_archive_tree_contract where project_id = #{projectId} and ext_type = 2 and is_deleted = 0 and FIND_IN_SET(#{parentId},ancestors)
     </select>
+    <select id="getArchiveTreeContractListByListOrderByTreeSort"
+            resultType="org.springblade.manager.entity.ArchiveTreeContract">
+        SELECT
+        d.*
+        FROM
+        m_archive_tree_contract d
+        WHERE
+        d.is_deleted = 0 and d.id in
+        <foreach item="id" collection="nodeIds" open="(" close=")" separator=",">
+            #{id}
+        </foreach>
+        ORDER BY case when d.tree_sort regexp '^[a-zA-Z]' then 0 when d.tree_sort regexp '^[0-9]' then 1 else 2 end, d.tree_sort
+    </select>
 </mapper>

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.java

@@ -77,4 +77,6 @@ public interface ContractInfoMapper extends BaseMapper<ContractInfo> {
     List<RoleSignPfxUserVO1> findAllUserAndRoleList1(Long contractId, Long roleId);
 
     List<RoleSignPfxUserVO2> findAllUserAndRoleList2(Long contractId, Long roleId);
+
+    List<ContractInfo> queryContractNamesListByProjectId(@Param("projectId") String projectId);
 }

+ 10 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ContractInfoMapper.xml

@@ -585,5 +585,15 @@
             And r.id=#{roleId}
         </if>
     </select>
+    <select id="queryContractNamesListByProjectId" resultType="org.springblade.manager.entity.ContractInfo">
+        SELECT
+        *
+        FROM
+        m_contract_info
+        WHERE
+        is_deleted = 0
+        AND p_id = #{projectId}
+    </select>
+
 
 </mapper>

+ 26 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/InformationImportRecordMapper.java

@@ -0,0 +1,26 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.manager.entity.InformationImportRecord;
+
+public interface InformationImportRecordMapper extends BaseMapper<InformationImportRecord> {
+
+
+}

+ 3 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ProjectInfoMapper.java

@@ -10,6 +10,7 @@ import org.springblade.manager.vo.ProjectUserAmountVO;
 import org.springblade.manager.vo.SingPfxManagementVO;
 
 import java.util.List;
+import java.util.Set;
 
 public interface ProjectInfoMapper extends BaseMapper<ProjectInfo> {
 
@@ -32,4 +33,6 @@ public interface ProjectInfoMapper extends BaseMapper<ProjectInfo> {
     ProjectInfo selectOneAndWbsTypeById(Long id);
 
     IPage<ProjectInfoVO> pageList(IPage<ProjectInfoVO> page, @Param("vo") ProjectInfoVO3 vo);
+
+    List<String> queryProjectIds(@Param("projectIds") Set<String> projectIds);
 }

+ 9 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/ProjectInfoMapper.xml

@@ -191,5 +191,14 @@
             </when>
         </choose>
     </select>
+    <select id="queryProjectIds" resultType="java.lang.String">
+        select id from m_project_info where is_deleted = 0
+        <if test="projectIds != null">
+            and id in
+            <foreach item="projectId" collection="projectIds" open="(" separator="," close=")">
+                #{projectId}
+            </foreach>
+        </if>
+    </select>
 
 </mapper>

+ 3 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreeMapper.xml

@@ -407,6 +407,7 @@
     <select id="selectFormElements" resultMap="wbsFormElementMap">
         select f_id,
                e_key,
+               a.assist,
                a.id,
                a.dynamic_dict,
                e_name,
@@ -425,6 +426,7 @@
         union
         select f_id,
                e_key,
+               a.assist,
                a.id,
                a.dynamic_dict,
                e_name,
@@ -445,6 +447,7 @@
         select b.id
              , IF(c.formula_id > 0, 1, 0)           globalFormula
              , b.e_name
+             , b.assist
              , e_type
              , e_length
              , e_allow_deviation

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/mapper/WbsTreePrivateMapper.xml

@@ -104,6 +104,7 @@
         <result column="fillRate" property="fillRate"/>
         <result column="initTableId" property="initTableId"/>
         <result column="initTableName" property="initTableName"/>
+        <result column="html_url" property="htmlUrl"/>
         <result column="excelIds" property="excelIds"/>
         <result column="excelId" property="excelId"/>
         <result column="table_type" property="tableType"/>

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IArchiveTreeContractService.java

@@ -147,4 +147,6 @@ public interface IArchiveTreeContractService extends BaseService<ArchiveTreeCont
     Long getNodeIdByName(String projectName, String contractName, String nodeName);
 
     boolean deleteTreeEx(Long id);
+
+    List<ArchiveTreeContract> getArchiveTreeContractListByListOrderByTreeSort(List<Long> nodeIds);
 }

+ 2 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IContractInfoService.java

@@ -91,4 +91,6 @@ public interface IContractInfoService extends BaseService<ContractInfo> {
 
     R<Object> getCollectTreeNode(String contractId, String tableOwner, Long folderId, String queryValue, Long pKeyId);
     R<Object> getCollectTreeNodeByQuery(String contractId, String tableOwner, Long folderId, String queryValue);
+
+    List<ContractInfo> queryContractNamesListByProjectId(String projectId);
 }

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

@@ -228,4 +228,6 @@ public interface IExcelTabService extends BaseService<ExcelTab> {
     ExcelTabVO templateDetail(Long id);
 
     void setAutomatic(Long pkeyId, String string, Document doc);
+
+    StringBuilder buildMTableInsertSql(String tabName, Map<String, String> dataMap2, Object id, Object groupId, Object pKeyId);
 }

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IProjectInfoService.java

@@ -6,6 +6,7 @@ import org.springblade.core.mp.base.BaseService;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 
 import java.util.List;
+import java.util.Set;
 
 public interface IProjectInfoService extends BaseService<ProjectInfo> {
 
@@ -15,6 +16,8 @@ public interface IProjectInfoService extends BaseService<ProjectInfo> {
 
     List<ProjectInfo> selectProjectList(List<String> projectIds);
 
+    List<ProjectInfo> selectProjectList(Set<String> projectIds);
+
     IPage<ProjectInfoVO> selectProjectInfoPage(IPage<ProjectInfoVO> page, ProjectInfoVO projectInfo);
 
     List<ContractlnfoCountVO> selectContractInfoCount();
@@ -28,4 +31,6 @@ public interface IProjectInfoService extends BaseService<ProjectInfo> {
     Long getProjectIdbyName(String projectName);
 
     IPage<ProjectInfoVO> pageList(IPage<ProjectInfoVO> page, ProjectInfoVO3 vo);
+
+    List<String> queryProjectIds(Set<String> projectId);
 }

+ 30 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/InformationImportRecordService.java

@@ -0,0 +1,30 @@
+/*
+ *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions are met:
+ *
+ *  Redistributions of source code must retain the above copyright notice,
+ *  this list of conditions and the following disclaimer.
+ *  Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in the
+ *  documentation and/or other materials provided with the distribution.
+ *  Neither the name of the dreamlu.net developer nor the names of its
+ *  contributors may be used to endorse or promote products derived from
+ *  this software without specific prior written permission.
+ *  Author: Chill 庄骞 (smallchill@163.com)
+ */
+package org.springblade.manager.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.springblade.manager.entity.InformationImportRecord;
+
+import java.time.LocalDateTime;
+
+
+public interface InformationImportRecordService extends IService<InformationImportRecord> {
+
+    Boolean updateProcess(Long id, Integer process, Integer status, String remake, LocalDateTime updateTime);
+
+
+}

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ArchiveTreeContractServiceImpl.java

@@ -800,6 +800,11 @@ public class ArchiveTreeContractServiceImpl extends BaseServiceImpl<ArchiveTreeC
 		return true;
 	}
 
+	@Override
+	public List<ArchiveTreeContract> getArchiveTreeContractListByListOrderByTreeSort(List<Long> nodeIds) {
+		return baseMapper.getArchiveTreeContractListByListOrderByTreeSort(nodeIds);
+	}
+
 
 	/**
 	 * 批量更新

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ContractInfoServiceImpl.java

@@ -1849,4 +1849,9 @@ public class ContractInfoServiceImpl extends BaseServiceImpl<ContractInfoMapper,
         }
         return null;
     }
+
+    @Override
+    public List<ContractInfo> queryContractNamesListByProjectId(String projectId) {
+        return contractInfoMapper.queryContractNamesListByProjectId(projectId);
+    }
 }

+ 25 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -239,6 +239,14 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
             tenantId = StringPool.EMPTY;
         }
         List<ExceTabTreVO> ls = baseMapper.tabLazyTree(tenantId, modeId, parentId + "");
+        if(ls.stream().anyMatch(f -> f.getFileType() == 3)){
+            Comparator<ExceTabTreVO> safeComparator = Comparator
+                    .comparing(ExceTabTreVO::getSort, Comparator.nullsFirst(Comparator.naturalOrder()))
+                    .thenComparing(new ComplexStringComparator<>(obj ->
+                            obj.getName() != null ? obj.getName() : ""));
+            //对结果进行排序
+            ls.sort(safeComparator);
+        }
         return ForestNodeMerger.merge(ls);
     }
 
@@ -705,6 +713,14 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                             htmlElementCellMap.put(e.getHtmlUrl(), map);
                         }
                         Map<String, String> elementCell = map;
+
+                        //扩展中间元素开发 获取字段是辅助字段  并且在html上没有这个字段
+                        List<String> collect = keyMappers.stream().filter(f -> f.getAssist() == 1
+                                && elementCell.get(f.getField()) == null).map(KeyMapper::getField).collect(Collectors.toList());
+                        collect.forEach(f->{
+                            elementCell.put(f,"0_0");
+                        });
+
                         coordinateMap.computeIfAbsent(e.getInitTableName(), k -> elementCell);
                     });
                 }
@@ -826,6 +842,15 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
         if(ObjectUtil.isNotEmpty(ids)){
             List<ContractLog> contractLogList = jdbcTemplate.query("select * from u_contract_log where id in (" + ids + ")", new BeanPropertyRowMapper<>(ContractLog.class));
             ContractLog contractLog = contractLogList.get(0);
+            //将原来的OSS文件删除
+            if(contractLog!=null&&StringUtils.isNotEmpty(contractLog.getPdfUrl())){
+                String pdfUrlResult = contractLog.getPdfUrl();
+                int lastIndex = pdfUrlResult.lastIndexOf("upload");
+                if (lastIndex != -1) {
+                    String fileName = pdfUrlResult.substring(lastIndex);
+                    this.newIOSSClient.removeFile(fileName);
+                }
+            }
             String oldDataId1 = contractLog.getOldDataId();
             if (ObjectUtil.isNotEmpty(oldDataId1)){
                 oldDataId = oldDataId1+","+ids;

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

@@ -5391,7 +5391,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     private List<Map<String, Object>> listMap(List<Long> ids, ExecuteType type) {
         String pkIds = ids.stream().map(String::valueOf).collect(Collectors.joining(","));
         String tableName = ExecuteType.INSPECTION.equals(type) ? "m_wbs_tree_contract" : "m_wbs_tree_private";
-        return this.jdbcTemplate.queryForList(" select a.init_table_name as tableName,a.p_key_id as pkId ,c.e_key as field,c.e_name as eName,c.id  as fieldId ,c.e_type eType , e_allow_deviation as eAllowDeviation  " +
+        return this.jdbcTemplate.queryForList(" select a.init_table_name as tableName,a.p_key_id as pkId ,c.e_key as field,c.e_name as eName,c.id  as fieldId ,c.e_type eType, c.assist , e_allow_deviation as eAllowDeviation  " +
                 "from " + tableName + " a " +
                 "inner join m_table_info b on a.init_table_name=b.tab_en_name " +
                 "INNER JOIN m_wbs_form_element c on b.id=c.f_id " +

+ 27 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/InformationImportRecordServiceImpl.java

@@ -0,0 +1,27 @@
+package org.springblade.manager.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springblade.manager.entity.InformationImportRecord;
+import org.springblade.manager.mapper.InformationImportRecordMapper;
+import org.springblade.manager.service.InformationImportRecordService;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+@Service
+public class InformationImportRecordServiceImpl extends ServiceImpl<InformationImportRecordMapper, InformationImportRecord> implements InformationImportRecordService {
+    @Override
+    public Boolean updateProcess(Long id, Integer process, Integer status, String remake, LocalDateTime updateTime) {
+        if (id == null || (process == null && status == null)) {
+            return false;
+        }
+        if (process != null && process < 100) {
+            process = (int) (Math.random() * 9 + process - 5);
+        }
+        return this.update(Wrappers.<InformationImportRecord>lambdaUpdate().eq(InformationImportRecord::getId, id).eq(InformationImportRecord::getUpdateTime, updateTime)
+                .set(process != null, InformationImportRecord::getProcess, process)
+                .set(status != null, InformationImportRecord::getStatus, status)
+                .set(remake != null, InformationImportRecord::getRemark, remake));
+    }
+}

+ 3 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ProfilerOffsetServiceImpl.java

@@ -215,7 +215,9 @@ public class ProfilerOffsetServiceImpl extends ServiceImpl<ProfilerOffsetMapper,
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    @Scheduled(cron = "0 0 23 ? * FRI")
+//    @Scheduled(cron = "0 0 23 ? * FRI")
+    //每周一18:30
+    @Scheduled(cron = "0 30 18 ? * MON")
     public void push() {
         String url = "/data/openapi/v1/push";
 

Some files were not shown because too many files changed in this diff