# sensitive-word [sensitive-word](https://github.com/houbb/sensitive-word) 基于 DFA 算法实现的高性能敏感词工具。 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/sensitive-word/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/sensitive-word) [![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/houbb/sensitive-word) [![](https://img.shields.io/badge/license-Apache2-FF0080.svg)](https://github.com/houbb/sensitive-word/blob/master/LICENSE.txt) ## 创作目的 实现一款好用敏感词工具。 基于 DFA 算法实现,目前敏感词库内容收录 6W+(源文件 18W+,经过一次删减)。 后期将进行持续优化和补充敏感词库,并进一步提升算法的性能。 希望可以细化敏感词的分类,感觉工作量比较大,暂时没有进行。 ## 特性 - 6W+ 词库,且不断优化更新 - 基于 DFA 算法,性能较好 - 基于 fluent-api 实现,使用优雅简洁 - 支持敏感词的判断、返回、脱敏等常见操作 - 支持全角半角互换 - 支持英文大小写互换 - 支持数字常见形式的互换 - 支持中文繁简体互换 - 支持英文常见形式的互换 - 支持用户自定义敏感词和白名单 ## 变更日志 [CHANGE_LOG.md](https://github.com/houbb/sensitive-word/blob/master/doc/CHANGE_LOG.md) # 快速开始 ## 准备 - JDK1.7+ - Maven 3.x+ ## Maven 引入 ```xml com.github.houbb sensitive-word 0.0.14 ``` ## api 概览 `SensitiveWordHelper` 作为敏感词的工具类,核心方法如下: | 方法 | 参数 | 返回值| 说明 | |:---|:---|:---|:---| | contains(String) | 待验证的字符串 | 布尔值 | 验证字符串是否包含敏感词 | | findAll(String) | 待验证的字符串 | 字符串列表 | 返回字符串中所有敏感词 | | replace(String, char) | 使用指定的 char 替换敏感词 | 字符串 | 返回脱敏后的字符串 | | replace(String) | 使用 `*` 替换敏感词 | 字符串 | 返回脱敏后的字符串 | ## 使用实例 所有测试案例参见 [SensitiveWordHelperTest](https://github.com/houbb/sensitive-word/blob/master/src/test/java/com/github/houbb/sensitive/word/core/SensitiveWordHelperTest.java) ### 判断是否包含敏感词 ```java final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; Assert.assertTrue(SensitiveWordHelper.contains(text)); ``` ### 返回第一个敏感词 ```java final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; String word = SensitiveWordHelper.findFirst(text); Assert.assertEquals("五星红旗", word); ``` ### 返回所有敏感词 ```java final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[五星红旗, 毛主席, 天安门]", wordList.toString()); ``` ### 默认的替换策略 ```java final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; String result = SensitiveWordHelper.replace(text); Assert.assertEquals("****迎风飘扬,***的画像屹立在***前。", result); ``` ### 指定替换的内容 ```java final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; String result = SensitiveWordHelper.replace(text, '0'); Assert.assertEquals("0000迎风飘扬,000的画像屹立在000前。", result); ``` # 更多特性 后续的诸多特性,主要是针对各种针对各种情况的处理,尽可能的提升敏感词命中率。 这是一场漫长的攻防之战。 ## 忽略大小写 ```java final String text = "fuCK the bad words."; String word = SensitiveWordHelper.findFirst(text); Assert.assertEquals("fuCK", word); ``` ## 忽略半角圆角 ```java final String text = "fuck the bad words."; String word = SensitiveWordHelper.findFirst(text); Assert.assertEquals("fuck", word); ``` ## 忽略数字的写法 这里实现了数字常见形式的转换。 ```java final String text = "这个是我的微信:9⓿二肆⁹₈③⑸⒋➃㈤㊄"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[9⓿二肆⁹₈③⑸⒋➃㈤㊄]", wordList.toString()); ``` ## 忽略繁简体 ```java final String text = "我爱我的祖国和五星紅旗。"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[五星紅旗]", wordList.toString()); ``` ## 忽略英文的书写格式 ```java final String text = "Ⓕⓤc⒦ the bad words"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[Ⓕⓤc⒦]", wordList.toString()); ``` ## 忽略重复词 ```java final String text = "ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦ the bad words"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦]", wordList.toString()); ``` ## 邮箱检测 ```java final String text = "楼主好人,邮箱 sensitiveword@xx.com"; List wordList = SensitiveWordHelper.findAll(text); Assert.assertEquals("[sensitiveword@xx.com]", wordList.toString()); ``` # 特性配置 ## 说明 上面的特性默认都是开启的,有时业务需要灵活定义相关的配置特性。 所以 v0.0.14 开放了属性配置。 ## 配置方法 为了让使用更加优雅,统一使用 fluent-api 的方式定义。 用户可以使用 `SensitiveWordBs` 进行如下定义: ```java SensitiveWordBs wordBs = SensitiveWordBs.newInstance() .ignoreCase(true) .ignoreWidth(true) .ignoreNumStyle(true) .ignoreChineseStyle(true) .ignoreEnglishStyle(true) .ignoreRepeat(true) .enableNumCheck(true) .enableEmailCheck(true) .enableUrlCheck(true) .init(); final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; Assert.assertTrue(wordBs.contains(text)); ``` ## 配置说明 其中各项配置的说明如下: | 序号 | 方法 | 说明 | |:---|:---|:---| | 1 | ignoreCase | 忽略大小写 | | 2 | ignoreWidth | 忽略半角圆角 | | 3 | ignoreNumStyle | 忽略数字的写法 | | 4 | ignoreChineseStyle | 忽略中文的书写格式 | | 5 | ignoreEnglishStyle | 忽略英文的书写格式 | | 6 | ignoreRepeat | 忽略重复词 | | 7 | enableNumCheck | 是否启用数字检测。默认连续 8 位数字认为是敏感词 | | 8 | enableEmailCheck | 是有启用邮箱检测 | | 9 | enableUrlCheck | 是否启用链接检测 | # 动态加载(用户自定义) ## 情景说明 有时候我们希望将敏感词的加载设计成动态的,比如控台修改,然后可以实时生效。 v0.0.13 支持了这种特性。 ## 接口说明 为了实现这个特性,并且兼容以前的功能,我们定义了两个接口。 ### IWordDeny 接口如下,可以自定义自己的实现。 返回的列表,表示这个词是一个敏感词。 ```java /** * 拒绝出现的数据-返回的内容被当做是敏感词 * @author binbin.hou * @since 0.0.13 */ public interface IWordDeny { /** * 获取结果 * @return 结果 * @since 0.0.13 */ List deny(); } ``` 比如: ```java public class MyWordDeny implements IWordDeny { @Override public List deny() { return Arrays.asList("我的自定义敏感词"); } } ``` ### IWordAllow 接口如下,可以自定义自己的实现。 返回的列表,表示这个词不是一个敏感词。 ```java /** * 允许的内容-返回的内容不被当做敏感词 * @author binbin.hou * @since 0.0.13 */ public interface IWordAllow { /** * 获取结果 * @return 结果 * @since 0.0.13 */ List allow(); } ``` 如: ```java public class MyWordAllow implements IWordAllow { @Override public List allow() { return Arrays.asList("五星红旗"); } } ``` ## 配置使用 **接口自定义之后,当然需要指定才能生效。** 为了让使用更加优雅,我们设计了引导类 `SensitiveWordBs`。 可以通过 wordDeny() 指定敏感词,wordAllow() 指定非敏感词,通过 init() 初始化敏感词字典。 ### 系统的默认配置 ```java SensitiveWordBs wordBs = SensitiveWordBs.newInstance() .wordDeny(WordDenys.system()) .wordAllow(WordAllows.system()) .init(); final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。"; Assert.assertTrue(wordBs.contains(text)); ``` 备注:init() 对于敏感词 DFA 的构建是比较耗时的,一般建议在应用初始化的时候**只初始化一次**。而不是重复初始化! ### 指定自己的实现 我们可以测试一下自定义的实现,如下: ```java String text = "这是一个测试,我的自定义敏感词。"; SensitiveWordBs wordBs = SensitiveWordBs.newInstance() .wordDeny(new MyWordDeny()) .wordAllow(new MyWordAllow()) .init(); Assert.assertEquals("[我的自定义敏感词]", wordBs.findAll(text).toString()); ``` 这里只有 `我的自定义敏感词` 是敏感词,而 `测试` 不是敏感词。 当然,这里是全部使用我们自定义的实现,一般建议使用系统的默认配置+自定义配置。 可以使用下面的方式。 ### 同时配置多个 - 多个敏感词 `WordDenys.chains()` 方法,将多个实现合并为同一个 IWordDeny。 - 多个白名单 `WordAllows.chains()` 方法,将多个实现合并为同一个 IWordAllow。 例子: ```java String text = "这是一个测试。我的自定义敏感词。"; IWordDeny wordDeny = WordDenys.chains(WordDenys.system(), new MyWordDeny()); IWordAllow wordAllow = WordAllows.chains(WordAllows.system(), new MyWordAllow()); SensitiveWordBs wordBs = SensitiveWordBs.newInstance() .wordDeny(wordDeny) .wordAllow(wordAllow) .init(); Assert.assertEquals("[我的自定义敏感词]", wordBs.findAll(text).toString()); ``` 这里都是同时使用了系统默认配置,和自定义的配置。 # 后期 road-map - 停顿词 - 同音字处理 - 形近字处理 - 文字镜像翻转 - 文字降噪处理 - 敏感词标签支持 - 邮箱后缀检测 # 拓展阅读 [敏感词工具实现思路](https://houbb.github.io/2020/01/07/sensitive-word) [DFA 算法讲解](https://houbb.github.io/2020/01/07/sensitive-word-dfa) [敏感词库优化流程](https://houbb.github.io/2020/01/07/sensitive-word-slim) [停止词的思考记录](https://houbb.github.io/2020/01/07/sensitive-word-stopword)