sensitive-word 基于 DFA ç®—æ³•å®žçŽ°çš„é«˜æ€§èƒ½æ•æ„Ÿè¯å·¥å…·ã€‚
如果有一些疑难æ‚症,å¯ä»¥åŠ å…¥ï¼šæŠ€æœ¯äº¤æµç¾¤
sensitive-word-admin 是对应的控å°çš„应用,目å‰åŠŸèƒ½å¤„äºŽåˆæœŸå¼€å‘ä¸ï¼ŒMVP 版本å¯ç”¨ã€‚
å®žçŽ°ä¸€æ¬¾å¥½ç”¨æ•æ„Ÿè¯å·¥å…·ã€‚
基于 DFA ç®—æ³•å®žçŽ°ï¼Œç›®å‰æ•感è¯åº“内容收录 6W+ï¼ˆæºæ–‡ä»¶ 18W+,ç»è¿‡ä¸€æ¬¡åˆ å‡ï¼‰ã€‚
åŽæœŸå°†è¿›è¡ŒæŒç»ä¼˜åŒ–å’Œè¡¥å……æ•æ„Ÿè¯åº“ï¼Œå¹¶è¿›ä¸€æ¥æå‡ç®—法的性能。
希望å¯ä»¥ç»†åŒ–æ•æ„Ÿè¯çš„åˆ†ç±»ï¼Œæ„Ÿè§‰å·¥ä½œé‡æ¯”较大,暂时没有进行。
-
6W+ è¯åº“ï¼Œä¸”ä¸æ–优化更新
-
基于 fluent-api 实现,使用优雅简æ´
全角åŠè§’互æ¢ã€è‹±æ–‡å¤§å°å†™äº’æ¢ã€æ•°å—常è§å½¢å¼çš„互æ¢ã€ä¸æ–‡ç¹ç®€ä½“互æ¢ã€è‹±æ–‡å¸¸è§å½¢å¼çš„互æ¢ã€å¿½ç•¥é‡å¤è¯ç‰
-
æ”¯æŒæ•æ„Ÿè¯æ£€æµ‹ã€é‚®ç®±æ£€æµ‹ã€æ•°å—检测ã€ç½‘倿£€æµ‹ã€IPV4ç‰
-
æ”¯æŒæ•°æ®çš„æ•°æ®åŠ¨æ€æ›´æ–°ï¼ˆç”¨æˆ·è‡ªå®šä¹‰ï¼‰ï¼Œå®žæ—¶ç”Ÿæ•ˆ
- 针对å•个è¯çš„æ–°å¢ž/åˆ é™¤ï¼Œæ— éœ€å…¨é‡åˆå§‹åŒ–
- 新增 allow/deny 空实现
- 新增数å—+英文的全è¯åŒ¹é…实现
-
ä¿®æ£ç™½åå•较长,包å«äº†é»‘åå•,导致白åå•ä¸ç¬¦åˆé¢„期的场景。
-
新增了白åå•å•个的编辑æ“作
æœ‰æ—¶å€™æ•æ„Ÿè¯æœ‰ä¸€ä¸ªæŽ§å°ï¼Œé…置起æ¥ä¼šæ›´åŠ çµæ´»æ–¹ä¾¿ã€‚
梳ç†äº†å¤§é‡çš„æ•æ„Ÿè¯æ ‡ç¾æ–‡ä»¶ï¼Œå¯ä»¥è®©æˆ‘ä»¬çš„æ•æ„Ÿè¯æ›´åŠ æ–¹ä¾¿ã€‚
这两个资料阅读å¯åœ¨ä¸‹æ–¹æ–‡ç« 获å–:
-
JDK1.8+
-
Maven 3.x+
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.21.0</version>
</dependency>
SensitiveWordHelper
ä½œä¸ºæ•æ„Ÿè¯çš„å·¥å…·ç±»ï¼Œæ ¸å¿ƒæ–¹æ³•å¦‚ä¸‹ï¼š
方法 | 傿•° | 返回值 | 说明 |
---|---|---|---|
contains(String) | 待验è¯çš„å—符串 | 布尔值 | 验è¯å—符串是å¦åŒ…嫿•æ„Ÿè¯ |
replace(String, ISensitiveWordReplace) | 使用指定的替æ¢ç–ç•¥æ›¿æ¢æ•æ„Ÿè¯ | å—符串 | 返回脱æ•åŽçš„å—符串 |
replace(String, char) | 使用指定的 char æ›¿æ¢æ•æ„Ÿè¯ | å—符串 | 返回脱æ•åŽçš„å—符串 |
replace(String) | 使用 * æ›¿æ¢æ•æ„Ÿè¯ |
å—符串 | 返回脱æ•åŽçš„å—符串 |
findAll(String) | 待验è¯çš„å—符串 | å—符串列表 | 返回å—ç¬¦ä¸²ä¸æ‰€æœ‰æ•æ„Ÿè¯ |
findFirst(String) | 待验è¯çš„å—符串 | å—符串 | 返回å—符串ä¸ç¬¬ä¸€ä¸ªæ•æ„Ÿè¯ |
findAll(String, IWordResultHandler) | IWordResultHandler 结果处ç†ç±» | å—符串列表 | 返回å—ç¬¦ä¸²ä¸æ‰€æœ‰æ•æ„Ÿè¯ |
findFirst(String, IWordResultHandler) | IWordResultHandler 结果处ç†ç±» | å—符串 | 返回å—符串ä¸ç¬¬ä¸€ä¸ªæ•æ„Ÿè¯ |
tags(String) | èŽ·å–æ•感è¯çš„æ ‡ç¾ | æ•æ„Ÿè¯å—符串 | è¿”å›žæ•æ„Ÿè¯çš„æ ‡ç¾åˆ—表 |
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
Assert.assertTrue(SensitiveWordHelper.contains(text));
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
String word = SensitiveWordHelper.findFirst(text);
Assert.assertEquals("五星红旗", word);
SensitiveWordHelper.findFirst(text) ç‰ä»·äºŽï¼š
String word = SensitiveWordHelper.findFirst(text, WordResultHandlers.word());
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[五星红旗, 毛主å¸, 天安门]", wordList.toString());
è¿”å›žæ‰€æœ‰æ•æ„Ÿè¯ç”¨æ³•上类似于 SensitiveWordHelper.findFirst()ï¼ŒåŒæ ·ä¹Ÿæ”¯æŒæŒ‡å®šç»“果处ç†ç±»ã€‚
SensitiveWordHelper.findAll(text) ç‰ä»·äºŽï¼š
List<String> wordList = SensitiveWordHelper.findAll(text, WordResultHandlers.word());
WordResultHandlers.raw() å¯ä»¥ä¿ç•™å¯¹åº”çš„ä¸‹æ ‡ä¿¡æ¯ã€ç±»åˆ«ä¿¡æ¯ï¼š
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
// é»˜è®¤æ•æ„Ÿè¯æ ‡ç¾ä¸ºç©º
List<WordTagsDto> wordList1 = SensitiveWordHelper.findAll(text, WordResultHandlers.wordTags());
Assert.assertEquals("[WordTagsDto{word='五星红旗', tags=[]}, WordTagsDto{word='毛主å¸', tags=[]}, WordTagsDto{word='天安门', tags=[]}]", wordList1.toString());
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
String result = SensitiveWordHelper.replace(text);
Assert.assertEquals("****迎风飘扬,***的画åƒå±¹ç«‹åœ¨***å‰ã€‚", result);
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
String result = SensitiveWordHelper.replace(text, '0');
Assert.assertEquals("0000迎风飘扬,000的画åƒå±¹ç«‹åœ¨000å‰ã€‚", result);
V0.2.0 支æŒè¯¥ç‰¹æ€§ã€‚
场景说明:有时候我们希望ä¸åŒçš„æ•æ„Ÿè¯æœ‰ä¸åŒçš„æ›¿æ¢ç»“æžœã€‚æ¯”å¦‚ã€æ¸¸æˆã€‘替æ¢ä¸ºã€ç”µå竞技】,ã€å¤±ä¸šã€‘替æ¢ä¸ºã€çµæ´»å°±ä¸šã€‘。
诚然,æå‰ä½¿ç”¨å—符串的æ£åˆ™æ›¿æ¢ä¹Ÿå¯ä»¥ï¼Œä¸è¿‡æ€§èƒ½ä¸€èˆ¬ã€‚
使用例å:
/**
* 自定替æ¢ç–ç•¥
* @since 0.2.0
*/
@Test
public void defineReplaceTest() {
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
ISensitiveWordReplace replace = new MySensitiveWordReplace();
String result = SensitiveWordHelper.replace(text, replace);
Assert.assertEquals("国家旗帜迎风飘扬,教员的画åƒå±¹ç«‹åœ¨***å‰ã€‚", result);
}
å…¶ä¸ MySensitiveWordReplace
是我们自定义的替æ¢ç–略,实现如下:
public class MyWordReplace implements IWordReplace {
@Override
public void replace(StringBuilder stringBuilder, final char[] rawChars, IWordResult wordResult, IWordContext wordContext) {
String sensitiveWord = InnerWordCharUtils.getString(rawChars, wordResult);
// 自定义ä¸åŒçš„æ•æ„Ÿè¯æ›¿æ¢ç–略,å¯ä»¥ä»Žæ•°æ®åº“ç‰åœ°æ–¹è¯»å–
if("五星红旗".equals(sensitiveWord)) {
stringBuilder.append("国家旗帜");
} else if("毛主å¸".equals(sensitiveWord)) {
stringBuilder.append("教员");
} else {
// 其他默认使用 * 代替
int wordLength = wordResult.endIndex() - wordResult.startIndex();
for(int i = 0; i < wordLength; i++) {
stringBuilder.append('*');
}
}
}
}
我们针对其ä¸çš„部分è¯åšå›ºå®šæ˜ 射处ç†ï¼Œå…¶ä»–的默认转æ¢ä¸º *
。
IWordResultHandler å¯ä»¥å¯¹æ•感è¯çš„结果进行处ç†ï¼Œå…许用户自定义。
å†…ç½®å®žçŽ°è§ WordResultHandlers
工具类:
- WordResultHandlers.word()
åªä¿ç•™æ•感è¯å•è¯æœ¬èº«ã€‚
- WordResultHandlers.raw()
ä¿ç•™æ•感è¯ç›¸å…³ä¿¡æ¯ï¼ŒåŒ…嫿•感è¯çš„开始和结æŸä¸‹æ ‡ã€‚
- WordResultHandlers.wordTags()
åŒæ—¶ä¿ç•™å•è¯ï¼Œå’Œå¯¹åº”çš„è¯æ ‡ç¾ä¿¡æ¯ã€‚
所有测试案例å‚è§ SensitiveWordHelperTest
1)基本例å
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[五星红旗, 毛主å¸, 天安门]", wordList.toString());
List<String> wordList2 = SensitiveWordHelper.findAll(text, WordResultHandlers.word());
Assert.assertEquals("[五星红旗, 毛主å¸, 天安门]", wordList2.toString());
List<IWordResult> wordList3 = SensitiveWordHelper.findAll(text, WordResultHandlers.raw());
Assert.assertEquals("[WordResult{startIndex=0, endIndex=4}, WordResult{startIndex=9, endIndex=12}, WordResult{startIndex=18, endIndex=21}]", wordList3.toString());
- wordTags 例å
我们在 dict_tag_test.txt
æ–‡ä»¶ä¸æŒ‡å®šå¯¹åº”è¯çš„æ ‡ç¾ä¿¡æ¯ã€‚
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
// é»˜è®¤æ•æ„Ÿè¯æ ‡ç¾ä¸ºç©º
List<WordTagsDto> wordList1 = SensitiveWordHelper.findAll(text, WordResultHandlers.wordTags());
Assert.assertEquals("[WordTagsDto{word='五星红旗', tags=[]}, WordTagsDto{word='毛主å¸', tags=[]}, WordTagsDto{word='天安门', tags=[]}]", wordList1.toString());
List<WordTagsDto> wordList2 = SensitiveWordBs.newInstance()
.wordTag(WordTags.file("dict_tag_test.txt"))
.init()
.findAll(text, WordResultHandlers.wordTags());
Assert.assertEquals("[WordTagsDto{word='五星红旗', tags=[政治, 国家]}, WordTagsDto{word='毛主å¸', tags=[政治, 伟人, 国家]}, WordTagsDto{word='天安门', tags=[政治, 国家, 地å€]}]", wordList2.toString());
åŽç»çš„è¯¸å¤šç‰¹æ€§ï¼Œä¸»è¦æ˜¯é’ˆå¯¹å„ç§é’ˆå¯¹å„ç§æƒ…况的处ç†ï¼Œå°½å¯èƒ½çš„æå‡æ•感è¯å‘½ä¸çŽ‡ã€‚
这是一场漫长的攻防之战。
final String text = "fuCK the bad words.";
String word = SensitiveWordHelper.findFirst(text);
Assert.assertEquals("fuCK", word);
final String text = "fuck the bad words.";
String word = SensitiveWordHelper.findFirst(text);
Assert.assertEquals("fuck", word);
这里实现了数å—常è§å½¢å¼çš„转æ¢ã€‚
final String text = "这个是我的微信:9⓿二肆â¹â‚ˆâ‘¢â‘¸â’‹âžƒãˆ¤ãŠ„";
List<String> wordList = SensitiveWordBs.newInstance().enableNumCheck(true).init().findAll(text);
Assert.assertEquals("[9⓿二肆â¹â‚ˆâ‘¢â‘¸â’‹âžƒãˆ¤ãŠ„]", wordList.toString());
final String text = "我爱我的祖国和五星紅旗。";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[五星紅旗]", wordList.toString());
final String text = "Ⓕⓤc⒦ the bad words";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[Ⓕⓤc⒦]", wordList.toString());
final String text = "ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦ the bad words";
List<String> wordList = SensitiveWordBs.newInstance()
.ignoreRepeat(true)
.init()
.findAll(text);
Assert.assertEquals("[ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦]", wordList.toString());
邮箱ç‰ä¸ªäººä¿¡æ¯ï¼Œé»˜è®¤æœªå¯ç”¨ã€‚
final String text = "楼主好人,邮箱 sensitiveword@xx.com";
List<String> wordList = SensitiveWordBs.newInstance().enableEmailCheck(true).init().findAll(text);
Assert.assertEquals("[sensitiveword@xx.com]", wordList.toString());
一般用于过滤手机å·/QQç‰å¹¿å‘Šä¿¡æ¯ï¼Œé»˜è®¤æœªå¯ç”¨ã€‚
V0.2.1 之åŽï¼Œæ”¯æŒé€šè¿‡ numCheckLen(长度)
自定义检测的长度。
final String text = "ä½ æ‡‚å¾—ï¼š12345678";
// 默认检测 8 ä½
List<String> wordList = SensitiveWordBs.newInstance()
.enableNumCheck(true)
.init().findAll(text);
Assert.assertEquals("[12345678]", wordList.toString());
// 指定数å—的长度,é¿å…误æ€
List<String> wordList2 = SensitiveWordBs.newInstance()
.enableNumCheck(true)
.numCheckLen(9)
.init()
.findAll(text);
Assert.assertEquals("[]", wordList2.toString());
用于过滤常è§çš„网å€ä¿¡æ¯ï¼Œé»˜è®¤æœªå¯ç”¨ã€‚
v0.18.0 优化 URL æ£€æµ‹ï¼Œæ›´åŠ ä¸¥æ ¼ï¼Œé™ä½Žè¯¯åˆ¤çއ
final String text = "点击链接 https://www.baidu.com æŸ¥çœ‹ç”æ¡ˆ";
final SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance().enableUrlCheck(true).init();
List<String> wordList = sensitiveWordBs.findAll(text);
Assert.assertEquals("[https://www.baidu.com]", wordList.toString());
Assert.assertEquals("点击链接 ********************* æŸ¥çœ‹ç”æ¡ˆ", sensitiveWordBs.replace(text));
v0.17.0 支æŒ
é¿å…用户通过 ip ç»•è¿‡ç½‘å€æ£€æµ‹ç‰ï¼Œé»˜è®¤æœªå¯ç”¨ã€‚
final String text = "ä¸ªäººç½‘ç«™ï¼Œå¦‚æžœç½‘å€æ‰“ä¸å¼€å¯ä»¥è®¿é—® 127.0.0.1。";
final SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance().enableIpv4Check(true).init();
List<String> wordList = sensitiveWordBs.findAll(text);
Assert.assertEquals("[127.0.0.1]", wordList.toString());
上é¢çš„特性默认都是开å¯çš„,有时业务需è¦çµæ´»å®šä¹‰ç›¸å…³çš„é…置特性。
所以 v0.0.14 开放了属性é…置。
ä¸ºäº†è®©ä½¿ç”¨æ›´åŠ ä¼˜é›…ï¼Œç»Ÿä¸€ä½¿ç”¨ fluent-api 的方å¼å®šä¹‰ã€‚
用户å¯ä»¥ä½¿ç”¨ SensitiveWordBs
进行如下定义:
SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
.ignoreCase(true)
.ignoreWidth(true)
.ignoreNumStyle(true)
.ignoreChineseStyle(true)
.ignoreEnglishStyle(true)
.ignoreRepeat(false)
.enableNumCheck(false)
.enableEmailCheck(false)
.enableUrlCheck(false)
.enableIpv4Check(false)
.enableWordCheck(true)
.numCheckLen(8)
.wordTag(WordTags.none())
.charIgnore(SensitiveWordCharIgnores.defaults())
.wordResultCondition(WordResultConditions.alwaysTrue())
.init();
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
Assert.assertTrue(wordBs.contains(text));
å…¶ä¸å„项é…置的说明如下:
åºå· | 方法 | 说明 | 默认值 |
---|---|---|---|
1 | ignoreCase | 忽略大å°å†™ | true |
2 | ignoreWidth | 忽略åŠè§’圆角 | true |
3 | ignoreNumStyle | 忽略数å—的写法 | true |
4 | ignoreChineseStyle | å¿½ç•¥ä¸æ–‡çš„ä¹¦å†™æ ¼å¼ | true |
5 | ignoreEnglishStyle | å¿½ç•¥è‹±æ–‡çš„ä¹¦å†™æ ¼å¼ | true |
6 | ignoreRepeat | 忽略é‡å¤è¯ | false |
7 | enableNumCheck | 是å¦å¯ç”¨æ•°å—检测。 | false |
8 | enableEmailCheck | 是有å¯ç”¨é‚®ç®±æ£€æµ‹ | false |
9 | enableUrlCheck | 是å¦å¯ç”¨é“¾æŽ¥æ£€æµ‹ | false |
10 | enableIpv4Check | 是å¦å¯ç”¨IPv4检测 | false |
11 | enableWordCheck | 是å¦å¯ç”¨æ•感å•è¯æ£€æµ‹ | true |
12 | numCheckLen | æ•°å—æ£€æµ‹ï¼Œè‡ªå®šä¹‰æŒ‡å®šé•¿åº¦ã€‚ | 8 |
13 | wordTag | è¯å¯¹åº”çš„æ ‡ç¾ | none |
14 | charIgnore | 忽略的å—符 | none |
15 | wordResultCondition | 针对匹é…çš„æ•æ„Ÿè¯é¢å¤–åŠ å·¥ï¼Œæ¯”å¦‚å¯ä»¥é™åˆ¶è‹±æ–‡å•è¯å¿…é¡»å…¨åŒ¹é… | æ’为真 |
v0.16.1 开始支æŒï¼Œæœ‰æ—¶å€™æˆ‘们需è¦é‡Šæ”¾å†…å˜ï¼Œå¯ä»¥å¦‚下:
SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
.init();
// åŽç»å› ä¸ºä¸€äº›åŽŸå› ç§»é™¤äº†å¯¹åº”ä¿¡æ¯ï¼Œå¸Œæœ›é‡Šæ”¾å†…å˜ã€‚
wordBs.destroy();
使用场景:在åˆå§‹åŒ–之åŽï¼Œæˆ‘们希望针对å•个è¯çš„æ–°å¢ž/åˆ é™¤ï¼Œè€Œä¸æ˜¯å®Œå…¨é‡æ–°åˆå§‹åŒ–。这个特性就是为æ¤å‡†å¤‡çš„。
支æŒç‰ˆæœ¬ï¼šv0.19.0
addWord(word)
æ–°å¢žæ•æ„Ÿè¯ï¼Œæ”¯æŒå•个è¯/集åˆ
removeWord(word)
åˆ é™¤æ•æ„Ÿè¯ï¼Œæ”¯æŒå•个è¯/集åˆ
final String text = "æµ‹è¯•ä¸€ä¸‹æ–°å¢žæ•æ„Ÿè¯ï¼ŒéªŒè¯ä¸€ä¸‹åˆ 除和新增对ä¸å¯¹";
SensitiveWordBs sensitiveWordBs =
SensitiveWordBs.newInstance()
.wordAllow(WordAllows.empty())
.wordDeny(WordDenys.empty())
.init();
// 当å‰
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// 新增å•个
sensitiveWordBs.addWord("测试");
sensitiveWordBs.addWord("新增");
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤å•个
sensitiveWordBs.removeWord("新增");
Assert.assertEquals("[测试]", sensitiveWordBs.findAll(text).toString());
sensitiveWordBs.removeWord("测试");
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// 新增集åˆ
sensitiveWordBs.addWord(Arrays.asList("新增", "测试"));
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤é›†åˆ
sensitiveWordBs.removeWord(Arrays.asList("新增", "测试"));
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// 新增数组
sensitiveWordBs.addWord("新增", "测试");
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤é›†åˆ
sensitiveWordBs.removeWord("新增", "测试");
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
使用场景:在åˆå§‹åŒ–之åŽï¼Œæˆ‘们希望针对å•个è¯çš„æ–°å¢ž/åˆ é™¤ï¼Œè€Œä¸æ˜¯å®Œå…¨é‡æ–°åˆå§‹åŒ–。这个特性就是为æ¤å‡†å¤‡çš„。
支æŒç‰ˆæœ¬ï¼šv0.21.0
addWordAllow(word)
新增白åå•,支æŒå•个è¯/集åˆ
removeWordAllow(word)
åˆ é™¤ç™½åå•,支æŒå•个è¯/集åˆ
final String text = "æµ‹è¯•ä¸€ä¸‹æ–°å¢žæ•æ„Ÿè¯ç™½åå•,验è¯ä¸€ä¸‹åˆ 除和新增对ä¸å¯¹";
SensitiveWordBs sensitiveWordBs =
SensitiveWordBs.newInstance()
.wordAllow(WordAllows.empty())
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Arrays.asList("测试", "新增");
}
})
.init();
// 当å‰
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// 新增å•个
sensitiveWordBs.addWordAllow("测试");
sensitiveWordBs.addWordAllow("新增");
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤å•个
sensitiveWordBs.removeWordAllow("测试");
Assert.assertEquals("[测试]", sensitiveWordBs.findAll(text).toString());
sensitiveWordBs.removeWordAllow("新增");
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// 新增集åˆ
sensitiveWordBs.addWordAllow(Arrays.asList("新增", "测试"));
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤é›†åˆ
sensitiveWordBs.removeWordAllow(Arrays.asList("新增", "测试"));
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
// 新增数组
sensitiveWordBs.addWordAllow("新增", "测试");
Assert.assertEquals("[]", sensitiveWordBs.findAll(text).toString());
// åˆ é™¤é›†åˆ
sensitiveWordBs.removeWordAllow("新增", "测试");
Assert.assertEquals("[测试, 新增, 新增]", sensitiveWordBs.findAll(text).toString());
支æŒç‰ˆæœ¬ï¼šv0.13.0
有时候我们å¯èƒ½å¸Œæœ›å¯¹åŒ¹é…çš„æ•æ„Ÿè¯è¿›ä¸€æ¥é™åˆ¶ï¼Œæ¯”如虽然我们定义了ã€avã€‘ä½œä¸ºæ•æ„Ÿè¯ï¼Œä½†æ˜¯ä¸å¸Œæœ›ã€have】被匹é…。
å°±å¯ä»¥è‡ªå®šä¹‰å®žçް wordResultCondition 接å£ï¼Œå®žçŽ°è‡ªå·±çš„ç–略。
系统内置的ç–略在 WordResultConditions#alwaysTrue()
æ’为真,WordResultConditions#englishWordMatch()
åˆ™è¦æ±‚英文必须全è¯åŒ¹é…。
WordResultConditions 工具类å¯ä»¥èŽ·å–匹é…ç–ç•¥
实现 | 说明 | 支æŒç‰ˆæœ¬ |
---|---|---|
alwaysTrue | æ’为真 | |
englishWordMatch | 英文å•è¯å…¨è¯åŒ¹é… | v0.13.0 |
englishWordNumMatch | 英文å•è¯/æ•°å—å…¨è¯åŒ¹é… | v0.20.0 |
原始的默认情况:
final String text = "I have a nice day。";
List<String> wordList = SensitiveWordBs.newInstance()
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Collections.singletonList("av");
}
})
.wordResultCondition(WordResultConditions.alwaysTrue())
.init()
.findAll(text);
Assert.assertEquals("[av]", wordList.toString());
我们å¯ä»¥æŒ‡å®šä¸ºè‹±æ–‡å¿…须全è¯åŒ¹é…。
final String text = "I have a nice day。";
List<String> wordList = SensitiveWordBs.newInstance()
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Collections.singletonList("av");
}
})
.wordResultCondition(WordResultConditions.englishWordMatch())
.init()
.findAll(text);
Assert.assertEquals("[]", wordList.toString());
当然å¯ä»¥æ ¹æ®éœ€è¦å®žçŽ°æ›´åŠ å¤æ‚çš„ç–略。
æˆ‘ä»¬çš„æ•æ„Ÿè¯ä¸€èˆ¬éƒ½æ˜¯æ¯”较连ç»çš„,比如ã€å‚»å¸½ã€‘
é‚£å°±æœ‰å¤§èªæ˜Žå‘现,å¯ä»¥åœ¨ä¸é—´åŠ ä¸€äº›å—符,比如ã€å‚»!@#$å¸½ã€‘è·³è¿‡æ£€æµ‹ï¼Œä½†æ˜¯éª‚äººç‰æ”»å‡»åŠ›ä¸å‡ã€‚
那么,如何应对这些类似的场景呢?
我们å¯ä»¥æŒ‡å®šç‰¹æ®Šå—符的跳过集åˆï¼Œå¿½ç•¥æŽ‰è¿™äº›æ— æ„义的å—符å³å¯ã€‚
v0.11.0 开始支æŒ
å…¶ä¸ charIgnore 对应的å—符ç–略,用户å¯ä»¥è‡ªè¡Œçµæ´»å®šä¹‰ã€‚
final String text = "傻@冒,狗+东西";
//é»˜è®¤å› ä¸ºæœ‰ç‰¹æ®Šå—ç¬¦åˆ†å‰²ï¼Œæ— æ³•è¯†åˆ«
List<String> wordList = SensitiveWordBs.newInstance().init().findAll(text);
Assert.assertEquals("[]", wordList.toString());
// 指定忽略的å—符ç–略,å¯è‡ªè¡Œå®žçŽ°ã€‚
List<String> wordList2 = SensitiveWordBs.newInstance()
.charIgnore(SensitiveWordCharIgnores.specialChars())
.init()
.findAll(text);
Assert.assertEquals("[傻@冒, 狗+东西]", wordList2.toString());
æœ‰æ—¶å€™æˆ‘ä»¬å¸Œæœ›å¯¹æ•æ„Ÿè¯åŠ ä¸€ä¸ªåˆ†ç±»æ ‡ç¾ï¼šæ¯”å¦‚ç¤¾æƒ…ã€æš´/力ç‰ç‰ã€‚
è¿™æ ·åŽç»å¯ä»¥æŒ‰ç…§æ ‡ç¾ç‰è¿›è¡Œæ›´å¤šç‰¹æ€§æ“作,比如åªå¤„ç†æŸä¸€ç±»çš„æ ‡ç¾ã€‚
支æŒç‰ˆæœ¬ï¼šv0.10.0
è¿™é‡Œåªæ˜¯ä¸€ä¸ªæŠ½è±¡çš„æŽ¥å£ï¼Œç”¨æˆ·å¯ä»¥è‡ªè¡Œå®šä¹‰å®žçŽ°ã€‚æ¯”å¦‚ä»Žæ•°æ®åº“查询ç‰ã€‚
public interface IWordTag {
/**
* æŸ¥è¯¢æ ‡ç¾åˆ—表
* @param word è„è¯
* @return 结果
*/
Set<String> getTag(String word);
}
我们å¯ä»¥è‡ªå®šä¹‰ dict æ ‡ç¾æ–‡ä»¶ï¼Œé€šè¿‡ WordTags.file() 创建一个 WordTag 实现。
- dict_tag_test.txt
五星红旗 政治,国家
æ ¼å¼å¦‚下:
æ•æ„Ÿè¯ tag1,tag2
具体的效果如下,在引导类设置一下å³å¯ã€‚
默认的 wordTag 是空的。
String filePath = "dict_tag_test.txt";
IWordTag wordTag = WordTags.file(filePath);
SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.wordTag(wordTag)
.init();
Assert.assertEquals("[政治, 国家]", sensitiveWordBs.tags("五星红旗").toString());;
åŽç»ä¼šè€ƒè™‘å¼•å…¥ä¸€ä¸ªå†…ç½®çš„æ ‡ç¾æ–‡ä»¶ç–略。
梳ç†äº†å¤§é‡çš„æ•æ„Ÿè¯æ ‡ç¾æ–‡ä»¶ï¼Œå¯ä»¥è®©æˆ‘ä»¬çš„æ•æ„Ÿè¯æ›´åŠ æ–¹ä¾¿ã€‚
这两个资料阅读å¯åœ¨ä¸‹æ–¹æ–‡ç« 获å–:
æœ‰æ—¶å€™æˆ‘ä»¬å¸Œæœ›å°†æ•æ„Ÿè¯çš„åŠ è½½è®¾è®¡æˆåЍæ€çš„,比如控å°ä¿®æ”¹ï¼Œç„¶åŽå¯ä»¥å®žæ—¶ç”Ÿæ•ˆã€‚
v0.0.13 支æŒäº†è¿™ç§ç‰¹æ€§ã€‚
为了实现这个特性,并且兼容以å‰çš„功能,我们定义了两个接å£ã€‚
接å£å¦‚下,å¯ä»¥è‡ªå®šä¹‰è‡ªå·±çš„实现。
è¿”å›žçš„åˆ—è¡¨ï¼Œè¡¨ç¤ºè¿™ä¸ªè¯æ˜¯ä¸€ä¸ªæ•感è¯ã€‚
/**
* æ‹’ç»å‡ºçŽ°çš„æ•°æ®-è¿”å›žçš„å†…å®¹è¢«å½“åšæ˜¯æ•感è¯
* @author binbin.hou
* @since 0.0.13
*/
public interface IWordDeny {
/**
* 获å–结果
* @return 结果
* @since 0.0.13
*/
List<String> deny();
}
比如:
public class MyWordDeny implements IWordDeny {
@Override
public List<String> deny() {
return Arrays.asList("æˆ‘çš„è‡ªå®šä¹‰æ•æ„Ÿè¯");
}
}
接å£å¦‚下,å¯ä»¥è‡ªå®šä¹‰è‡ªå·±çš„实现。
返回的列表,表示这个è¯ä¸æ˜¯ä¸€ä¸ªæ•感è¯ã€‚
/**
* å…许的内容-返回的内容ä¸è¢«å½“åšæ•感è¯
* @author binbin.hou
* @since 0.0.13
*/
public interface IWordAllow {
/**
* 获å–结果
* @return 结果
* @since 0.0.13
*/
List<String> allow();
}
如:
public class MyWordAllow implements IWordAllow {
@Override
public List<String> allow() {
return Arrays.asList("五星红旗");
}
}
接å£è‡ªå®šä¹‰ä¹‹åŽï¼Œå½“ç„¶éœ€è¦æŒ‡å®šæ‰èƒ½ç”Ÿæ•ˆã€‚
ä¸ºäº†è®©ä½¿ç”¨æ›´åŠ ä¼˜é›…ï¼Œæˆ‘ä»¬è®¾è®¡äº†å¼•å¯¼ç±» SensitiveWordBs
。
å¯ä»¥é€šè¿‡ wordDeny() æŒ‡å®šæ•æ„Ÿè¯ï¼ŒwordAllow() æŒ‡å®šéžæ•感è¯ï¼Œé€šè¿‡ init() åˆå§‹åŒ–æ•æ„Ÿè¯å—典。
SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
.wordDeny(WordDenys.defaults())
.wordAllow(WordAllows.defaults())
.init();
final String text = "五星红旗迎风飘扬,毛主å¸çš„ç”»åƒå±¹ç«‹åœ¨å¤©å®‰é—¨å‰ã€‚";
Assert.assertTrue(wordBs.contains(text));
备注:init() å¯¹äºŽæ•æ„Ÿè¯ DFA 的构建是比较耗时的,一般建议在应用åˆå§‹åŒ–的时候åªåˆå§‹åŒ–ä¸€æ¬¡ã€‚è€Œä¸æ˜¯é‡å¤åˆå§‹åŒ–ï¼
我们å¯ä»¥æµ‹è¯•一下自定义的实现,如下:
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。
例å:
String text = "è¿™æ˜¯ä¸€ä¸ªæµ‹è¯•ã€‚æˆ‘çš„è‡ªå®šä¹‰æ•æ„Ÿè¯ã€‚";
IWordDeny wordDeny = WordDenys.chains(WordDenys.defaults(), new MyWordDeny());
IWordAllow wordAllow = WordAllows.chains(WordAllows.defaults(), new MyWordAllow());
SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
.wordDeny(wordDeny)
.wordAllow(wordAllow)
.init();
Assert.assertEquals("[æˆ‘çš„è‡ªå®šä¹‰æ•æ„Ÿè¯]", wordBs.findAll(text).toString());
è¿™é‡Œéƒ½æ˜¯åŒæ—¶ä½¿ç”¨äº†ç³»ç»Ÿé»˜è®¤é…置,和自定义的é…置。
注æ„:我们åˆå§‹åŒ–了新的 wordBs,那么用新的 wordBs 去判æ–ã€‚è€Œä¸æ˜¯ç”¨ä»¥å‰çš„ SensitiveWordHelper
工具方法,工具方法é…置是默认的ï¼
实际使用ä¸ï¼Œæ¯”如å¯ä»¥åœ¨é¡µé¢é…置修改,然åŽå®žæ—¶ç”Ÿæ•ˆã€‚
æ•°æ®å˜å‚¨åœ¨æ•°æ®åº“ä¸ï¼Œä¸‹é¢æ˜¯ä¸€ä¸ªä¼ªä»£ç 的例å,å¯ä»¥å‚考 SpringSensitiveWordConfig.java
è¦æ±‚,版本 v0.0.15 åŠå…¶ä»¥ä¸Šã€‚
简化伪代ç 如下,数æ®çš„æºå¤´ä¸ºæ•°æ®åº“。
MyDdWordAllow å’Œ MyDdWordDeny 是基于数æ®åº“为æºå¤´çš„自定义实现类。
@Configuration
public class SpringSensitiveWordConfig {
@Autowired
private MyDdWordAllow myDdWordAllow;
@Autowired
private MyDdWordDeny myDdWordDeny;
/**
* åˆå§‹åŒ–引导类
* @return åˆå§‹åŒ–引导类
* @since 1.0.0
*/
@Bean
public SensitiveWordBs sensitiveWordBs() {
SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.wordAllow(WordAllows.chains(WordAllows.defaults(), myDdWordAllow))
.wordDeny(myDdWordDeny)
// å„ç§å…¶ä»–é…ç½®
.init();
return sensitiveWordBs;
}
}
æ•æ„Ÿè¯åº“çš„åˆå§‹åŒ–较为耗时,建议程åºå¯åŠ¨æ—¶åšä¸€æ¬¡ init åˆå§‹åŒ–。
为了ä¿è¯æ•感è¯ä¿®æ”¹å¯ä»¥å®žæ—¶ç”Ÿæ•ˆä¸”ä¿è¯æŽ¥å£çš„å°½å¯èƒ½ç®€åŒ–,æ¤å¤„没有新增 add/remove 的方法。
而是在调用 sensitiveWordBs.init()
çš„æ—¶å€™ï¼Œæ ¹æ® IWordDeny+IWordAllow 釿–°æž„å»ºæ•æ„Ÿè¯åº“。
å› ä¸ºåˆå§‹åŒ–å¯èƒ½è€—时较长(秒级别),所有优化为 init æœªå®Œæˆæ—¶ä¸å½±å“æ—§çš„è¯åº“功能,完æˆåŽä»¥æ–°çš„为准。
@Component
public class SensitiveWordService {
@Autowired
private SensitiveWordBs sensitiveWordBs;
/**
* æ›´æ–°è¯åº“
*
* æ¯æ¬¡æ•°æ®åº“的信æ¯å‘生å˜åŒ–之åŽï¼Œé¦–先调用更新数æ®åº“æ•æ„Ÿè¯åº“的方法。
* 如果需è¦ç”Ÿæ•ˆï¼Œåˆ™è°ƒç”¨è¿™ä¸ªæ–¹æ³•。
*
* è¯´æ˜Žï¼šé‡æ–°åˆå§‹åŒ–ä¸å½±å“旧的方法使用。åˆå§‹åŒ–完æˆåŽï¼Œä¼šä»¥æ–°çš„为准。
*/
public void refresh() {
// æ¯æ¬¡æ•°æ®åº“的信æ¯å‘生å˜åŒ–之åŽï¼Œé¦–先调用更新数æ®åº“æ•æ„Ÿè¯åº“的方法,然åŽè°ƒç”¨è¿™ä¸ªæ–¹æ³•。
sensitiveWordBs.init();
}
}
å¦‚ä¸Šï¼Œä½ å¯ä»¥åœ¨æ•°æ®åº“è¯åº“å‘ç”Ÿå˜æ›´æ—¶ï¼Œéœ€è¦è¯åº“生效,主动触å‘一次åˆå§‹åŒ– sensitiveWordBs.init();
。
å…¶ä»–ä½¿ç”¨ä¿æŒä¸å˜ï¼Œæ— 需é‡å¯åº”用。
V0.6.0 以åŽï¼Œæ·»åŠ å¯¹åº”çš„ benchmark 测试。
测试环境为普通的笔记本:
处ç†å™¨ 12th Gen Intel(R) Core(TM) i7-1260P 2.10 GHz
机带 RAM 16.0 GB (15.7 GB å¯ç”¨)
系统类型 64 使“作系统, 基于 x64 的处ç†å™¨
ps: ä¸åŒçŽ¯å¢ƒä¼šæœ‰å·®å¼‚ï¼Œä½†æ˜¯æ¯”ä¾‹åŸºæœ¬ç¨³å®šã€‚
测试数æ®ï¼š100+ å—符串,循环 10W 次。
åºå· | 场景 | 耗时 | 备注 |
---|---|---|---|
1 | åªå𿕿„Ÿè¯ï¼Œæ— ä»»ä½•æ ¼å¼è½¬æ¢ | 1470ms,约 7.2W QPS | 追求æžè‡´æ€§èƒ½ï¼Œå¯ä»¥è¿™æ ·é…ç½® |
2 | åªå𿕿„Ÿè¯ï¼Œæ”¯æŒå…¨éƒ¨æ ¼å¼è½¬æ¢ | 2744ms,约 3.7W QPS | 满足大部分场景 |
-
移除å•个汉å—çš„æ•æ„Ÿè¯ï¼Œåœ¨ä¸å›½ï¼Œè¦æŠŠè¯ç»„当åšä¸€æ¬¡è¯ï¼Œé™ä½Žè¯¯åˆ¤çŽ‡ã€‚
-
支æŒå•ä¸ªçš„æ•æ„Ÿè¯å˜åŒ–?
removeã€addã€edit?
-
æ•æ„Ÿè¯æ ‡ç¾æŽ¥å£æ”¯æŒ
-
æ•æ„Ÿè¯å¤„ç†æ—¶æ ‡ç¾æ”¯æŒ
-
wordData 的内å˜å 用对比 + 优化
-
用户指定自定义的è¯ç»„ï¼ŒåŒæ—¶å…许指定è¯ç»„的组åˆèŽ·å–ï¼Œæ›´åŠ çµæ´»
FormatCombine/CheckCombine/AllowDenyCombine 组åˆç–略,å…许用户自定义。
-
word check ç–略的优化,统一é历+转æ¢
-
æ·»åŠ ThreadLocal ç‰æ€§èƒ½ä¼˜åŒ–
sensitive-word-admin æ•æ„Ÿè¯æŽ§å° v1.2.0 版本开æº
sensitive-word-admin v1.3.0 å‘布 如何支æŒåˆ†å¸ƒå¼éƒ¨ç½²ï¼Ÿ
01-å¼€æºæ•感è¯å·¥å…·å…¥é—¨ä½¿ç”¨
02-å¦‚ä½•å®žçŽ°ä¸€ä¸ªæ•æ„Ÿè¯å·¥å…·ï¼Ÿè¿ç¦è¯å®žçްæ€è·¯æ¢³ç†
03-æ•æ„Ÿè¯ä¹‹ StopWord åœæ¢è¯ä¼˜åŒ–与特殊符å·
05-æ•æ„Ÿè¯ä¹‹ DFA 算法(Trie Tree 算法)详解
06-æ•æ„Ÿè¯(è„è¯) å¦‚ä½•å¿½ç•¥æ— æ„义的å—符?达到更好的过滤效果
v0.10.0-è„è¯åˆ†ç±»æ ‡ç¾åˆæ¥æ”¯æŒ
v0.11.0-æ•æ„Ÿè¯æ–°ç‰¹æ€§ï¼šå¿½ç•¥æ— æ„义的å—ç¬¦ï¼Œè¯æ ‡ç¾å—å…¸
v0.12.0-æ•æ„Ÿè¯/è„è¯è¯æ ‡ç¾èƒ½åŠ›è¿›ä¸€æ¥å¢žå¼º
v0.13.0-æ•æ„Ÿè¯ç‰¹æ€§ç‰ˆæœ¬å‘布 支æŒè‹±æ–‡å•è¯å…¨è¯åŒ¹é…
v0.16.1-æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹å—典内å˜èµ„æºé‡Šæ”¾
v0.19.0-æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹æ•感è¯å•个编辑,ä¸å¿…é‡å¤åˆå§‹åŒ–
v0.20.0 æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹æ•°å—全部匹é…ï¼Œè€Œä¸æ˜¯éƒ¨åˆ†åŒ¹é…
v0.21.0 æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹ç™½å啿”¯æŒå•个编辑,修æ£ç™½åå•包å«é»‘å啿—¶çš„问题