sensitive-word 基于 DFA ç®—æ³•å®žçŽ°çš„é«˜æ€§èƒ½æ•æ„Ÿè¯å·¥å…·ã€‚
如果有一些疑难æ‚症,å¯ä»¥åŠ å…¥ï¼šæŠ€æœ¯äº¤æµç¾¤
sensitive-word-admin 是对应的控å°çš„应用,目å‰åŠŸèƒ½å¤„äºŽåˆæœŸå¼€å‘ä¸ï¼ŒMVP 版本å¯ç”¨ã€‚
大家好,我是è€é©¬ã€‚
一直想实现一款简å•å¥½ç”¨æ•æ„Ÿè¯å·¥å…·ï¼ŒäºŽæ˜¯å¼€æºå®žçŽ°äº†è¿™ä¸ªå·¥å…·ã€‚
基于 DFA ç®—æ³•å®žçŽ°ï¼Œç›®å‰æ•感è¯åº“内容收录 6W+ï¼ˆæºæ–‡ä»¶ 18W+,ç»è¿‡ä¸€æ¬¡åˆ å‡ï¼‰ã€‚
åŽæœŸå°†è¿›è¡ŒæŒç»ä¼˜åŒ–å’Œè¡¥å……æ•æ„Ÿè¯åº“ï¼Œå¹¶è¿›ä¸€æ¥æå‡ç®—法的性能。
v0.24.0 开始内置支æŒå¯¹æ•感è¯çš„分类细化,ä¸è¿‡å·¥ä½œé‡æ¯”较大,难å…å˜åœ¨ç–æ¼ã€‚
欢迎 PR 改进, github æéœ€æ±‚ï¼Œæˆ–è€…åŠ å…¥æŠ€æœ¯äº¤æµç¾¤æ²Ÿé€šå¹ç‰›ï¼
-
6W+ è¯åº“ï¼Œä¸”ä¸æ–优化更新
-
基于 fluent-api 实现,使用优雅简æ´
全角åŠè§’互æ¢ã€è‹±æ–‡å¤§å°å†™äº’æ¢ã€æ•°å—常è§å½¢å¼çš„互æ¢ã€ä¸æ–‡ç¹ç®€ä½“互æ¢ã€è‹±æ–‡å¸¸è§å½¢å¼çš„互æ¢ã€å¿½ç•¥é‡å¤è¯ç‰
-
æ”¯æŒæ•æ„Ÿè¯æ£€æµ‹ã€é‚®ç®±æ£€æµ‹ã€æ•°å—检测ã€ç½‘倿£€æµ‹ã€IPV4ç‰
-
æ”¯æŒæ•°æ®çš„æ•°æ®åŠ¨æ€æ›´æ–°ï¼ˆç”¨æˆ·è‡ªå®šä¹‰ï¼‰ï¼Œå®žæ—¶ç”Ÿæ•ˆ
䏋颿˜¯ä¸€äº›æ—¥å¿—ã€åŠ è§£å¯†ã€è„±æ•安全相关的库推è:
项目 | ä»‹ç» |
---|---|
sensitive-word | é«˜æ€§èƒ½æ•æ„Ÿè¯æ ¸å¿ƒåº“ |
sensitive-word-admin | æ•æ„Ÿè¯æŽ§å°ï¼Œå‰åŽç«¯åˆ†ç¦» |
sensitive | 高性能日志脱æ•组件 |
auto-log | 统一日志切é¢ç»„件,支æŒå…¨é“¾è·¯traceId |
encryption-local | ç¦»çº¿åŠ å¯†æœºç»„ä»¶ |
encryption | åŠ å¯†æœºæ ‡å‡†API+本地客户端 |
encryption-server | åŠ å¯†æœºæœåŠ¡ |
æœ‰æ—¶å€™æ•æ„Ÿè¯æœ‰ä¸€ä¸ªæŽ§å°ï¼Œé…置起æ¥ä¼šæ›´åŠ çµæ´»æ–¹ä¾¿ã€‚
梳ç†äº†å¤§é‡çš„æ•æ„Ÿè¯æ ‡ç¾æ–‡ä»¶ï¼Œå¯ä»¥è®©æˆ‘ä»¬çš„æ•æ„Ÿè¯æ›´åŠ æ–¹ä¾¿ã€‚
这两个资料阅读å¯åœ¨ä¸‹æ–¹æ–‡ç« 获å–:
ç›®å‰ v0.24.0 已内置实现å•è¯æ ‡ç¾ï¼Œéœ€è¦çš„建议å‡çº§åˆ°æœ€æ–°ç‰ˆæœ¬ã€‚
å¼€æºä¸æ˜“ï¼Œå¦‚æžœæœ¬é¡¹ç›®å¯¹ä½ æœ‰å¸®åŠ©ï¼Œä½ å¯ä»¥è¯·è€é©¬å–一æ¯å¥¶èŒ¶ã€‚
-
JDK1.8+
-
Maven 3.x+
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.26.0</version>
</dependency>
SensitiveWordHelper
ä½œä¸ºæ•æ„Ÿè¯çš„å·¥å…·ç±»ï¼Œæ ¸å¿ƒæ–¹æ³•å¦‚ä¸‹ï¼š
注æ„: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());
v0.25.0 ç›®å‰çš„å‡ ä¸ªç–略,也支æŒç”¨æˆ·å¼•导类自定义。所有的ç–略都是接å£ï¼Œæ”¯æŒç”¨æˆ·è‡ªå®šä¹‰å®žçŽ°ã€‚
åºå· | 方法 | 说明 | 默认值 |
---|---|---|---|
16 | wordCheckNum | æ•°å—æ£€æµ‹ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.num() |
17 | wordCheckEmail | 邮箱检测ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.email() |
18 | wordCheckUrl | URL检测ç–ç•¥(v0.25.0开始支æŒ),内置还是实现了 urlNoPrefix() |
(WordChecks.url() |
19 | wordCheckIpv4 | ipv4检测ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.ipv4() |
20 | wordCheckWord | æ•æ„Ÿè¯æ£€æµ‹ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.word() |
内置实现:
a) WordChecks.urlNoPrefix()
作为 url çš„é¢å¤–实现,å¯ä»¥ä¸éœ€è¦ https://
和 http://
å‰ç¼€ã€‚
邮箱ç‰ä¸ªäººä¿¡æ¯ï¼Œé»˜è®¤æœªå¯ç”¨ã€‚
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.25.0 内置支æŒä¸éœ€è¦ http å议的å‰ç¼€æ£€æµ‹ï¼š
final String text = "点击链接 https://www.baidu.com æŸ¥çœ‹ç”æ¡ˆï¼Œå½“然也å¯ä»¥æ˜¯ baidu.comã€www.baidu.com";
final SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.enableUrlCheck(true) // å¯ç”¨URL检测
.wordCheckUrl(WordChecks.urlNoPrefix()) //指定检测的方å¼
.init();
List<String> wordList = sensitiveWordBs.findAll(text);
Assert.assertEquals("[www.baidu.com, baidu.com, www.baidu.com]", wordList.toString());
Assert.assertEquals("点击链接 https://************* æŸ¥çœ‹ç”æ¡ˆï¼Œå½“然也å¯ä»¥æ˜¯ *********ã€*************", 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
çš„å¯¹è±¡ï¼Œè€Œä¸æ˜¯ä»¥å‰çš„工具方法。工具方法é…置都是默认的。
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)
.wordFailFast(true)
.wordCheckNum(WordChecks.num())
.wordCheckEmail(WordChecks.email())
.wordCheckUrl(WordChecks.url())
.wordCheckIpv4(WordChecks.ipv4())
.wordCheckWord(WordChecks.word())
.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 | 针对匹é…çš„æ•æ„Ÿè¯é¢å¤–åŠ å·¥ï¼Œæ¯”å¦‚å¯ä»¥é™åˆ¶è‹±æ–‡å•è¯å¿…é¡»å…¨åŒ¹é… | æ’为真 |
16 | wordCheckNum | æ•°å—æ£€æµ‹ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.num() |
17 | wordCheckEmail | 邮箱检测ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.email() |
18 | wordCheckUrl | URL检测ç–ç•¥(v0.25.0开始支æŒ) | (WordChecks.url() |
19 | wordCheckIpv4 | ipv4检测ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.ipv4() |
20 | wordCheckWord | æ•æ„Ÿè¯æ£€æµ‹ç–ç•¥(v0.25.0开始支æŒ) | WordChecks.word() |
21 | wordReplace | 替æ¢ç–ç•¥ | WordReplaces.defaults() |
22 | wordFailFast | æ•æ„Ÿè¯åŒ¹é…æ¨¡å¼æ˜¯å¦å¿«é€Ÿè¿”回 | true |
v0.26.0 开始支æŒã€‚
默认情况下,wordFailFast=trueã€‚åŒ¹é…æ—¶å¿«é€Ÿè¿”回,性能较好。
但是有时候ä¸å¤ªç¬¦åˆäººçš„直觉。
默认如下:
SensitiveWordBs bs2 = SensitiveWordBs.newInstance()
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Arrays.asList("我的世界", "我的");
}
}).init();
String text = "ä»–çš„ä¸–ç•Œå®ƒçš„ä¸–ç•Œå’Œå¥¹çš„ä¸–ç•Œéƒ½ä¸æ˜¯æˆ‘çš„ä¹Ÿä¸æ˜¯æˆ‘的世界";
List<String> textList2 = bs2.findAll(text);
Assert.assertEquals(Arrays.asList("我的", "我的"), textList2);
æ¤æ—¶ä¼šä¼˜å…ˆåŒ¹é…çŸçš„ã€æˆ‘的】,导致åŽé¢çš„ã€æˆ‘的世界】被跳过。
å°½å¯èƒ½æ‰¾åˆ°æœ€é•¿çš„匹é…è¯ã€‚
SensitiveWordBs bs = SensitiveWordBs.newInstance()
.wordFailFast(false)
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Arrays.asList("我的世界", "我的");
}
}).init();
String text = "ä»–çš„ä¸–ç•Œå®ƒçš„ä¸–ç•Œå’Œå¥¹çš„ä¸–ç•Œéƒ½ä¸æ˜¯æˆ‘çš„ä¹Ÿä¸æ˜¯æˆ‘的世界";
List<String> textList = bs.findAll(text);
Assert.assertEquals(Arrays.asList("我的", "我的世界"), textList);
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());
æ¤æ–¹å¼å·²åºŸå¼ƒï¼Œå»ºè®®ä½¿ç”¨ä¸Šé¢å¢žé‡æ·»åŠ çš„æ–¹å¼ï¼Œé¿å…å…¨é‡åŠ è½½ã€‚ä¸ºäº†å…¼å®¹ï¼Œæ¤æ–¹å¼ä¾ç„¶ä¿ç•™ã€‚
使用方å¼ï¼šåœ¨è°ƒç”¨ sensitiveWordBs.init()
çš„æ—¶å€™ï¼Œæ ¹æ® IWordDeny+IWordAllow 釿–°æž„å»ºæ•æ„Ÿè¯åº“。 å› ä¸ºåˆå§‹åŒ–å¯èƒ½è€—时较长(秒级别),所有优化为 init æœªå®Œæˆæ—¶ä¸å½±å“æ—§çš„è¯åº“功能,完æˆåŽä»¥æ–°çš„为准。
@Component
public class SensitiveWordService {
@Autowired
private SensitiveWordBs sensitiveWordBs;
/**
* æ›´æ–°è¯åº“
*
* æ¯æ¬¡æ•°æ®åº“的信æ¯å‘生å˜åŒ–之åŽï¼Œé¦–先调用更新数æ®åº“æ•æ„Ÿè¯åº“的方法。
* 如果需è¦ç”Ÿæ•ˆï¼Œåˆ™è°ƒç”¨è¿™ä¸ªæ–¹æ³•。
*
* è¯´æ˜Žï¼šé‡æ–°åˆå§‹åŒ–ä¸å½±å“旧的方法使用。åˆå§‹åŒ–完æˆåŽï¼Œä¼šä»¥æ–°çš„为准。
*/
public void refresh() {
// æ¯æ¬¡æ•°æ®åº“的信æ¯å‘生å˜åŒ–之åŽï¼Œé¦–先调用更新数æ®åº“æ•æ„Ÿè¯åº“的方法,然åŽè°ƒç”¨è¿™ä¸ªæ–¹æ³•。
sensitiveWordBs.init();
}
}
å¦‚ä¸Šï¼Œä½ å¯ä»¥åœ¨æ•°æ®åº“è¯åº“å‘ç”Ÿå˜æ›´æ—¶ï¼Œéœ€è¦è¯åº“生效,主动触å‘一次åˆå§‹åŒ– sensitiveWordBs.init();
。
å…¶ä»–ä½¿ç”¨ä¿æŒä¸å˜ï¼Œæ— 需é‡å¯åº”用。
支æŒç‰ˆæœ¬ï¼šv0.13.0
有时候我们å¯èƒ½å¸Œæœ›å¯¹åŒ¹é…çš„æ•æ„Ÿè¯è¿›ä¸€æ¥é™åˆ¶ï¼Œæ¯”如虽然我们定义了ã€avã€‘ä½œä¸ºæ•æ„Ÿè¯ï¼Œä½†æ˜¯ä¸å¸Œæœ›ã€have】被匹é…。
å°±å¯ä»¥è‡ªå®šä¹‰å®žçް wordResultCondition 接å£ï¼Œå®žçŽ°è‡ªå·±çš„ç–略。
系统内置的ç–略在 WordResultConditions#alwaysTrue()
æ’为真,WordResultConditions#englishWordMatch()
åˆ™è¦æ±‚英文必须全è¯åŒ¹é…。
WordResultConditions 工具类å¯ä»¥èŽ·å–匹é…ç–ç•¥
实现 | 说明 | 支æŒç‰ˆæœ¬ |
---|---|---|
alwaysTrue | æ’为真 | |
englishWordMatch | 英文å•è¯å…¨è¯åŒ¹é… | v0.13.0 |
englishWordNumMatch | 英文å•è¯/æ•°å—å…¨è¯åŒ¹é… | v0.20.0 |
wordTags | æ»¡è¶³ç‰¹å®šæ ‡ç¾çš„,比如åªå…³æ³¨ã€å¹¿å‘Šã€‘æ ‡ç¾ | v0.23.0 |
chains(IWordResultCondition ...conditions) | æ”¯æŒæŒ‡å®šå¤šä¸ªæ¡ä»¶ï¼ŒåŒæ—¶æ»¡è¶³ | v0.23.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.23.0
我们å¯ä»¥åªè¿”回隶属于æŸä¸€ç§æ ‡ç¾çš„æ•æ„Ÿè¯ã€‚
æˆ‘ä»¬æŒ‡å®šäº†ä¸¤ä¸ªæ•æ„Ÿè¯ï¼šå•†å“ã€AV
MyWordTag æ˜¯æˆ‘ä»¬å®šä¹‰çš„ä¸€ä¸ªæ•æ„Ÿè¯æ ‡ç¾å®žçŽ°ï¼š
/**
* 自定义å•è¯æ ‡ç¾
* @since 0.23.0
*/
public class MyWordTag extends AbstractWordTag {
private static Map<String, Set<String>> dataMap;
static {
dataMap = new HashMap<>();
dataMap.put("商å“", buildSet("广告", "䏿–‡"));
dataMap.put("AV", buildSet("色情", "å•è¯", "英文"));
}
private static Set<String> buildSet(String... tags) {
Set<String> set = new HashSet<>();
for(String tag : tags) {
set.add(tag);
}
return set;
}
@Override
protected Set<String> doGetTag(String word) {
return dataMap.get(word);
}
}
测试用例如下,我们模拟了两个ä¸åŒçš„实现类,æ¯ä¸€ä¸ªå…³æ³¨çš„å•è¯æ ‡ç¾ä¸åŒã€‚
// åªå…³å¿ƒSE情
SensitiveWordBs sensitiveWordBsYellow = SensitiveWordBs.newInstance()
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Arrays.asList("商å“", "AV");
}
})
.wordAllow(WordAllows.empty())
.wordTag(new MyWordTag())
.wordResultCondition(WordResultConditions.wordTags(Arrays.asList("色情")))
.init();
// åªå…³å¿ƒå¹¿å‘Š
SensitiveWordBs sensitiveWordBsAd = SensitiveWordBs.newInstance()
.wordDeny(new IWordDeny() {
@Override
public List<String> deny() {
return Arrays.asList("商å“", "AV");
}
})
.wordAllow(WordAllows.empty())
.wordTag(new MyWordTag())
.wordResultCondition(WordResultConditions.wordTags(Arrays.asList("广告")))
.init();
final String text = "这些 AV 商å“ä»€ä¹ˆä»·æ ¼ï¼Ÿ";
Assert.assertEquals("[AV]", sensitiveWordBsYellow.findAll(text).toString());
Assert.assertEquals("[商å“]", sensitiveWordBsAd.findAll(text).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
主è¦ç‰¹æ€§æ”¯æŒç‰ˆæœ¬ï¼šv0.24.0
è¿™é‡Œåªæ˜¯ä¸€ä¸ªæŠ½è±¡çš„æŽ¥å£ï¼Œç”¨æˆ·å¯ä»¥è‡ªè¡Œå®šä¹‰å®žçŽ°ã€‚æ¯”å¦‚ä»Žæ•°æ®åº“æŸ¥è¯¢ã€æ–‡ä»¶è¯»å–ã€api 调用ç‰ã€‚
public interface IWordTag {
/**
* æŸ¥è¯¢æ ‡ç¾åˆ—表
* @param word è„è¯
* @return 结果
*/
Set<String> getTag(String word);
}
为了方便大部分情况使用,内置实现一些场景ç–略在 WordTags
ç±»ä¸
实现方法 | 说明 | 备注 |
---|---|---|
none() | 空实现 | v0.10.0 æ”¯æŒ |
file(String filePath) | 指定文件路径 | v0.10.0 æ”¯æŒ |
file(String filePath, String wordSplit, String tagSplit) | 指定文件路径,以åŠå•è¯åˆ†éš”ç¬¦ã€æ ‡ç¾åˆ†éš”符 | v0.10.0 æ”¯æŒ |
map(final Map<String, Set> wordTagMap) | æ ¹æ® mapåˆå§‹åŒ– | v0.24.0 æ”¯æŒ |
lines(Collection lines) | å—符串列表 | v0.24.0 æ”¯æŒ |
lines(Collection lines, String wordSplit, String tagSpli) | å—符串列表,以åŠå•è¯åˆ†éš”ç¬¦ã€æ ‡ç¾åˆ†éš”符 | v0.24.0 æ”¯æŒ |
system() | 系件文件内置实现,整åˆç½‘络分类 | v0.24.0 æ”¯æŒ |
defaults() | 默认ç–略,目å‰ä¸º system | v0.24.0 æ”¯æŒ |
chains(IWordTag... others) | 链弿–¹æ³•,支æŒç”¨æˆ·æ•´åˆå®žçŽ°å¤šä¸ªç–ç•¥ | v0.24.0 æ”¯æŒ |
æ•æ„Ÿè¯æ ‡ç¾çš„æ ¼å¼æˆ‘们默认约定如下 æ•æ„Ÿè¯ tag1,tag2
,代表这 æ•æ„Ÿè¯
çš„æ ‡ç¾ä¸º tag1 å’Œ tag2
比如
五星红旗 政治,国家
所有的文件行内容,和指定的å—ç¬¦ä¸²è¡Œå†…å®¹ä¹Ÿå»ºè®®ç”¨è¿™ç§æ–¹å¼ã€‚å¦‚æžœä¸æ»¡è¶³ï¼Œè‡ªå®šä¹‰å®žçްå³å¯ã€‚
v0.24.0 版本开始,默认的å•è¯æ ‡ç¾ä¸º WordTags.system()
。
è¯´æ˜Žï¼šç›®å‰æ•°æ®ç»Ÿè®¡è‡ªç½‘络,å˜åœ¨ä¸å°‘ç–æ¼ã€‚也欢迎大家指æ£ï¼ŒæŒç»æ”¹è¿›ä¸...
SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.wordTag(WordTags.system())
.init();
Set<String> tagSet = sensitiveWordBs.tags("åšå½©");
Assert.assertEquals("[3]", tagSet.toString());
这里为了压缩大å°ä¼˜åŒ–,对应的类别用数å—表示。
æ•°å—çš„å«ä¹‰åˆ—表如下:
0 政治
1 毒å“
2 色情
3 赌åš
4 è¿æ³•
这里以文件为例å,演示一下如何使用。
final String path = "~\\test\\resources\\dict_tag_test.txt";
// 演示默认方法
IWordTag wordTag = WordTags.file(path);
SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.wordTag(wordTag)
.init();
Set<String> tagSet = sensitiveWordBs.tags("é›¶å”®");
Assert.assertEquals("[广告, 网络]", tagSet.toString());
// 演示指定分隔符
IWordTag wordTag2 = WordTags.file(path, " ", ",");
SensitiveWordBs sensitiveWordBs2 = SensitiveWordBs.newInstance()
.wordTag(wordTag2)
.init();
Set<String> tagSet2 = sensitiveWordBs2.tags("é›¶å”®");
Assert.assertEquals("[广告, 网络]", tagSet2.toString());
å…¶ä¸ dict_tag_test.txt
我们自定义的内容如下:
零售 广告,网络
æˆ‘ä»¬åœ¨èŽ·å–æ•感è¯çš„æ—¶å€™ï¼Œæ˜¯å¯ä»¥è®¾ç½®å¯¹åº”的结果处ç†ç–略,从而获å–å¯¹åº”çš„æ•æ„Ÿè¯æ ‡ç¾ä¿¡æ¯
// è‡ªå®šä¹‰æµ‹è¯•æ ‡ç¾ç±»
IWordTag wordTag = WordTags.lines(Arrays.asList("天安门 政治,国家,地å€"));
// 指定åˆå§‹åŒ–
SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
.wordTag(wordTag)
.init()
;
List<WordTagsDto> wordTagsDtoList1 = sensitiveWordBs.findAll("天安门", WordResultHandlers.wordTags());
Assert.assertEquals("[WordTagsDto{word='天安门', tags=[政治, 国家, 地å€]}]", wordTagsDtoList1.toString());
我们自定义了 天安门
关键è¯çš„æ ‡ç¾ï¼Œç„¶åŽé€šè¿‡æŒ‡å®š findAll 的结果处ç†ç–略为 WordResultHandlers.wordTags()
,就å¯ä»¥åœ¨èŽ·å–æ•感è¯çš„åŒæ—¶ï¼ŒèŽ·å–å¯¹åº”çš„æ ‡ç¾åˆ—表。
æœ‰æ—¶å€™æˆ‘ä»¬å¸Œæœ›å°†æ•æ„Ÿè¯çš„åŠ è½½è®¾è®¡æˆåЍæ€çš„,比如控å°ä¿®æ”¹ï¼Œç„¶åŽå¯ä»¥å®žæ—¶ç”Ÿæ•ˆã€‚
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 åˆå§‹åŒ–。
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 æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹ç™½å啿”¯æŒå•个编辑,修æ£ç™½åå•包å«é»‘å啿—¶çš„问题
v0.23.0 æ•æ„Ÿè¯ç»“æžœæ¡ä»¶æ‹“展,内置支æŒé“¾å¼+å•è¯æ ‡ç¾
v0.24.0 æ–°ç‰¹æ€§æ”¯æŒæ ‡ç¾åˆ†ç±»ï¼Œå†…置实现多ç§ç–ç•¥
v0.25.0 新特性之 wordCheck ç–略支æŒç”¨æˆ·è‡ªå®šä¹‰
v0.25.1 新特性之返回匹é…è¯ï¼Œä¿®æ£ tags æ ‡ç¾