Преглед на файлове

Merge branch 'refs/heads/feature-lihb-20251110-four' into dev

LHB преди 18 часа
родител
ревизия
9e79ecbfa4
променени са 15 файла, в които са добавени 1546 реда и са изтрити 153 реда
  1. 20 0
      blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/vo/ArchiveExaminingVo.java
  2. 4 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/entity/ArchiveFile.java
  3. 4 0
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/MetadataClassificationClient.java
  4. 5 0
      blade-service/blade-archive/pom.xml
  5. 14 9
      blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ArchiveExaminingReportController.java
  6. 534 144
      blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ArchiveExaminingReportImpl.java
  7. 49 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/ClamAVClientScanner.java
  8. 97 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileAnalysisResult.java
  9. 352 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileFormatSecurityChecker.java
  10. 33 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileExtension.java
  11. 306 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileMD5Calculator.java
  12. 114 0
      blade-service/blade-archive/src/main/java/org/springblade/archive/utils/RemoteFileSecurityChecker.java
  13. 7 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/MetadataClassificationClientImpl.java
  14. 3 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/IMetadataClassificationService.java
  15. 4 0
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/MetadataClassificationServiceImpl.java

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

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

@@ -342,6 +342,10 @@ public class ArchiveFile extends BaseEntity {
     @ApiModelProperty("是否锁定 1已锁定")
     private Integer isLock;
 
+
+    private String fileMd5;
+    private String pdfMd5;
+
     public void fromExternal(ArchiveFileVo vo) {
         if (vo == null) {
             return;

+ 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);
 }

+ 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秒检测当前报告状态然后推送

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

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

+ 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);
+    }
 }

+ 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);
 }

+ 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);
+    }
 }