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");
+ }
+}
+