diff --git a/web/pom.xml b/web/pom.xml index b956420..469fc1f 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -129,6 +129,14 @@ true + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + diff --git a/web/src/main/java/com/zhehekeji/web/service/CronTab.java b/web/src/main/java/com/zhehekeji/web/service/CronTab.java index 65fa47a..ede1aad 100644 --- a/web/src/main/java/com/zhehekeji/web/service/CronTab.java +++ b/web/src/main/java/com/zhehekeji/web/service/CronTab.java @@ -25,19 +25,21 @@ import javax.annotation.Resource; import java.io.File; import java.io.IOException; import java.math.BigInteger; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.time.*; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static java.nio.file.FileVisitResult.CONTINUE; @Component @EnableScheduling @@ -170,7 +172,133 @@ public class CronTab { } } } + private static List getSortedFoldersByCreateTime(File parentDir) { + File[] folders = parentDir.listFiles(File::isDirectory); + if (folders == null || folders.length == 0) { + return Collections.emptyList(); + } + + return Arrays.stream(folders) + .filter(f -> { + try { + Path path = f.toPath(); + BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class); + return attr != null && attr.creationTime() != null; + } catch (IOException e) { + return false; + } + }) + .sorted(Comparator.comparingLong(f -> { + try { + return Files.readAttributes(f.toPath(), BasicFileAttributes.class).creationTime().toMillis(); + } catch (IOException e) { + return Long.MAX_VALUE; + } + })) + .collect(Collectors.toList()); + } + /** + * 删除旧文件夹,直到磁盘剩余空间大于 350GB + * + * @param dirPath 要清理的目录路径 + */ + public static void deleteUntilFreeSpace(String dirPath) { + File rootDir = new File(dirPath); + if (!rootDir.exists() || !rootDir.isDirectory()) { + log.warn("目标路径不存在或不是目录: {}", dirPath); + return; + } + + while (true) { + long freeSpaceGB = rootDir.getFreeSpace() / (1024L * 1024L * 1024L); + if (freeSpaceGB > 350) { + log.info("磁盘空间已满足要求: {} GB", freeSpaceGB); + break; + } + + // 获取所有可删除的文件和空文件夹,并按创建时间排序 + List deletableItems = findDeletableItemsSortedByCreateTime(rootDir); + if (deletableItems.isEmpty()) { + log.warn("没有更多可删除的文件或文件夹"); + break; + } + + // 删除最早的项 + Path oldestItem = deletableItems.get(0); + try { + Files.walk(oldestItem) + .sorted(Comparator.reverseOrder()) + .forEach(path -> { + try { + Files.delete(path); + log.info("已删除: {}", path); + } catch (IOException e) { + log.error("删除失败: {}", path, e); + } + }); + } catch (Exception e) { + log.error("删除路径时出错: {}", oldestItem, e); + } + } + } + + + private static class PathWithCreateTime { + Path path; + long createTime; + + PathWithCreateTime(Path path, long createTime) { + this.path = path; + this.createTime = createTime; + } + } + + /** + * 查找所有可删除的文件和空文件夹,并按创建时间排序 + */ + private static List findDeletableItemsSortedByCreateTime(File rootDir) { + List candidates = new ArrayList<>(); + + try { + Files.walkFileTree(rootDir.toPath(), new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (attrs.isRegularFile()) { + candidates.add(new PathWithCreateTime(file, attrs.creationTime().toMillis())); + } + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (exc != null) { + return CONTINUE; + } + try { + // 只收集空文件夹 + if (Files.list(dir).findAny().isEmpty()) { + BasicFileAttributes attrs = Files.readAttributes(dir, BasicFileAttributes.class); + candidates.add(new PathWithCreateTime(dir, attrs.creationTime().toMillis())); + } + } catch (IOException e) { + log.warn("读取文件夹失败: {}", dir, e); + } + return CONTINUE; + } + }); + } catch (IOException e) { + log.error("遍历文件系统时出错", e); + } + + return candidates.stream() + .sorted(Comparator.comparingLong(p -> p.createTime)) + .map(p -> p.path) + .collect(Collectors.toList()); + } + public static void main(String[] args) { + deleteUntilFreeSpace("D:\\data"); + } public static void deleteOldFoldersByName(File parentDir, int days) { if (!parentDir.exists() || !parentDir.isDirectory()) { System.out.println("指定的路径不是一个有效的目录: " + parentDir.getName()); diff --git a/web/src/main/java/com/zhehekeji/web/service/CronTabDelete.java b/web/src/main/java/com/zhehekeji/web/service/CronTabDelete.java new file mode 100644 index 0000000..aa6d744 --- /dev/null +++ b/web/src/main/java/com/zhehekeji/web/service/CronTabDelete.java @@ -0,0 +1,149 @@ +package com.zhehekeji.web.service; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +import static java.nio.file.FileVisitResult.CONTINUE; + +@Slf4j +public class CronTabDelete { + + private static final long MIN_FREE_SPACE_GB = 350; + private static final long GB = 1024L * 1024L * 1024L; + private static final int THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2; + private static final ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE); + + /** + * 删除直到磁盘空间大于指定值(单位:GB) + */ + public static void deleteUntilFreeSpace(String dirPath) { + File rootDir = new File(dirPath); + if (!rootDir.exists() || !rootDir.isDirectory()) { + log.warn("目标路径不存在或不是目录: {}", dirPath); + return; + } + + while (true) { + long freeSpaceGB = rootDir.getFreeSpace() / GB; + if (freeSpaceGB > MIN_FREE_SPACE_GB) { + log.info("磁盘空间已满足要求: {} GB", freeSpaceGB); + break; + } + + List deletableItems = findDeletableItemsSortedByCreateTime(rootDir); + if (deletableItems.isEmpty()) { + log.warn("没有更多可删除的文件或文件夹"); + break; + } + + // 批量删除前 N 个最老项(例如 100 个) + int batchSize = Math.min(deletableItems.size(), 100); + List batchToDelete = deletableItems.subList(0, batchSize); + log.info("准备删除 {} 个最旧文件/空文件夹", batchToDelete.size()); + + List> futures = new ArrayList<>(); + for (Path path : batchToDelete) { + futures.add(executor.submit(() -> deleteRecursively(path))); + } + + // 等待所有任务完成 + for (Future future : futures) { + try { + future.get(); + } catch (Exception e) { + log.error("并发删除失败", e); + } + } + } + } + + /** + * 异步递归删除路径 + */ + private static void deleteRecursively(Path path) { + try { + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .forEach(p -> { + try { + Files.delete(p); + log.debug("已删除: {}", p); + } catch (IOException e) { + log.warn("删除失败: {}", p); + } + }); + } catch (IOException e) { + log.warn("遍历路径失败: {}", path, e); + } + } + + /** + * 查找所有可删除的文件和空文件夹,并按创建时间排序 + */ + private static List findDeletableItemsSortedByCreateTime(File rootDir) { + List candidates = Collections.synchronizedList(new ArrayList<>()); + + ForkJoinPool forkJoinPool = new ForkJoinPool(THREAD_POOL_SIZE); + try { + forkJoinPool.submit(() -> { + try { + Files.walkFileTree(rootDir.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + if (attrs.isRegularFile()) { + candidates.add(new PathWithCreateTime(file, attrs.creationTime().toMillis())); + } + return CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + if (exc != null) return CONTINUE; + try { + if (Files.list(dir).findAny().isEmpty()) { + BasicFileAttributes attrs = Files.readAttributes(dir, BasicFileAttributes.class); + candidates.add(new PathWithCreateTime(dir, attrs.creationTime().toMillis())); + } + } catch (IOException e) { + log.warn("读取文件夹失败: {}", dir, e); + } + return CONTINUE; + } + }); + } catch (IOException e) { + log.error("遍历文件系统时出错", e); + } + }).join(); + } finally { + forkJoinPool.shutdown(); + } + + return candidates.parallelStream() + .sorted(Comparator.comparingLong(p -> p.createTime)) + .map(p -> p.path) + .collect(Collectors.toList()); + } + + private static class PathWithCreateTime { + Path path; + long createTime; + + PathWithCreateTime(Path path, long createTime) { + this.path = path; + this.createTime = createTime; + } + } + + public static void main(String[] args) { + deleteUntilFreeSpace("D:\\data\\videos"); + } +} +