不得不做,敏感词过滤最佳实践

因为在博客里上线了评论的功能,但是为了防止有些人发一些敏感词汇,所以做这个敏感词过滤是非常有必要的(这也是为啥我不愿意在评论中加图片一样,假如有人发一些奇怪的图片咋整 🙂)

博客里敏感词策略是检测到如果文案中有敏感词就不允许发布,也会带出相应的敏感词整改,而不是给评论打*处理,防止评论里全是****,哈哈哈

一、 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技术和记录生活的博主。

欢迎扫码关注“一零贰肆”的公众号,一起学习,共同进步,多看路,少踩坑。

posted @ 2024-03-09 19:00  孙半仙人  阅读(249)  评论(0编辑  收藏  举报