这里的项目实战, 我们使用的是 SpringBoot2.x+JDK1.8搭建的,核心思想是借助了Hutool工具类的 WordTree。想了解更多DFA算法的实现可以参考DFA算法的实现
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
Hutool工具类中定义了 SensitiveProcessor 接口,它的作用是把敏感词替换成 *
package cn.hutool.dfa;
/**
* @author 肖海斌
* 敏感词过滤处理器,默认按字符数替换成*
*/
public interface SensitiveProcessor {
/**
* 敏感词过滤处理
* @param foundWord 敏感词匹配到的内容
* @return 敏感词过滤后的内容,默认按字符数替换成*
*/
default String process(FoundWord foundWord) {
int length = foundWord.getFoundWord().length();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append("*");
}
return sb.toString();
}
}
我们可以根据不同的业务需求,实现不同的处理器。这里可以定义了一个默认处理器和高亮处理器。
SensitiveDefaultProcessor 默认处理器和原逻辑一样,可以直接调用父类的process()方法实现把铭感词替换为*
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.SensitiveProcessor;
/**
* 自定义敏感词*号替代处理器
*/
public class SensitiveDefaultProcessor implements SensitiveProcessor {
}
SensitiveHighlightProcessor 定义了敏感词进行高亮处理,可以在铭感词前后打上对应的标签。
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.SensitiveProcessor;
/**
* 自定义敏感词高亮处理器
*/
public class SensitiveHighlightProcessor implements SensitiveProcessor {
private static final String SHIELD_START = "<shield>";
private static final String SHIELD_END = "</shield>";
private static final String DST_START = "<dst>";
private static final String DST_END = "</dst>";
private static final String WARN_START = "<warn>";
private static final String WARN_END = "</warn>";
@Override
public String process(FoundWord foundWord) {
String word = foundWord.getFoundWord();
StringBuilder sb = new StringBuilder();
sb.append(WARN_START).append(word).append(WARN_END);
return sb.toString();
}
public String process(FoundWord foundWord, SensitiveWordModeEnum mode) {
String word = foundWord.getFoundWord();
StringBuilder sb = new StringBuilder();
if (SensitiveWordModeEnum.SHIELD.equals(mode)) {
sb.append(SHIELD_START).append(word).append(SHIELD_END);
} else if (SensitiveWordModeEnum.DST.equals(mode)) {
sb.append(DST_START).append(word).append(DST_END);
} else if (SensitiveWordModeEnum.WARN.equals(mode)) {
sb.append(WARN_START).append(word).append(WARN_END);
}
return sb.toString();
}
}
其中 SensitiveWordModeEnum 是自己定义的一个敏感词模式枚举
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 敏感词模式枚举
*
*/
@AllArgsConstructor
@Getter
public enum SensitiveWordModeEnum {
SHIELD("SHIELD", "屏蔽"),
DST("DST", "脱敏"),
WARN("WARN", "警告");
@EnumValue
@JsonValue
private final String code;
private final String name;
}
通常来说,铭感词的内容是相对固定的。我们在项目启动时可以进行预加载。当铭感词变更时,我们可以通过更新本地缓存,定时刷新的方法进行处理。
Tips: 我们可以定义初始化类,它实现了ApplicationRunner接口。这个类中的 run方法将会在Boot项目的程序的入口方法 main 执行完毕之后被调用。在该类中可以定义一些应用程序启动后需要进行初始化的操作或任务
/**
* 敏感词工具类
*/
@Slf4j
@Component
public class SensitiveWordUtil implements ApplicationRunner {
// 一个定义铭感词查找的Dao层 [查询数据库中的敏感词数据]
@Resource
private SensitiveWordConfigDao sensitiveWordConfigDao;
// DFA敏感词树
private static final WordTree SENSITIVE_TREE = new WordTree();
// 定义了一个初始化的敏感词容器
private static final ConcurrentHashMap<String, SensitiveWordConfigVO> SENSITIVE_WORDS_MAP = new ConcurrentHashMap<>();
@Override
public void run(ApplicationArguments args) {
// 1.查询数据库中的铭感词列表
List<SensitiveWordConfig> sensitiveWordConfigList = sensitiveWordConfigDao.list();
if (ObjectUtil.isEmpty(sensitiveWordConfigList)) {
return;
}
for (SensitiveWordConfig sensitiveWord : sensitiveWordConfigList) {
// 1.1 敏感词VO对象的转换
SensitiveWordConfigVO sensitiveWordConfigVO = new SensitiveWordConfigVO();
BeanUtils.copyProperties(sensitiveWord, sensitiveWordConfigVO);
//1.2 本地容器缓存的初始化
SENSITIVE_WORDS_MAP.put(sensitiveWordConfigVO.getWord(), sensitiveWordConfigVO);
}
// 1.3 初始DFA敏感词树
this.init(ListUtil.toList(SENSITIVE_WORDS_MAP.keys()), true);
log.info("初始化敏感词库完毕, 共" + sensitiveWordConfigList.size() + "个敏感词");
}
/**
* 初始化敏感词树
* @param isAsync 是否异步初始化
* @param sensitiveWords 敏感词列表
*/
public void init(final Collection<String> sensitiveWords, boolean isAsync) {
if (isAsync) {
ThreadUtil.execAsync(() -> {
init(sensitiveWords);
return true;
});
} else {
init(sensitiveWords);
}
}
/**
* 初始化敏感词树
*
* @param sensitiveWords 敏感词列表
*/
public void init(Collection<String> sensitiveWords) {
SENSITIVE_TREE.clear();
SENSITIVE_TREE.addWords(sensitiveWords);
}
}
上面的工具类调用 run() 方法后,就能实现铭感词容器的初始化。
除了定义一些最基础的初始化步骤外,我们可以把一些添加铭感词,移除敏感词,查找等方法都定义在该类中。
// 添加敏感词
public static void addSensitiveWord(SensitiveWordConfig sw) {
SensitiveWordConfigVO vo = new SensitiveWordConfigVO();
BeanUtils.copyProperties(sensitiveWord, vo);
SENSITIVE_WORDS_MAP.put(sw.getWord(), vo);
SENSITIVE_TREE.addWord(sw.getWord());
}
// 移除敏感词
public static void removeSensitiveWord(String word) {
SENSITIVE_WORDS_MAP.remove(word);
SENSITIVE_TREE.clear();
SENSITIVE_TREE.addWords(ListUtil.toList(SENSITIVE_WORDS_MAP.keySet()));
}
/**
* 查找敏感词,返回找到的第一个敏感词
*
* @param text 文本
* @return 敏感词
* @since 5.5.3
*/
public static FoundWord getFoundFirstSensitive(String text) {
return SENSITIVE_TREE.matchWord(text);
}
// 还可以通过上面的processor处理器进行敏感词处理
至此,我们在项目中只要引入SensitiveWordUtil 工具类,就能实现敏感词的基本操作了。