不得不做,敏感词过滤最佳实践
因为在博客里上线了评论的功能,但是为了防止有些人发一些敏感词汇,所以做这个敏感词过滤是非常有必要的(这也是为啥我不愿意在评论中加图片一样,假如有人发一些奇怪的图片咋整 🙂)
博客里敏感词策略是检测到如果文案中有敏感词就不允许发布,也会带出相应的敏感词整改,而不是给评论打*处理,防止评论里全是****,哈哈哈
一、 DFA 算法
DFA,全称 Deterministic Finite Automaton 即确定有穷自动机:从一个状态通过一系列的事件转换到另一个状态,即 state -> event -> state
大概是这么个意思,通过节点获取到下一个节点是否符合敏感词,这样只要循环一遍文案就可以找到所有的敏感词了,以上图片的敏感词分别为:敏感词
,敏捷
,坏人
,坏蛋
二、sensitive-word 工具包
当然有需求就有实现,有现成的工具类sensitive-word
,具体使用方式可参考:https://blog.csdn.net/yumuing/article/details/129717450
我就不重复写了,博客里使用的就是这个工具包
三、自定义实现
当然,不想造轮子的程序员不是好架构师,既然知道了这个算法的基本原理,那么我们就手动来实现一下这个功能吧
1. 创建节点
这个又穷状态机跟链表很像,所以我们先创建一个链表的节点,创建好子节点和结束状态
public class TrieNode implements Serializable {
/**
* 子节点
*/
private Map<Character, TrieNode> children = new HashMap<>();
/**
* 是否为最后一个字符
*/
private Boolean isEndOfWord = false;
public Map<Character, TrieNode> getChildren() {
return children;
}
public Boolean getEndOfWord() {
return isEndOfWord;
}
public void setEndOfWord(Boolean endOfWord) {
isEndOfWord = endOfWord;
}
}
2. 初始化数据
从准备好的敏感词txt文件中读取敏感词,并放入到List集合中,注意这个txt中一行为一个敏感词,不要有逗号,空格等等字符
敏感词库网上很多,这里推荐一个:https://github.com/fwwdn/sensitive-stop-words
在resource包下,创建sensitive目录,并加入mySensitiveWords.txt敏感词库
private List<String> sensitiveList;
private void loadData() {
try {
Resource mySensitiveWords = new ClassPathResource("sensitive/mySensitiveWords.txt");
Path mySensitiveWordsPath = Paths.get(mySensitiveWords.getFile().getPath());
sensitiveList = Files.readAllLines(mySensitiveWordsPath, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("敏感词初始化失败", e);
}
}
/**
* 初始化数据
*/
private void init() {
for (String word : sensitiveList) {
addSensitive(word);
}
}
/**
* 添加敏感词
*
* @param word
*/
public void addSensitive(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
// computeIfAbsent返回结果值
node = node.getChildren().computeIfAbsent(ch, k -> new TrieNode());
}
// 一个敏感词循环结束,设置结束标记
node.setEndOfWord(true);
}
3. 工具类编写
这里我把所有的工具类方法都贴出来了,可以直接拿来用
@Slf4j
public enum SensitiveUtil {
/**
* 工具类
*/
X;
/**
* 根节点
*/
private TrieNode root = new TrieNode();
/**
* 敏感词列表
*/
private List<String> sensitiveList;
SensitiveUtil() {
// 加载数据
loadData();
// 初始化数据
init();
}
/**
* 加载数据
*/
private void loadData() {
try {
Resource mySensitiveWords = new ClassPathResource("sensitive/mySensitiveWords.txt");
Path mySensitiveWordsPath = Paths.get(mySensitiveWords.getFile().getPath());
sensitiveList = Files.readAllLines(mySensitiveWordsPath, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("敏感词初始化失败", e);
}
}
/**
* 初始化数据
*/
private void init() {
for (String word : sensitiveList) {
addSensitive(word);
}
}
/**
* 添加敏感词
*
* @param word
*/
public void addSensitive(String word) {
TrieNode node = root;
for (char ch : word.toCharArray()) {
// computeIfAbsent返回结果值
node = node.getChildren().computeIfAbsent(ch, k -> new TrieNode());
}
// 一个敏感词循环结束,设置结束标记
node.setEndOfWord(true);
}
/**
* 判断是否存在敏感词
*/
public boolean contains(String content) {
TrieNode currentNode = root;
for (char ch : content.toCharArray()) {
TrieNode node = currentNode.getChildren().get(ch);
if (node != null) {
if (node.getEndOfWord()) {
return true;
}
currentNode = node;
} else {
currentNode = root;
}
}
return false;
}
/**
* 返回第一个敏感词
*/
public String findFirst(String text) {
Set<String> first = find(text, true);
if (!first.isEmpty()) {
return first.iterator().next();
}
return null;
}
/**
* 返回所有敏感词
*/
public Set<String> findAll(String text) {
return find(text, false);
}
/**
* 返回敏感词
*/
public Set<String> find(String text, boolean isFirst) {
TrieNode currentNode = root;
Set<String> set = new HashSet<>();
StringBuilder builder = new StringBuilder();
for (char ch : text.toCharArray()) {
TrieNode node = currentNode.getChildren().get(ch);
// 匹配到敏感词
if (node != null) {
// 匹配到就加入到builder中
builder.append(ch);
// 判断该敏感词是否为最后一个
if (node.getEndOfWord()) {
set.add(builder.toString());
// 如果只要返回第一个敏感词,则直接返回
if (isFirst) {
break;
}
}
// 切换到下一个节点
currentNode = node;
} else {
// 未匹配到敏感词,从根节点重新开始
currentNode = root;
// 因为没有匹配到敏感词,所以之前加入的字符需要清空
builder.delete(0, builder.length());
}
}
return set;
}
/**
* 替换敏感词
*/
public String replace(String text, String replaceChar) {
Set<String> all = findAll(text);
for (String word : all) {
// 获取敏感词长度
String myReplace = replaceStr(replaceChar, word.length());
text = text.replaceAll(word, myReplace);
}
return text;
}
/**
* 返回需要替换的敏感词字符
*/
private static String replaceStr(String replaceChar, Integer length) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < length; i++) {
builder.append(replaceChar);
}
return builder.toString();
}
}
4. 测试
敏感词:笨蛋
,坏人
,TMD
@Test
public void test_myUtil() {
String text = "你TMD真是个坏人,笨蛋啊啊啊啊";
String first = SensitiveUtil.X.findFirst(text);
System.out.println("返回字符串中第一个敏感词====>>>>" + first);
Set<String> all = SensitiveUtil.X.findAll(text);
System.out.println("返回字符串中所有敏感词====>>>>" + all);
String replace = SensitiveUtil.X.replace(text, "*");
System.out.println("返回打马后字符串====>>>>" + replace);
}
返回字符串中第一个敏感词====>>>>TMD
返回字符串中所有敏感词====>>>>[坏人, TMD, 笨蛋]
返回打马后字符串====>>>>你***真是个**,**啊啊啊啊
我是一零贰肆,一个关注Java技术和记录生活的博主。
欢迎扫码关注“一零贰肆”的公众号,一起学习,共同进步,多看路,少踩坑。