yangys
2025-09-18 a3048fa6fa72fa3cc5da2c43c59bd000e00c9599
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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<Path, WatchService> watchServices = new HashMap<>();
    private final Map<Path, FileChangeListener> listeners = new HashMap<>();
    private final Map<Path, FileState> fileStatesOld = new HashMap<>();
    Cache<Path, FileState> 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<Path> ev = (WatchEvent<Path>) event;
                        Path fileName = ev.context();
                        Path fullPath = directory.resolve(fileName);
 
                        FileState currentState = fileStates.getIfPresent(fullPath);
                        currentState = (currentState == null) ? FileState.STABLE : currentState;
 
                        //FileState currentState = fileStates.getOrDefault(fullPath, FileState.STABLE);
 
                        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<Path, WatchService> getWatchedDirectories(){
        return this.watchServices;
    }
}