过滤敏感词工具类SensitiveFilter

网上过滤敏感词工具类有的存在挺多bug,这是我自己改用的过滤敏感词工具类,目前来说没啥bug,如果有bug欢迎在评论指出

使用前缀树 Trie 实现的过滤敏感词,树节点用静态内部类表示了,都写在一个 SensitiveFilter 一个文件里了

package top.linzeliang.util; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * 敏感词过滤 * * @Author: linzeliang * @Date: 2021/12/8 */ @Component public class SensitiveFilter { private static final Logger LOGGER = LoggerFactory.getLogger(SensitiveFilter.class); /** * 替换符 */ private static final String REPLACEMENT = "*"; /** * 根节点,根节点是不带值的 */ private final TrieNode ROOT_NODE = new TrieNode(); /** * 初始化前缀树,读取敏感词文件构造前缀树 * * @date 2021/12/9 */ @PostConstruct private void init() { try ( InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt"); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)) ) { String keyword; // 每次读取一行 while ((keyword = reader.readLine()) != null) { // 添加到前缀树 this.addKeyword(keyword); } } catch (IOException e) { LOGGER.error("加载敏感词文件失败: " + e.getMessage()); } } /** * 将一个敏感词添加到前缀树中 * * @param keyword 敏感词 * @date 2021/12/9 */ private void addKeyword(String keyword) { TrieNode tempNode = ROOT_NODE; for (int i = 0; i < keyword.length(); i++) { //获取单个字符 char c = keyword.charAt(i); // 先查询是否存在,就是是否有这个开头的敏感词 TrieNode subNode = tempNode.getSubNode(c); // 如果子节点中不存在,就新建,并且添加到tempNode的子节点 if (null == subNode) { subNode = new TrieNode(); tempNode.addSubNodes(c, subNode); } // 标记一下最后一个节点,即叶子节点 if (i == keyword.length() - 1) { subNode.setKeywordEnd(true); } // 将指针指向子节点 tempNode = subNode; } } /** * 过滤敏感词 * * @param text 待过滤文本 * @return java.lang.String * @date 2021/12/9 */ public String filter(String text) { // 过滤文本为空返回 null if (StringUtils.isBlank(text)) { return null; } // 指针1,刚开始指向根节点 TrieNode tempNode = ROOT_NODE; // 指针2 int start = 0; // 指针3 int end = 0; // 过滤结果 StringBuilder sb = new StringBuilder(); // 当指针3未到字符串末尾时,都进行过滤 while (end < text.length()) { // 获取待过滤的每个字符 char c = text.charAt(end); // 如果是无效符号就跳过 if (isSymbol(c) && end != text.length() - 1) { // 若指针1处于根节点,就将此符号计入结果,让指针2向下走一步 if (tempNode == ROOT_NODE) { sb.append(c); start++; } // 无论符号在开头或中间,指针3都向下走一步 end++; continue; } // 查看敏感字符对应的子节点是否存在 tempNode = tempNode.getSubNode(c); // 如果没有敏感词对应的子节点,说明不包含,因此跳过这个字符 if (tempNode == null) { // 以begin开头的字符串不是敏感词 sb.append(text.charAt(start)); // start 和 begin 都进入下一个位置 end = ++start; // 重新指向根节点 tempNode = ROOT_NODE; } else if (tempNode.isKeywordEnd()) { // 遇到敏感词结束标识,即发现敏感词,将begin~position字符串替换掉 for (int i = start; i <= end; i++) { sb.append(REPLACEMENT); } // 进入下一个位置 start = ++end; // 重新指向根节点 tempNode = ROOT_NODE; } else { // 如果找到了敏感字符,但是又没结束,因此继续检查下一个字符 // 如果当前 start 字符到 end 末尾字符没有识别出敏感词,那么就从 start 的下一个开始进行查找 if (end < text.length() - 1) { end++; } else { // 这里还是指向 start,并没有加 1,因为下一步循环就进入到 tempNode == null 判断里面了 // 因此 start 和 end 都会加 1,同时上一个字符也会被加入到sb中 end = start; } } } // 将最后一批字符计入结果 sb.append(text.substring(start)); return sb.toString(); } /** * 判断是否为符号 * * @param c 待判断符号 * @return boolean * @date 2021/12/9 */ private boolean isSymbol(Character c) { // 0x2E80~0x9FFF 是东亚文字范围 return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF); } /** * 前缀树节点 * 因为不需要用到外部类SensitiveFilter,所以设置成静态的就行,能提高性能 */ private static class TrieNode { /** * 关键词结束标识符 */ private boolean isKeywordEnd; /** * 存放子节点 * 因为子节点集合是固定的,只会往这个集合增删元素,而不会改变这个集合指针指向,所以使用final */ private final Map<Character, TrieNode> subNodes; public TrieNode() { this.isKeywordEnd = false; this.subNodes = new HashMap<>(); } public boolean isKeywordEnd() { return isKeywordEnd; } public void setKeywordEnd(boolean keywordEnd) { isKeywordEnd = keywordEnd; } /** * 添加子节点 * * @param c 节点名称 * @param node 节点 * @date 2021/12/9 */ public void addSubNodes(Character c, TrieNode node) { subNodes.put(c, node); } /** * 获取子节点 * * @param c 查询的字符 * @return top.linzeliang.community.util.SensitiveFilter.TrieNode * @date 2021/12/9 */ public TrieNode getSubNode(Character c) { return subNodes.get(c); } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpyxz.html