|
@@ -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); // 替换为真实逻辑
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|