lyh
2026-03-23 ae9c823ff91b6b0c73139792ccf5c622575f999b
src/main/java/com/lxzn/framework/utils/file/FileUtil.java
@@ -5,7 +5,6 @@
import com.lxzn.framework.exception.ExceptionCast;
import com.lxzn.framework.utils.SHA256Util;
import com.lxzn.framework.utils.date.DateUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@@ -16,6 +15,11 @@
import java.io.*;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -25,6 +29,11 @@
public class FileUtil {
    private static String fileUploadFolder;
    private static String fileNcGateway;
    @Value("${fileNcGateway}")
    public void setFileNcGateway(String fileNcGateway) {
        FileUtil.fileNcGateway = fileNcGateway;
    }
    @Value("${fileHomePath}")
    public void setFileUploadFolder(String fileUploadFolder) {
@@ -59,6 +68,179 @@
        dto.setFileSize(fileSize);
        dto.setFileSuffix(suffix);
        return dto;
    }
    /**
     * 方法一:使用 FileWriter 写文件
     * @param filepath 文件目录
     * @param content  待写入内容
     * @throws IOException
     */
    public static void fileWriterSql(String filepath, String content) throws IOException {
        OutputStreamWriter outputStreamWriter = null;
        try {
            File file = new File(filepath);
            if (!file.exists()){
                file.createNewFile();
            }
            FileOutputStream outputStream = new FileOutputStream(file);
            if (outputStream != null){
                outputStreamWriter = new OutputStreamWriter(outputStream, "utf-8");
                outputStreamWriter.write(content);
                outputStreamWriter.flush();
            }
            try {
                if (outputStreamWriter != null){
                    outputStreamWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        catch (IOException e) {
            e.getMessage();
        }
        finally {
            try {
                if (outputStreamWriter != null){
                    outputStreamWriter.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 删除文件
     *
     * @param filePath
     * @return
     */
    public static boolean deleteNcFile(String filePath) {
        String lastFileReName =  fileUploadFolder + "/" + filePath ;
        boolean flag = false;
        File file = new File(lastFileReName);
        if (!file.exists()) {
            return flag;
        }
        boolean b = file.delete();
        if (!b) {
            System.out.println("文件删除失败: " + filePath);
        }
        return true;
    }
    /**
     * 复制文件(带重试、大小校验和MD5校验,确保复制完整性与可靠性)
     * 从 fileUploadFolder 下的源路径复制到 fileNcGateway 下的目标路径,可指定新的文件扩展名
     * @param lastFile 源文件的相对路径(相对于 fileUploadFolder)
     * @param newFile 目标文件的相对路径(相对于 fileNcGateway)
     * @param ncFix (可选)目标文件的新扩展名(不带点,如 "txt"),传入 null 或空字符串则不修改原扩展名
     * @return 复制成功返回 true,否则返回 false
     */
    public static boolean copyFileUpName(String lastFile, String newFile, String ncFix) {
        // 1. 构建源文件和目标文件的绝对路径
        String sourceFilePath = fileUploadFolder + "/" + lastFile;
        String targetFilePath = newFile;
        // 2. 如果指定了新的扩展名,则附加到目标文件名
        if (StringUtils.isNotBlank(ncFix)) {
            targetFilePath = targetFilePath + "." + ncFix;
        }
        // 3. 创建目标文件夹
        File targetFile = new File(targetFilePath);
        File targetParentDir = targetFile.getParentFile();
        if (!targetParentDir.exists() && !targetParentDir.mkdirs()) {
            log.error("无法创建目标文件夹: {}", targetParentDir.getAbsolutePath());
            return false;
        }
        // 4. 获取源文件预期大小
        long expectedSize = -1;
        File sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            log.error("源文件不存在: {}", sourceFilePath);
            return false;
        }
        try {
            expectedSize = sourceFile.length();
            log.info("文件复制: 源={}, 目标={}, 大小={}字节", sourceFilePath, targetFilePath, expectedSize);
        } catch (SecurityException e) {
            log.error("无法访问源文件: {}", sourceFilePath, e);
            return false;
        }
        // 5. 复制操作(最多重试3次)
        int maxRetries = 3;
        int attempt = 0;
        boolean copySuccess = false;
        while (attempt < maxRetries && !copySuccess) {
            attempt++;
            try {
                log.info("---- 开始第{}次复制尝试 ----", attempt);
                // 6. 执行复制操作(使用NIO FileChannel以提高效率)
                long startTime = System.currentTimeMillis();
                try (FileChannel in = new FileInputStream(sourceFilePath).getChannel();
                     FileChannel out = new FileOutputStream(targetFilePath).getChannel()) {
                    out.transferFrom(in, 0, in.size());
                }
                long endTime = System.currentTimeMillis();
                log.info("文件传输完成,耗时: {}毫秒", (endTime - startTime));
                // 7. 文件大小验证
                File copiedFile = new File(targetFilePath);
                long actualSize = copiedFile.length();
                if (expectedSize != actualSize) {
                    log.warn("大小不一致! 预期: {}B, 实际: {}B (差值: {}B)",
                            expectedSize, actualSize, Math.abs(actualSize - expectedSize));
                } else {
                    log.info("文件大小验证成功: {}B", actualSize);
                    copySuccess = true;
                }
                // 8. MD5校验
                if (copySuccess) {
                    String sourceMd5 = calculateMD5(sourceFilePath);
                    String targetMd5 = calculateMD5(targetFilePath);
                    if (!sourceMd5.equals(targetMd5)) {
                        log.warn("MD5不一致! 源: {}, 目标: {}", sourceMd5, targetMd5);
                        copySuccess = false;
                    } else {
                        log.info("MD5验证成功");
                    }
                }
            } catch (IOException | SecurityException e) {
                log.error("第{}次复制失败", attempt, e);
            }
            // 9. 重试间隔处理
            if (!copySuccess && attempt < maxRetries) {
                try {
                    int delay = 200 * attempt; // 递增延迟:200ms, 400ms
                    log.info("等待 {}ms 后重试", delay);
                    Thread.sleep(delay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    log.warn("重试等待被中断", ie);
                    break; // 如果线程被中断,则退出重试循环
                }
            }
        }
        if (copySuccess) {
            log.info("文件复制完成: {}", targetFilePath);
        } else {
            log.error("文件复制失败,已尝试{}次。源文件: {}, 目标文件: {}", maxRetries, sourceFilePath, targetFilePath);
        }
        return copySuccess;
    }
    public static FileUploadResult uploadFile(String fileName, InputStream fis) {
@@ -176,6 +358,54 @@
            log.error(e.getMessage());
        }
        return -1;
    }
    /**
     * 删除文件
     *
     * @param filePath
     * @return
     */
    public static boolean deleteFile(String filePath) {
        boolean flag = false;
        File file = new File(filePath);
        if (!file.exists()) {
            return flag;
        }
        boolean b = file.delete();
        if (!b) {
            try {
                System.gc();
                Path path = Paths.get(filePath);
                Files.deleteIfExists(path);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
    /**
     *
     * @param file
     * @return
     */
    public static long selectFileSize(File file) {
        try (FileInputStream fis = new FileInputStream(file)) {
            long size = 0;
            int read;
            byte[] buffer = new byte[1024];
            while ((read = fis.read(buffer)) != -1) {
                size += read;
            }
            return size;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }
    /**
@@ -306,41 +536,133 @@
    }
    /**
     * 复制文件
     * 复制文件(带重试和校验-防止中科方德复制文件出错问题)
     * @param oldPath
     * @param newPath
     * @param fileName
     */
    public static boolean copyFile(String oldPath, String newPath, String fileName){
        oldPath = fileUploadFolder + oldPath;
        newPath = fileUploadFolder + newPath;
        File oldFile = new File(oldPath, fileName);
        File newFile = new File(newPath, fileName);
        if(oldFile.exists()){
            if(!newFile.getParentFile().exists()){
                newFile.getParentFile().mkdirs();
            }
            FileChannel inputChannel = null;
            FileChannel outputChannel = null;
            try{
                inputChannel = new FileInputStream(oldFile).getChannel();
                outputChannel = new FileOutputStream(newFile).getChannel();
                outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
                return true;
            }catch (Exception e){
                log.error("复制文件错误{}", e);
                return false;
            }finally {
                try {
                    inputChannel.close();
                    outputChannel.close();
                }catch (Exception e){
                    log.error("复制文件错误{}", e);
        // 1. 路径规范化
        String absolutePathSend = fileUploadFolder + oldPath;
        String absolutePathTarget = fileUploadFolder + newPath;
        // 2. 创建目标文件夹
        File targetFolder = new File(absolutePathTarget);
        if (!targetFolder.exists() && !targetFolder.mkdirs()) {
            log.error("无法创建目标文件夹: {}", absolutePathTarget);
            return false;
        }
        // 3. 源文件和目标文件路径
        String sourceFilePath = absolutePathSend + File.separator + fileName;
        String targetFilePath = absolutePathTarget + File.separator + fileName;
        // 4. 获取源文件预期大小
        long expectedSize = -1;
        File sourceFile = new File(sourceFilePath);
        if (!sourceFile.exists()) {
            log.error("源文件不存在: {}", sourceFilePath);
            return false;
        }
        try {
            expectedSize = sourceFile.length();
            log.info("文件复制: 源={}, 目标={}, 大小={}字节", sourceFilePath, targetFilePath, expectedSize);
        } catch (SecurityException e) {
            log.error("无法访问源文件: {}", sourceFilePath, e);
            return false;
        }
        // 5. 复制操作(最多重试3次)
        int maxRetries = 3;
        int attempt = 0;
        boolean copySuccess = false;
        while (attempt < maxRetries && !copySuccess) {
            attempt++;
            try {
                log.info("---- 开始第{}次复制尝试 ----", attempt);
                // 6. 执行复制操作
                try (FileChannel in = new FileInputStream(sourceFilePath).getChannel();
                     FileChannel out = new FileOutputStream(targetFilePath).getChannel()) {
                    out.transferFrom(in, 0, in.size());
                }
                // 7. 文件大小验证
                File copiedFile = new File(targetFilePath);
                long actualSize = copiedFile.length();
                if (expectedSize != actualSize) {
                    log.warn("大小不一致! 预期: {}B, 实际: {}B (差值: {}B)",
                            expectedSize, actualSize, Math.abs(actualSize - expectedSize));
                } else {
                    log.info("文件大小验证成功: {}B", actualSize);
                    copySuccess = true;
                }
                // 8. MD5校验
                if (copySuccess) {
                    String sourceMd5 = calculateMD5(sourceFilePath);
                    String targetMd5 = calculateMD5(targetFilePath);
                    if (!sourceMd5.equals(targetMd5)) {
                        log.warn("MD5不一致! 源: {}, 目标: {}", sourceMd5, targetMd5);
                        copySuccess = false;
                    } else {
                        log.info("MD5验证成功");
                    }
                }
            } catch (IOException | SecurityException e) {
                log.error("第{}次复制失败", attempt, e);
            }
            // 9. 重试间隔处理
            if (!copySuccess && attempt < maxRetries) {
                try {
                    int delay = 200 * attempt; // 递增延迟:200ms, 400ms
                    log.info("等待 {}ms 后重试", delay);
                    Thread.sleep(delay);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        return false;
        if (copySuccess) {
            log.info("文件复制完成: {}", targetFilePath);
        } else {
            log.error("文件复制失败,已尝试{}次", maxRetries);
        }
        return copySuccess;
    }
    /**
     * 计算文件的MD5值
     * @param filePath
     * @return
     */
    public static String calculateMD5(String filePath) {
        try (InputStream is = Files.newInputStream(Paths.get(filePath))) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int read;
            while ((read = is.read(buffer)) != -1) {
                md.update(buffer, 0, read);
            }
            byte[] digest = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException("计算MD5失败: " + filePath, e);
        }
    }
    public static String getFileAbsPath(String path, String fileName){
@@ -352,5 +674,12 @@
        return null;
    }
    public static String getFileAbsPathTxt(String path){
        File file = new File(path);
        if(file.exists()){
            return file.getAbsolutePath();
        }
        return null;
    }
}