package com.lxzn.framework.utils.file; import com.lxzn.framework.domain.filesystem.response.FileUploadResult; import com.lxzn.framework.domain.nc.response.DocumentCode; import com.lxzn.framework.exception.ExceptionCast; import com.lxzn.framework.utils.SHA256Util; import com.lxzn.framework.utils.date.DateUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; 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; @Slf4j @Component 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) { FileUtil.fileUploadFolder = fileUploadFolder; } public static FileUploadResult uploadFile(MultipartFile file) { if(file == null || file.isEmpty()) return null; String fileName = file.getOriginalFilename(); String suffix = getFileSuffix(fileName); // if(StringUtils.isBlank(suffix)) // return null; Date currentDate = DateUtil.getNow(); String monthStr = DateUtil.format(currentDate, DateUtil.STR_YEAR_MONTH); //相对路径 String relativePath = "/" + monthStr + "/" + DateUtil.getDayStr(currentDate) + "/"; //绝对路径 String absolutePath = fileUploadFolder + "/" + monthStr + "/" + DateUtil.getDayStr(currentDate) + "/"; String fileNameNonSuffix = getFilenameNonSuffix(fileName); if(fileNameNonSuffix == null) return null; String encodeFileName = SHA256Util.getSHA256Str(fileNameNonSuffix + System.currentTimeMillis()); Long fileSize = file.getSize(); boolean b = uploadFile(file, absolutePath, encodeFileName); if(!b) return null; FileUploadResult dto = new FileUploadResult(); dto.setFileName(fileNameNonSuffix); dto.setEncodeFileName(encodeFileName); dto.setFilePath(relativePath); 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) { if(fis == null) return null; //String fileName = file.getName(); String suffix = getFileSuffix(fileName); // if(StringUtils.isBlank(suffix)) // return null; Date currentDate = DateUtil.getNow(); String monthStr = DateUtil.format(currentDate, DateUtil.STR_YEAR_MONTH); //相对路径 String relativePath = "/" + monthStr + "/" + DateUtil.getDayStr(currentDate) + "/"; //绝对路径 String absolutePath = fileUploadFolder + "/" + monthStr + "/" + DateUtil.getDayStr(currentDate) + "/"; String fileNameNonSuffix = getFilenameNonSuffix(fileName); if(fileNameNonSuffix == null) { return null; } String encodeFileName = SHA256Util.getSHA256Str(fileNameNonSuffix + System.currentTimeMillis()); //Long fileSize = fis; long b = uploadFile(fis, absolutePath, encodeFileName); if(b <= 0) return null; FileUploadResult dto = new FileUploadResult(); dto.setFileName(fileNameNonSuffix); dto.setEncodeFileName(encodeFileName); dto.setFilePath(relativePath); dto.setFileSize(b); dto.setFileSuffix(suffix); return dto; } public static void downLoadFile(HttpServletResponse response, String fileName, String filePath, String toFileName) { String absolutePath = fileUploadFolder + filePath; File file = new File(absolutePath , fileName); if(file.exists()) { byte[] buffer = new byte[1024]; FileInputStream fis = null; BufferedInputStream bis = null; try { response.setHeader("Content-Type", "application/octet-stream;charset=utf-8"); // 告诉浏览器输出内容为流 //response.setHeader("Content-Disposition", "attachment;fileName="+ new String(toFileName.getBytes("UTF-8"),"ISO-8859-1")); response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(toFileName, "UTF-8")); fis = new FileInputStream(file); bis = new BufferedInputStream(fis); OutputStream os = response.getOutputStream(); int i = bis.read(buffer); while (i != -1) { os.write(buffer, 0, i); i = bis.read(buffer); } }catch (Exception e) { log.error(e.getMessage(), e.getStackTrace()); }finally { if(bis != null) { try { bis.close(); } catch (IOException e) { } } if(fis != null) { try { fis.close(); } catch (IOException e) { } } } }else { ExceptionCast.cast(DocumentCode.DOC_PUBLISH_FILE_NOT_EXIST); } } /** * 上传文件工具类 * @param multipartFile * @param path * @param fileNewName 新的文件名 * @return */ public static boolean uploadFile(MultipartFile multipartFile, String path, String fileNewName) { File targetFile = new File(path, fileNewName); if(!targetFile.getParentFile().exists()){ targetFile.getParentFile().mkdirs(); } try { multipartFile.transferTo(targetFile); return true; } catch (Exception e) { log.error(e.getMessage(), e.getStackTrace()); return false; } } public static long uploadFile(InputStream fis, String path, String fileNewName) { File targetFile = new File(path, fileNewName); if(!targetFile.getParentFile().exists()){ targetFile.getParentFile().mkdirs(); } try { FileOutputStream fos = new FileOutputStream(targetFile); //FileInputStream fis = new FileInputStream(file); byte[] bytes = new byte[1024 * 4]; int index = 0; long total = 0; while ((index = fis.read(bytes)) != -1) { fos.write(bytes, 0, index); total =+ index; } fis.close(); fos.flush(); fos.close(); return total; } catch (Exception e) { 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; } /** * 获取文件后缀 无后缀文件返回NULL * @param fileName * @return */ public static String getFileSuffix(String fileName) { if (fileName.contains(".")) { String suffix = fileName.substring(fileName.lastIndexOf('.') + 1); return suffix; }else { return null; } } /** * 获取文件名 不带后缀和点号 * @param fileName * @return */ public static String getFilenameNonSuffix(String fileName) { if (fileName.contains(".")) { String filename = fileName.substring(0, fileName.lastIndexOf('.')); return filename; }else { return fileName; } } public static List readFile(String fileEncodeName, String filePath) { String absolutePath = fileUploadFolder + filePath; File file = new File(absolutePath , fileEncodeName); if(!file.exists() || file.isDirectory()) ExceptionCast.cast(DocumentCode.DOC_PUBLISH_FILE_NOT_EXIST); String charset = checkFileEncode(file); if(charset == null) ExceptionCast.cast(DocumentCode.DOC_PUBLISH_FILE_NOT_EXIST); List readList = new ArrayList<>(); String tempString = null; BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset)); while ((tempString = reader.readLine()) != null) { readList.add(tempString); } if(readList.isEmpty()) return null; return readList; } catch (Exception e) { log.error(e.getMessage(), e.getStackTrace()); return null; } finally { if(reader != null) { try { reader.close(); } catch (IOException e) { log.error(e.getMessage(), e.getStackTrace()); } } } } private static String checkFileEncode(File file) { String charset = "GBK"; byte[] first3Bytes = new byte[3]; BufferedInputStream bis = null; try { boolean checked = false; bis = new BufferedInputStream(new FileInputStream(file)); bis.mark(0); int read = bis.read(first3Bytes, 0, 3); if (read == -1) return charset; if(first3Bytes[0] == (byte) 0xFF && first3Bytes[1] == (byte) 0xFE ) { charset = "UTF-16LE"; checked = true; }else if ( first3Bytes[0] == (byte) 0xFE && first3Bytes[1] == (byte) 0xFF ) { charset = "UTF-16BE"; checked = true; }else if (first3Bytes[0] == (byte) 0xEF && first3Bytes[1] == (byte) 0xBB && first3Bytes[2] == (byte) 0xBF ) { charset = "UTF-8"; checked = true; } bis.reset(); if (!checked) { while ((read = bis.read()) != -1 ) { if ( read >= 0xF0 ) break; if ( 0x80 <= read && read <= 0xBF ) // 单独出现BF以下的,也算是GBK break; if ( 0xC0 <= read && read <= 0xDF ) { read = bis.read(); if ( 0x80 <= read && read <= 0xBF ) // 双字节 (0xC0 - 0xDF) (0x80 - 0xBF),也可能在GBK编码内   continue; else break; } else if ( 0xE0 <= read && read <= 0xEF ) { // 也有可能出错,但是几率较小 read = bis.read(); if ( 0x80 <= read && read <= 0xBF ) { read = bis.read(); if ( 0x80 <= read && read <= 0xBF ) { charset = "UTF-8"; break; } else break; } else { break; } } } } return charset; }catch (Exception e) { return null; }finally { if(bis != null) { try { bis.close(); } catch (IOException e) { } } } } /** * 复制文件(带重试和校验-防止中科方德复制文件出错问题) * @param oldPath * @param newPath * @param fileName */ public static boolean copyFile(String oldPath, String newPath, String fileName){ // 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(); } } } 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){ path = fileUploadFolder + path; File file = new File(path, fileName); if(file.exists()){ return file.getAbsolutePath(); } return null; } public static String getFileAbsPathTxt(String path){ File file = new File(path); if(file.exists()){ return file.getAbsolutePath(); } return null; } }