Ver Fonte

扫描文件

cr há 3 semanas atrás
pai
commit
4429f453b8

+ 7 - 2
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFile.java

@@ -7,6 +7,7 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.omg.CORBA.PRIVATE_MEMBER;
 
 @TableName(value = "scan_file")
 @Data
@@ -29,13 +30,17 @@ public class ScanFile {
     @ApiModelProperty(value = "数字编号")
     private Integer digitalNum;
     @ApiModelProperty(value = "文件编号")
-    private Integer fileNum;
+    private String fileNum;
     @ApiModelProperty(value = "文件题名")
     private String fileName;
     @ApiModelProperty(value = "文件页数")
-    private Integer fileSize;
+    private String fileSize;
     @ApiModelProperty(value = "文件日期")
     private String fileDate;
+    @ApiModelProperty(value = "文件路径")
+    private String filePath;
+    @ApiModelProperty(value = "OSS路径")
+    private String ossUrl;
     @ApiModelProperty(value = "负责人")
     private String responsible;
     @ApiModelProperty(value = "是否删除")

+ 2 - 0
blade-service-api/blade-archive-api/src/main/java/org/springblade/archive/entity/ScanFolder.java

@@ -28,6 +28,8 @@ public class ScanFolder {
     private Long contractId;
     @ApiModelProperty(value = "文件夹名称")
     private String folderName;
+    @ApiModelProperty(value = "文件夹路径")
+    private String folderPath;
     @ApiModelProperty(value = "是否删除")
     private Integer isDeleted;
 }

+ 13 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/controller/ScanFileController.java

@@ -1,11 +1,14 @@
 package org.springblade.archive.controller;
 
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 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.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -18,4 +21,14 @@ public class ScanFileController {
     private final ScanFolderService scanFolderService;
 
 
+    @GetMapping("/scanAndSaveFolder")
+    @ApiOperation("开始扫描文件夹")
+    public R scanAndSaveFolder(Long contractId, Long projectId){
+        return scanFileService.scanAndSave(contractId, projectId);
+    }
+    @GetMapping("/scanAndSaveFiles")
+    @ApiOperation("开始扫描文件")
+    public R scanANdSavefFile(Long contractId,Long projectId){
+      return  scanFileService.scanAndSaveFiles(contractId,projectId);
+    }
 }

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

@@ -1,7 +1,12 @@
 package org.springblade.archive.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import feign.Param;
 import org.springblade.archive.entity.ScanFile;
 
 public interface ScanFileMapper extends BaseMapper<ScanFile> {
+
+    Integer exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("fileName") String fileName);
+
+    int insert(ScanFile scanFile);
 }

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

@@ -0,0 +1,9 @@
+<?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>
+</mapper>

+ 8 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.java

@@ -1,7 +1,15 @@
 package org.springblade.archive.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
 import org.springblade.archive.entity.ScanFolder;
 
 public interface ScanFolderMapper extends BaseMapper<ScanFolder> {
+
+    int exists(@Param("projectId") Long projectId, @Param("contractId") Long contractId, @Param("folderName") String folderName);
+
+    int insert(ScanFolder scanFolder);
+
+
+    Long getId(@Param("folderName") String folderName, @Param("contractId") Long contractId, @Param("projectId") Long projectId);
 }

+ 12 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/mapper/ScanFolderMapper.xml

@@ -0,0 +1,12 @@
+<?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.ScanFolderMapper">
+
+
+    <select id="exists" resultType="java.lang.Integer">
+        select COUNT(*) FROM scan_folder WHERE project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+    <select id="getId" resultType="java.lang.Long">
+        select id from scan_folder where project_id = #{projectId} AND contract_id = #{contractId} AND folder_name = #{folderName}
+    </select>
+</mapper>

+ 4 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/service/ScanFileService.java

@@ -2,7 +2,11 @@ package org.springblade.archive.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springblade.archive.entity.ScanFile;
+import org.springblade.core.tool.api.R;
 
 
 public interface ScanFileService extends IService<ScanFile> {
+    R scanAndSave(Long contractId, Long projectId);
+
+    R scanAndSaveFiles(Long contractId, Long projectId);
 }

+ 276 - 1
blade-service/blade-archive/src/main/java/org/springblade/archive/service/impl/ScanFileServiceImpl.java

@@ -1,14 +1,289 @@
 package org.springblade.archive.service.impl;
 
-
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.AllArgsConstructor;
 import org.springblade.archive.entity.ScanFile;
+import org.springblade.archive.entity.ScanFolder;
 import org.springblade.archive.mapper.ScanFileMapper;
+import org.springblade.archive.mapper.ScanFolderMapper;
 import org.springblade.archive.service.ScanFileService;
