package com.zy.core.task; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.format.ResolverStyle; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; @Slf4j @Component public class DeviceLogScheduler { private static final ReentrantLock FILE_OP_LOCK = new ReentrantLock(); private static final int DEFAULT_EXPIRE_DAYS = 1; private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("uuuuMMdd") .withResolverStyle(ResolverStyle.STRICT); @Value("${deviceLogStorage.type}") private String storageType; @Value("${deviceLogStorage.loggingPath}") private String loggingPath; @Value("${deviceLogStorage.expireDays}") private Integer expireDays; @PostConstruct public void validateStorageMode() { if (!"file".equalsIgnoreCase(storageType)) { throw new IllegalStateException("deviceLogStorage.type 仅支持 file,当前配置为: " + storageType); } ensureLoggingDirectoryExistsOrThrow(); } @Scheduled(cron = "${deviceLogStorage.cleanupScanCron:0 0 3 * * ?}") public void delDeviceLog() { if (!FILE_OP_LOCK.tryLock()) { return; } try { ensureLoggingDirectoryExists(); clearFileLog(resolveExpireDays()); } finally { FILE_OP_LOCK.unlock(); } } private void ensureLoggingDirectoryExistsOrThrow() { Path loggingRootPath = Paths.get(loggingPath); try { Files.createDirectories(loggingRootPath); } catch (Exception e) { throw new IllegalStateException("初始化设备日志目录失败, path=" + loggingPath, e); } } private void ensureLoggingDirectoryExists() { try { Files.createDirectories(Paths.get(loggingPath)); } catch (Exception e) { log.warn("初始化设备日志目录失败, path={}", loggingPath, e); } } private int resolveExpireDays() { if (expireDays == null || expireDays <= 0) { log.warn("deviceLogStorage.expireDays 配置无效,使用默认值: {}", DEFAULT_EXPIRE_DAYS); return DEFAULT_EXPIRE_DAYS; } return expireDays; } private void clearFileLog(int days) { try { Path baseDir = Paths.get(loggingPath); if (!Files.exists(baseDir)) { return; } LocalDate earliestReservedDay = LocalDate.now().minusDays(days - 1L); List expiredDayDirs = new ArrayList<>(); try (Stream stream = Files.list(baseDir)) { stream.filter(Files::isDirectory) .forEach(dayDir -> collectExpiredDayDirectory(dayDir, earliestReservedDay, expiredDayDirs)); } for (Path dayDir : expiredDayDirs) { Files.walkFileTree(dayDir, new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws java.io.IOException { Files.deleteIfExists(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, java.io.IOException exc) throws java.io.IOException { Files.deleteIfExists(dir); return FileVisitResult.CONTINUE; } }); } } catch (Exception e) { log.error("删除设备日志文件失败, path={}", loggingPath, e); } } private void collectExpiredDayDirectory(Path dayDir, LocalDate earliestReservedDay, List expiredDayDirs) { LocalDate dayValue = parseDayDirectory(dayDir.getFileName().toString()); if (dayValue == null || !dayValue.isBefore(earliestReservedDay)) { return; } expiredDayDirs.add(dayDir); } private LocalDate parseDayDirectory(String dayDirectoryName) { try { return LocalDate.parse(dayDirectoryName, DAY_FORMATTER); } catch (DateTimeParseException ignored) { return null; } } }