DFA算法实战-敏感词过滤

前言

这里的项目实战, 我们使用的是 SpringBoot2.x+JDK1.8搭建的,核心思想是借助了Hutool工具类的 WordTree。想了解更多DFA算法的实现可以参考DFA算法的实现

实战案例

1. 引入Hutool的工具类

xml 复制代码
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.18</version>
</dependency>

2. 自定义铭感词过滤处理器

Hutool工具类中定义了 SensitiveProcessor 接口,它的作用是把敏感词替换成 *

java 复制代码
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()方法实现把铭感词替换为*

java 复制代码
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.SensitiveProcessor;

/**
 * 自定义敏感词*号替代处理器
 */
public class SensitiveDefaultProcessor implements SensitiveProcessor {
    
}

SensitiveHighlightProcessor 定义了敏感词进行高亮处理,可以在铭感词前后打上对应的标签。

java 复制代码
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 是自己定义的一个敏感词模式枚举

java 复制代码
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;
}

3. 定义铭感词初始化工具

通常来说,铭感词的内容是相对固定的。我们在项目启动时可以进行预加载。当铭感词变更时,我们可以通过更新本地缓存,定时刷新的方法进行处理。

Tips: 我们可以定义初始化类,它实现了ApplicationRunner 接口。这个类中的 run方法将会在Boot项目的程序的入口方法 main 执行完毕之后被调用。在该类中可以定义一些应用程序启动后需要进行初始化的操作任务

java 复制代码
/**
 * 敏感词工具类
 */
@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() 方法后,就能实现铭感词容器的初始化

除了定义一些最基础的初始化步骤外,我们可以把一些添加铭感词,移除敏感词,查找等方法都定义在该类中。

java 复制代码
// 添加敏感词
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 工具类,就能实现敏感词的基本操作了。

相关推荐
大鱼>4 分钟前
AI+货物追踪:全链路货物可视化追踪系统
人工智能·深度学习·算法·机器学习
大江东去浪淘尽千古风流人物9 分钟前
【SLAM】slam高动态位姿估计全链路拆解
算法·面试·职场和发展·视觉里程计·slam·vio·大疆
想你依然心痛11 分钟前
量子计算对嵌入式密码学的挑战与后量子算法准备
算法·密码学·量子计算
Jerry4 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry6 小时前
LeetCode 459. 重复的子字符串
算法
海石9 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石9 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry10 小时前
LeetCode 151. 反转字符串中的单词
算法
a11177613 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜13 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai