| 
					
				 | 
			
			
				@@ -0,0 +1,141 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+package com.fs.app.config; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.fs.common.utils.spring.SpringUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.fs.live.service.ILiveSensitiveWordsService; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import lombok.Getter; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.apache.commons.lang3.StringUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.beans.factory.InitializingBean; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.beans.factory.annotation.Autowired; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.stereotype.Component; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.*; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.Executors; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.ScheduledExecutorService; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.concurrent.TimeUnit; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+@Component 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+public class ProductionWordFilter implements InitializingBean { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Autowired 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private ILiveSensitiveWordsService liveSensitiveWordsService; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static class TrieNode { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        @Getter 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final Map<Character, TrieNode> children = new HashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private boolean isEndOfWord; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public boolean isEndOfWord() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return isEndOfWord; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public void setEndOfWord(boolean endOfWord) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            isEndOfWord = endOfWord; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Getter 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static class MatchResult { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final String word; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final int endIndex; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public MatchResult(String word, int endIndex) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.word = word; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.endIndex = endIndex; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public int getLength() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return word.length(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Getter 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public static class FilterResult { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final String filteredText; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final Set<String> matchedWords; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private final int replacedCount; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public FilterResult(String filteredText, Set<String> matchedWords, int replacedCount) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.filteredText = filteredText; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.matchedWords = matchedWords; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            this.replacedCount = replacedCount; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private TrieNode root = new TrieNode(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private List<String> wordSources; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private final ScheduledExecutorService executor; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public ProductionWordFilter(List<String> wordSources) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.wordSources = wordSources; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.executor = Executors.newSingleThreadScheduledExecutor(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    @Override 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public void afterPropertiesSet() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        reload(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        executor.scheduleWithFixedDelay(this::reload, 1, 1, TimeUnit.HOURS); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public synchronized void reload() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        wordSources = liveSensitiveWordsService.selectAllWords(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        TrieNode newRoot = new TrieNode(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        wordSources.stream() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .flatMap(source -> loadWords(source).stream()) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .forEach(word -> addWord(newRoot, word)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        this.root = newRoot; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public FilterResult filter(String text) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        StringBuilder result = new StringBuilder(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Set<String> foundWords = new HashSet<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        int replacedCount = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (int i = 0; i < text.length(); ) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            MatchResult match = findNextMatch(text, i); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (match != null) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                foundWords.add(match.getWord()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                result.append(StringUtils.repeat('*', match.getLength())); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                replacedCount++; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                i = match.getEndIndex(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                result.append(text.charAt(i)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                i++; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return new FilterResult(result.toString(), foundWords, replacedCount); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private MatchResult findNextMatch(String text, int start) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        TrieNode current = root; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (int i = start; i < text.length(); i++) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            char c = text.charAt(i); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (!current.getChildren().containsKey(c)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            current = current.getChildren().get(c); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            if (current.isEndOfWord()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                return new MatchResult(text.substring(start, i + 1), i + 1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private void addWord(TrieNode root, String word) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        TrieNode node = root; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        for (char c : word.toCharArray()) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            node = node.getChildren().computeIfAbsent(c, k -> new TrieNode()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        node.setEndOfWord(true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private List<String> loadWords(String source) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 示例:实际应从 source(如 URL 或文件路径)读取 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return Collections.singletonList(source); // 替换为真实逻辑 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+} 
			 |