package org.springblade.mdm.machinefile.filewatch; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.IOException; import java.nio.file.*; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import static java.nio.file.StandardWatchEventKinds.*; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @Slf4j @Service public class FileWatcherService { private final ExecutorService executor = Executors.newCachedThreadPool(); private final Map watchServices = new HashMap<>(); private final Map listeners = new HashMap<>(); Cache fileStates = CacheBuilder.newBuilder() .maximumSize(10) // 最大容量3 .build(); enum FileState { CREATED, MODIFIED, STABLE } public interface FileChangeListener { void onFileCreated(Path filePath); void onFileModified(Path filePath); void onFileDeleted(Path filePath); } public void watchDirectory(Path directory, FileChangeListener listener) throws IOException { if (!Files.isDirectory(directory)) { throw new IllegalArgumentException("Path must be a directory: " + directory); } if (watchServices.containsKey(directory)) { throw new IllegalStateException("Directory is already being watched: " + directory); } WatchService watchService = FileSystems.getDefault().newWatchService(); directory.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); watchServices.put(directory, watchService); listeners.put(directory, listener); executor.submit(() -> { try { while (true) { WatchKey key = watchService.take(); for (WatchEvent event : key.pollEvents()) { WatchEvent.Kind kind = event.kind(); @SuppressWarnings("unchecked") WatchEvent ev = (WatchEvent) event; Path fileName = ev.context(); Path fullPath = directory.resolve(fileName); FileState currentState = fileStates.getIfPresent(fullPath); currentState = (currentState == null) ? FileState.STABLE : currentState; FileChangeListener currentListener = listeners.get(directory); if (currentListener == null) break; if (kind == ENTRY_CREATE) { if (currentState != FileState.CREATED) { log.info("新文件创建: {}", fullPath); fileStates.put(fullPath, FileState.CREATED); } currentListener.onFileCreated(fullPath); } else if (kind == ENTRY_MODIFY) { if (currentState == FileState.CREATED) { // 忽略创建后的第一次修改 log.info("文件创建时的修改事件,忽略: {}", fullPath); fileStates.put(fullPath, FileState.STABLE); } else { currentListener.onFileModified(fullPath); } } else if (kind == ENTRY_DELETE) { currentListener.onFileDeleted(fullPath); } } boolean valid = key.reset(); if (!valid) { break; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (ClosedWatchServiceException e) { // Service was closed, exit normally } }); } public void stopWatching(Path directory) throws IOException { WatchService watchService = watchServices.remove(directory); listeners.remove(directory); if (watchService != null) { watchService.close(); } } public void shutdown() { executor.shutdownNow(); watchServices.values().forEach(watchService -> { try { watchService.close(); } catch (IOException e) { // Ignore on shutdown } }); watchServices.clear(); listeners.clear(); } public Map getWatchedDirectories(){ return this.watchServices; } }