+import org.springblade.archive.utils.FileUtils;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.oss.model.BladeFile;
+import org.springblade.core.tool.api.R;
+import org.springblade.resource.feign.NewIOSSClient;
 import org.springframework.stereotype.Service;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
 
 @Service
 @AllArgsConstructor
 public class ScanFileServiceImpl  extends ServiceImpl<ScanFileMapper, ScanFile> implements ScanFileService {
+    //文件根目录
+    private static final String ROOT_PREFIX = "D:" + File.separator + "ScanPDF";
+    //备份文件根目录
+    private static final String ROOT_PREFIX_back = "D:" + File.separator + "ScanBackPDF";
+
+    private final ScanFolderMapper scanFolderMapper;
+    private final ScanFileMapper scanFileMapper;
+    private final NewIOSSClient newIOSSClient;
+
+    /**
+     * 入口方法:扫描并入库指定contractId的所有文件夹
+     * @param contractId 传入的合同ID(对应D:\PDF下的文件夹名)
+     * @param projectId 传入的项目ID
+     */
+    public R scanAndSave(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        // 2. 扫描一级文件夹(parent_id=0)并入库,同时递归子文件夹
+        File[] firstLevelFolders = contractFolder.listFiles(File::isDirectory);
+        if (firstLevelFolders == null || firstLevelFolders.length == 0) {
+            return R.fail("提示:合同ID=" + contractId + "的根文件夹下无任何子文件夹");
+        }
+
+        // 遍历一级文件夹,逐个入库并递归子目录
+        for (File firstLevelFolder : firstLevelFolders) {
+            String folderName = firstLevelFolder.getName();
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if(folderId==null){
+                folderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = firstLevelFolder.getAbsolutePath();
+            // 创建一级目录的ScanFolder对象(parent_id=0)
+            ScanFolder firstLevelScanFolder = new ScanFolder(
+                    folderId,
+                    0L,          // 一级目录父ID固定为0
+                    projectId,
+                    contractId,
+                    folderName,
+                    filePath,
+                    0
+            );
+                 // 入库一级目录
+                 checkInsertScanFolder(firstLevelScanFolder);
+                // 递归扫描当前一级目录下的所有子文件夹(子目录的父ID=当前一级目录ID)
+                recursiveScanSubFolders(firstLevelFolder, folderId, projectId, contractId);
+
+        }
+        return R.success("扫描并入库成功");
+    }
+
+    /**
+     * 递归扫描子文件夹并入库
+     * @param parentFolder 父文件夹对象(当前要扫描子目录的文件夹)
+     * @param parentFolderId 父文件夹在数据库中的ID(子目录的parent_id)
+     * @param projectId 项目ID(透传)
+     * @param contractId 合同ID(透传)
+     */
+    private void recursiveScanSubFolders(File parentFolder, Long parentFolderId, Long projectId, Long contractId) {
+        // 获取父文件夹下的所有子文件夹
+        File[] subFolders = parentFolder.listFiles(File::isDirectory);
+        if (subFolders == null || subFolders.length == 0) {
+            System.out.println("递归终止:父ID=" + parentFolderId + "(名称=" + parentFolder.getName() + ")下无子文件夹");
+            return;
+        }
+
+        // 遍历每个子文件夹,入库并继续递归其下的子目录
+        for (File subFolder : subFolders) {
+            String subFolderName = subFolder.getName();
+            Long subFolderId = scanFolderMapper.getId(subFolderName,contractId,projectId);
+            if(subFolderId==null){
+                subFolderId=SnowFlakeUtil.getId(); // 生成随机ID
+            }
+            String filePath = subFolder.getAbsolutePath();
+
+            // 创建子目录的ScanFolder对象(parent_id=父文件夹ID)
+            ScanFolder subScanFolder = new ScanFolder(
+                    subFolderId,
+                    parentFolderId, // 关联父目录ID
+                    projectId,
+                    contractId,
+                    subFolderName,
+                    filePath,
+                    0
+            );
+                // 入库子目录
+                 checkInsertScanFolder(subScanFolder);
+                // 递归扫描当前子目录下的子文件夹
+                recursiveScanSubFolders(subFolder, subFolderId, projectId, contractId);
+        }
+    }
+
+    /**
+     * 检查文件夹是否存在,不存在则新增
+     * @param scanFolder
+     * @return
+     */
+    public boolean checkInsertScanFolder(ScanFolder scanFolder) {
+        int result = scanFolderMapper.exists(scanFolder.getProjectId(), scanFolder.getContractId(), scanFolder.getFolderName());
+        if(result>0){
+            return false;
+        }else {
+            scanFolderMapper.insert(scanFolder);
+        }
+        return true;
+    }
+
+
+    /**
+     * 扫描文件并入库
+     * @param contractId
+     * @param projectId
+     */
+    public R scanAndSaveFiles(Long contractId, Long projectId) {
+        // 1. 构建合同对应的根文件夹路径(D:\PDF\${contractId})
+        String contractFolderPath = ROOT_PREFIX + File.separator + contractId;
+        File contractFolder = new File(contractFolderPath);
+
+        // 校验根文件夹是否存在且为目录
+        if (!contractFolder.exists() || !contractFolder.isDirectory()) {
+            return R.fail("错误:根文件夹不存在或非法,路径=" + contractFolderPath);
+        }
+
+        System.out.println("开始扫描合同ID=" + contractId + "下的所有文件...");
+        // 递归扫描所有文件
+        recursiveScanFiles(contractFolder, projectId, contractId);
+        System.out.println("合同ID=" + contractId + "的文件扫描入库完成");
+        return R.success("扫描并入库完成");
+    }
+
+    /**
+     * 递归扫描文件夹中的所有文件并入库
+     * @param currentFolder 当前扫描的文件夹
+     * @param projectId 项目ID
+     * @param contractId 合同ID
+     */
+    private void recursiveScanFiles(File currentFolder, Long projectId, Long contractId) {
+        // 1. 先处理当前文件夹中的文件
+        File[] files = currentFolder.listFiles(File::isFile);
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                processFile(file, currentFolder, projectId, contractId);
+            }
+        }
+
+        // 2. 递归处理子文件夹
+        File[] subFolders = currentFolder.listFiles(File::isDirectory);
+        if (subFolders != null && subFolders.length > 0) {
+            for (File subFolder : subFolders) {
+                recursiveScanFiles(subFolder, projectId, contractId);
+            }
+        }
+    }
+
+    /**
+     * 处理单个文件:获取信息并入库
+     */
+    private void processFile(File file, File parentFolder, Long projectId, Long contractId) {
+        try {
+            // 获取文件创建时间
+            BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class);
+            Date date = new Date(attr.creationTime().toMillis());
+            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+            String createTime = sdf1.format(date); // 2023-12-25 14:30:45
+            // 获取文件信息
+            String fileName = file.getName();
+            String filePath = file.getAbsolutePath();
+            String folderName = parentFolder.getName();
+            // 查询对应的folder_id
+            Long folderId = scanFolderMapper.getId(folderName,contractId,projectId);
+            if (folderId == null) {
+                System.err.println("警告:未找到对应的文件夹记录,跳过文件:" + filePath +
+                        " (folderName=" + folderName + ")");
+                return;
+            }
+            // 检查文件是否已存在(根据project_id, contract_id, file_path判断)
+            if (fileExists(projectId, contractId, folderName)) {
+                System.out.println("文件已存在,跳过:" + filePath);
+                return;
+            }
+            BladeFile bladeFile = newIOSSClient.uploadFile(fileName, filePath);
+            if(bladeFile==null){
+                return;
+            }
+            String pdfNum = FileUtils.getPdfNum(bladeFile.getLink());
+            // 生成随机ID
+            Long fileId = SnowFlakeUtil.getId();
+            // 创建文件实体
+            ScanFile scanFile = new ScanFile(
+                    fileId,
+                    projectId,
+                    contractId,
+                    folderId,
+                    null,//序号
+                    null,//数字编号
+                    null,//文件编号
+                    fileName,
+                    pdfNum,//文件页数
+                    createTime,
+                    filePath,
+                    bladeFile.getLink(),//OSS路径
+                    null,//负责人
+                    0
+            );
+            // 入库
+            if (scanFileMapper.insert(scanFile)>0) {
+                System.out.println("文件入库成功:" + filePath);
+                try {
+                    // 构建备份文件路径,保持原有的文件夹结构
+                    if (filePath.startsWith(ROOT_PREFIX)) {
+                        // 截取原始路径中除根目录外的部分
+                        String relativePath = filePath.substring(ROOT_PREFIX.length());
+                        // 构建备份文件完整路径
+                        String backupFilePath = ROOT_PREFIX_back + relativePath;
+                        File backupFile = new File(backupFilePath);
+                        // 创建备份文件所在的目录
+                        File backupDir = backupFile.getParentFile();
+                        if (!backupDir.exists()) {
+                            boolean dirCreated = backupDir.mkdirs();
+                            if (!dirCreated) {
+                                throw new IOException("无法创建备份目录: " + backupDir.getAbsolutePath());
+                            }
+                        }
+                        // 复制文件到备份目录
+                        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);
+                        }
+                    } 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){
+            return true;
+        }else {
+            return false;
+        }
+    }
 }

+ 18 - 0
blade-service/blade-archive/src/main/java/org/springblade/archive/utils/FileUtils.java

@@ -9,6 +9,7 @@ import com.sun.image.codec.jpeg.JPEGImageEncoder;
 import lombok.extern.slf4j.Slf4j;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
 import org.apache.poi.hssf.usermodel.HSSFPrintSetup;
 import org.apache.poi.ss.usermodel.ClientAnchor;
 import org.apache.poi.ss.usermodel.Sheet;
@@ -879,4 +880,21 @@ public class FileUtils {
         return String.valueOf(pageNumber);
     }
 
+    public static String getPdfNum(String url) {
+        try {
+            if (url.isEmpty() || url.equals("")) {
+                return "0";
+            }
+            InputStream pdfInputStream = CommonUtil.getOSSInputStream(url);
+            //获取这份文件的页数并设置签章策略
+            //获取PDF文件
+            PDDocument document = PDDocument.load(pdfInputStream);
+            int page = document.getPages().getCount();
+            return page + "";
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return "";
+    }
+
 }