cr 3 veckor sedan
förälder
incheckning
7890f6772b

+ 89 - 9
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java

@@ -7,28 +7,108 @@ import org.springblade.archive.entity.ScanFolder;
 import org.springblade.archive.service.ScanFileService;
 import org.springblade.archive.service.ScanFolderService;
 import org.springblade.core.tool.api.R;
+import org.springblade.manager.entity.ContractInfo;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
 @RestController
 @AllArgsConstructor
 @RequestMapping("/scanFile")
 @Api(value = "扫描文件接口", tags = "扫描文件接口")
 public class ScanFileController {
     private final ScanFileService scanFileService;
-    private final ScanFolderService scanFolderService;
+    private final JdbcTemplate jdbcTemplate;
+
+    // 线程池
+    @Resource(name = "taskExecutor1")
+    private ThreadPoolExecutor executor;
 
 
-    @GetMapping("/scanAndSaveFolder")
-    @ApiOperation("开始扫描文件夹")
-    public R scanAndSaveFolder(Long contractId, Long projectId){
-        return scanFileService.scanAndSave(contractId, projectId);
+    @GetMapping("/startOrEndScan")
+    public R startOrEndScan(Long contractId,Integer type){
+        String sql="update m_contract_info set is_scan="+type+" where id="+contractId;
+        jdbcTemplate.update(sql);
+        return R.success("操作成功"+ (type==1?",正在扫描中":",已结束扫描"));
     }
-    @GetMapping("/scanAndSaveFiles")
-    @ApiOperation("开始扫描文件")
-    public R scanANdSavefFile(Long contractId,Long projectId){
-      return  scanFileService.scanAndSaveFiles(contractId,projectId);
+
+
+    public void scan (){
+//        String sql="select * from m_contract_info where is_scan=1 and is_deleted=0";
+//        List<ContractInfo> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ContractInfo.class));
+//        if(!list.isEmpty()){
+//            for (ContractInfo contractInfo : list){
+//                CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> {
+//                    try {
+//                        /*===============执行批量任务===============*/
+//                        scanFileService.scanAndSave(contractInfo.getId(), Long.parseLong(contractInfo.getPId()));
+//                    } catch (Exception e) {
+//                        e.printStackTrace();
+//                    }
+//                }, executor);
+//            }
+//        }
+        String sql = "select * from m_contract_info where is_scan=1 and is_deleted=0";
+        List<ContractInfo> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(ContractInfo.class));
+        if (!list.isEmpty()) {
+            // 创建线程池
+            int threadCount = Math.min(list.size(), Runtime.getRuntime().availableProcessors() * 2);
+            ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
+            try {
+                // 存储Future对象以获取执行结果
+                List<Future<?>> futures = new ArrayList<>();
+                // 提交任务到线程池
+                for (ContractInfo contractInfo : list) {
+                    Future<?> future = executorService.submit(() -> {
+                        try {
+                            scanFileService.scanAndSave(contractInfo.getId(), Long.parseLong(contractInfo.getPId()));
+                            System.out.println("合同ID " + contractInfo.getId() + " 扫描完成");
+                        } catch (Exception e) {
+                            System.err.println("处理合同ID " + contractInfo.getId() + " 时出错: " + e.getMessage());
+                            throw e; // 重新抛出异常以便Future能捕获
+                        }
+                    });
+                    futures.add(future);
+                }
+                // 关闭线程池
+                executorService.shutdown();
+                // 等待所有任务完成并检查结果
+                for (int i = 0; i < futures.size(); i++) {
+                    try {
+                        futures.get(i).get(); // 这会阻塞直到任务完成
+                        System.out.println("任务 " + i + " 已成功完成");
+                    } catch (Exception e) {
+                        System.err.println("任务 " + i + " 执行失败: " + e.getMessage());
+                    }
+                }
+                // 等待线程池完全终止
+                if (executorService.awaitTermination(30, TimeUnit.MINUTES)) {
+                    System.out.println("所有扫描任务已完成");
+                } else {
+                    System.err.println("部分扫描任务超时未完成");
+                    executorService.shutdownNow();
+                }
+
+            } catch (InterruptedException e) {
+                System.err.println("线程执行被中断: " + e.getMessage());
+                executorService.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
     }
 }

+ 7 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFileMapper.java

@@ -4,9 +4,16 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import feign.Param;
 import org.springblade.archive.entity.ScanFile;
 
+import java.util.List;
+
 public interface ScanFileMapper extends BaseMapper<ScanFile> {
 
     Integer exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("fileName") String fileName);
 
     int insert(ScanFile scanFile);
+
+    Integer selectMaxDigitalNum(@Param("contractId") Long contractId, @Param("projectId") Long projectId);
+
+    Integer selectMaxSort(@Param("contractId") Long contractId, @Param("projectId") Long projectId, @Param("folderId") Long folderId);
+
 }

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

@@ -1,9 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.springblade.archive.mapper.ScanFileMapper">
-
-
     <select id="exists" resultType="java.lang.Integer">
         select COUNT(*) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}  AND file_name = #{fileName}
     </select>
+    <select id="selectMaxDigitalNum" resultType="java.lang.Integer">
+        select MAX(digital_num) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId}
+    </select>
+    <select id="selectMaxSort" resultType="java.lang.Integer">
+        select MAX(sort) FROM scan_file WHERE project_id = #{projectId} AND contract_id = #{contractId} And folder_id = #{folderId}
+    </select>
 </mapper>

+ 5 - 1
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java

@@ -6,7 +6,11 @@ import org.springblade.core.tool.api.R;
 
 
 public interface ScanFileService extends IService<ScanFile> {
-    R scanAndSave(Long contractId, Long projectId);
+    R scanAndSaveFolder(Long contractId, Long projectId);
 
     R scanAndSaveFiles(Long contractId, Long projectId);
+
+    void sortNumber(Long contractId, Long projectId);
+
+    void scanAndSave(Long contractId,Long projectId);
 }

+ 1 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFolderService.java

@@ -1,4 +1,5 @@
 package org.springblade.archive.service;
 
 public interface ScanFolderService {
+
 }

+ 107 - 13
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java

@@ -1,5 +1,6 @@
 package org.springblade.archive.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.AllArgsConstructor;
 import org.springblade.archive.entity.ScanFile;
@@ -15,11 +16,18 @@ import org.springblade.resource.feign.NewIOSSClient;
 import org.springframework.stereotype.Service;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 
 @Service
@@ -34,12 +42,24 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
     private final ScanFileMapper scanFileMapper;
     private final NewIOSSClient newIOSSClient;
 
+
+
+    @Override
+    public void scanAndSave(Long contractId, Long projectId) {
+        //扫描文件夹
+        scanAndSaveFolder(contractId, projectId);
+        //扫描文件
+        scanAndSaveFiles(contractId,projectId);
+        //整理编号,序号
+        sortNumber(contractId, projectId);
+    }
+
     /**
      * 入口方法:扫描并入库指定contractId的所有文件夹
      * @param contractId 传入的合同ID(对应D:\PDF下的文件夹名)
      * @param projectId 传入的项目ID
      */
-    public R scanAndSave(Long contractId, Long projectId) {
+    public R scanAndSaveFolder(Long contractId, Long projectId) {
         // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
         String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
         File contractFolder = new File(contractFolderPath);
@@ -161,6 +181,30 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
         return R.success("扫描并入库完成");
     }
 
+    @Override
+    public void sortNumber(Long contractId, Long projectId) {
+        List<ScanFile> scanFiles = scanFileMapper.selectList(new LambdaQueryWrapper<ScanFile>().eq(ScanFile::getContractId, contractId).eq(ScanFile::getProjectId, projectId).isNull(ScanFile::getSort).isNull(ScanFile::getDigitalNum).orderByDesc(ScanFile::getFileDate));
+        Integer maxDigitalNum=scanFileMapper.selectMaxDigitalNum(contractId,projectId);
+        if(maxDigitalNum==null||maxDigitalNum<1){
+            maxDigitalNum=0;
+        }
+        Map<Long, List<ScanFile>> scanFilesMap = scanFiles.stream()
+                .collect(Collectors.groupingBy(ScanFile::getFolderId));
+        for (Map.Entry<Long, List<ScanFile>> entry : scanFilesMap.entrySet()) {
+            Integer MaxSort=scanFileMapper.selectMaxSort(contractId,projectId,entry.getKey());
+            if(MaxSort==null||MaxSort<1){
+                MaxSort=0;
+            }
+            List<ScanFile> list = entry.getValue();
+            for (ScanFile scanFile : list) {
+                scanFile.setSort(++MaxSort);
+                scanFile.setDigitalNum(++maxDigitalNum);
+                scanFileMapper.updateById(scanFile);
+            }
+        }
+    }
+
+
     /**
      * 递归扫描文件夹中的所有文件并入库
      * @param currentFolder 当前扫描的文件夹
@@ -189,6 +233,7 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
      * 处理单个文件:获取信息并入库
      */
     private void processFile(File file, File parentFolder, Long projectId, Long contractId) {
+        String fileMD5 = calculateFileMD5(file);
         try {
             // 获取文件创建时间
             BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
@@ -215,6 +260,10 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
             if(bladeFile==null){
                 return;
             }
+            String oSSFileMD5 = calculateOSSFileMD5(bladeFile.getLink());
+            if (fileMD5 != null && !fileMD5.equals(oSSFileMD5)) {
+                return;
+            }
             String pdfNum = FileUtils.getPdfNum(bladeFile.getLink());
             // 生成随机ID
             Long fileId = SnowFlakeUtil.getId();
@@ -256,28 +305,20 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
                         }
                         // 复制文件到备份目录
                         Files.copy(file.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
-                        System.out.println("文件备份成功:" + backupFilePath);
-                        // 备份成功后删除源文件
-                        boolean deleted = file.delete();
-                        if (deleted) {
-                            System.out.println("源文件已删除:" + filePath);
-                        } else {
-                            System.err.println("警告:备份成功,但无法删除源文件:" + filePath);
+                        String fileMD5New = calculateFileMD5(backupFile);
+                        if (fileMD5New != null && fileMD5New.equals(fileMD5)) {
+                            // 备份成功后删除源文件
+                            file.delete();
                         }
-                    } else {
-                        System.err.println("警告:文件路径不在预期的根目录下,无法备份:" + filePath);
                     }
                 } catch (IOException e) {
                     System.err.println("文件备份失败:" + filePath + ",错误信息:" + e.getMessage());
                 }
-            } else {
-                System.err.println("文件入库失败:" + filePath);
             }
         } catch (IOException e) {
             System.err.println("处理文件时出错:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
         }
     }
-
     private Boolean fileExists(Long projectId, Long contractId, String folderName){
         Integer i = scanFileMapper.exists(projectId, contractId, folderName);
         if(i==1){
@@ -286,4 +327,57 @@ public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile>
             return false;
         }
     }
+
+    private String calculateFileMD5(File file) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            try (InputStream is = Files.newInputStream(file.toPath());
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算本地文件MD5失败:" + file.getAbsolutePath() + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 计算OSS文件的MD5值
+     * @param ossUrl OSS文件路径
+     * @return MD5值,如果出错返回null
+     */
+    private String calculateOSSFileMD5(String ossUrl) {
+        try {
+            MessageDigest md = MessageDigest.getInstance("MD5");
+
+            // 使用URL下载OSS文件
+            URL url = new URL(ossUrl);
+            try (InputStream is = url.openStream();
+                 DigestInputStream dis = new DigestInputStream(is, md)) {
+                byte[] buffer = new byte[8192];
+                while (dis.read(buffer) != -1) {
+                    // 读取文件内容以计算MD5
+                }
+            }
+
+            byte[] digest = md.digest();
+            StringBuilder sb = new StringBuilder();
+            for (byte b : digest) {
+                sb.append(String.format("%02x", b));
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            System.err.println("计算OSS文件MD5失败:" + ossUrl + ",错误信息:" + e.getMessage());
+            return null;
+        }
+    }
 }