8000 GitHub - kiddmewhat/sensitive-word: 👮â€â™‚ï¸The sensitive word tool for java.(æ•æ„Ÿè¯/è¿ç¦è¯/è¿æ³•è¯/è„è¯ã€‚基于 DFA 算法实现的高性能 java æ•æ„Ÿè¯è¿‡æ»¤å·¥å…·æ¡†æž¶ã€‚请勿å‘å¸ƒæ¶‰åŠæ”¿æ²»ã€å¹¿å‘Šã€è¥é”€ã€ç¿»å¢™ã€è¿åå›½å®¶æ³•å¾‹æ³•è§„ç­‰å†…å®¹ã€‚é«˜æ€§èƒ½æ•æ„Ÿè¯æ£€æµ‹è¿‡æ»¤ç»„件,附带ç¹ä½“简体互æ¢ï¼Œæ”¯æŒå…¨è§’åŠè§’互æ¢ï¼Œæ±‰å­—转拼音,模糊æœç´¢ç­‰åŠŸèƒ½ã€‚)
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

👮â€â™‚ï¸The sensitive word tool for java.(æ•æ„Ÿè¯/è¿ç¦è¯/è¿æ³•è¯/è„è¯ã€‚基于 DFA 算法实现的高性能 java æ•æ„Ÿè¯è¿‡æ»¤å·¥å…·æ¡†æž¶ã€‚请勿å‘å¸ƒæ¶‰åŠæ”¿æ²»ã€å¹¿å‘Šã€è¥é”€ã€ç¿»å¢™ã€è¿åå›½å®¶æ³•å¾‹æ³•è§„ç­‰å†…å®¹ã€‚é«˜æ€§èƒ½æ•æ„Ÿè¯æ£€æµ‹è¿‡æ»¤ç»„件,附带ç¹ä½“简体互æ¢ï¼Œæ”¯æŒå…¨è§’åŠè§’互æ¢ï¼Œæ±‰å­—转拼音,模糊æœç´¢ç­‰åŠŸèƒ½ã€‚)

License

Notifications You must be signed in to change notification settings

kiddmewhat/sensitive-word

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sensitive-word

sensitive-word 基于 DFA ç®—æ³•å®žçŽ°çš„é«˜æ€§èƒ½æ•æ„Ÿè¯å·¥å…·ã€‚

Maven Central Open Source Love

在线体验

如果有一些疑难æ‚症,å¯ä»¥åŠ å…¥ï¼šæŠ€æœ¯äº¤æµç¾¤

sensitive-word-admin 是对应的控å°çš„应用,目å‰åŠŸèƒ½å¤„äºŽåˆæœŸå¼€å‘中,MVP 版本å¯ç”¨ã€‚

创作目的

å®žçŽ°ä¸€æ¬¾å¥½ç”¨æ•æ„Ÿè¯å·¥å…·ã€‚

基于 DFA ç®—æ³•å®žçŽ°ï¼Œç›®å‰æ•感è¯åº“内容收录 6W+ï¼ˆæºæ–‡ä»¶ 18W+,ç»è¿‡ä¸€æ¬¡åˆ å‡ï¼‰ã€‚

åŽæœŸå°†è¿›è¡ŒæŒç»­ä¼˜åŒ–å’Œè¡¥å……æ•æ„Ÿè¯åº“,并进一步æå‡ç®—法的性能。

希望å¯ä»¥ç»†åŒ–æ•æ„Ÿè¯çš„åˆ†ç±»ï¼Œæ„Ÿè§‰å·¥ä½œé‡æ¯”较大,暂时没有进行。

特性

全角åŠè§’互æ¢ã€è‹±æ–‡å¤§å°å†™äº’æ¢ã€æ•°å­—常è§å½¢å¼çš„互æ¢ã€ä¸­æ–‡ç¹ç®€ä½“互æ¢ã€è‹±æ–‡å¸¸è§å½¢å¼çš„互æ¢ã€å¿½ç•¥é‡å¤è¯ç­‰

å˜æ›´æ—¥å¿—

CHANGE_LOG.md

V0.17.0

  • æ”¯æŒ ipv4

V0.18.0

  • 优化 URL 检测,é™ä½Žè¯¯åˆ¤çއ

更多资料

æ•æ„Ÿè¯æŽ§å°

æœ‰æ—¶å€™æ•æ„Ÿè¯æœ‰ä¸€ä¸ªæŽ§å°ï¼Œé…置起æ¥ä¼šæ›´åŠ çµæ´»æ–¹ä¾¿ã€‚

java 如何实现开箱å³ç”¨çš„æ•æ„Ÿè¯æŽ§å°æœåŠ¡ï¼Ÿ

æ•æ„Ÿè¯æ ‡ç­¾æ–‡ä»¶

梳ç†äº†å¤§é‡çš„æ•æ„Ÿè¯æ ‡ç­¾æ–‡ä»¶ï¼Œå¯ä»¥è®©æˆ‘ä»¬çš„æ•æ„Ÿè¯æ›´åŠ æ–¹ä¾¿ã€‚

这两个资料阅读å¯åœ¨ä¸‹æ–¹æ–‡ç« èŽ·å–:

v0.11.0-æ•æ„Ÿè¯æ–°ç‰¹æ€§åŠå¯¹åº”标签文件

快速开始

准备

  • JDK1.8+

  • Maven 3.x+

Maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>sensitive-word</artifactId>
    <version>0.18.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 结果处ç†ç±»

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());
  1. 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));

IPV4 检测

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();

wordResultCondition-针对匹é…è¯è¿›ä¸€æ­¥åˆ¤æ–­

说明

支æŒç‰ˆæœ¬ï¼šv0.13.0

有时候我们å¯èƒ½å¸Œæœ›å¯¹åŒ¹é…çš„æ•æ„Ÿè¯è¿›ä¸€æ­¥é™åˆ¶ï¼Œæ¯”如虽然我们定义了ã€avã€‘ä½œä¸ºæ•æ„Ÿè¯ï¼Œä½†æ˜¯ä¸å¸Œæœ›ã€have】被匹é…。

å°±å¯ä»¥è‡ªå®šä¹‰å®žçް wordResultCondition 接å£ï¼Œå®žçŽ°è‡ªå·±çš„ç­–ç•¥ã€‚

系统内置的策略在 WordResultConditions#alwaysTrue() æ’为真,WordResultConditions#englishWordMatch() åˆ™è¦æ±‚英文必须全è¯åŒ¹é…。

入门例å­

原始的默认情况:

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.11.0-æ•æ„Ÿè¯æ–°ç‰¹æ€§åŠå¯¹åº”标签文件

动æ€åŠ è½½ï¼ˆç”¨æˆ·è‡ªå®šä¹‰ï¼‰

情景说明

æœ‰æ—¶å€™æˆ‘ä»¬å¸Œæœ›å°†æ•æ„Ÿè¯çš„加载设计æˆåЍæ€çš„,比如控å°ä¿®æ”¹ï¼Œç„¶åŽå¯ä»¥å®žæ—¶ç”Ÿæ•ˆã€‚

v0.0.13 支æŒäº†è¿™ç§ç‰¹æ€§ã€‚

接å£è¯´æ˜Ž

为了实现这个特性,并且兼容以å‰çš„功能,我们定义了两个接å£ã€‚

IWordDeny

接å£å¦‚下,å¯ä»¥è‡ªå®šä¹‰è‡ªå·±çš„实现。

è¿”å›žçš„åˆ—è¡¨ï¼Œè¡¨ç¤ºè¿™ä¸ªè¯æ˜¯ä¸€ä¸ªæ•感è¯ã€‚

/**
 * æ‹’ç»å‡ºçŽ°çš„æ•°æ®-è¿”å›žçš„å†…å®¹è¢«å½“åšæ˜¯æ•感è¯
 * @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("æˆ‘çš„è‡ªå®šä¹‰æ•æ„Ÿè¯");
    }

}

IWordAllow

接å£å¦‚下,å¯ä»¥è‡ªå®šä¹‰è‡ªå·±çš„实现。

返回的列表,表示这个è¯ä¸æ˜¯ä¸€ä¸ªæ•感è¯ã€‚

/**
 * å…许的内容-返回的内容ä¸è¢«å½“åšæ•感è¯
 * @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 工具方法,工具方法é…置是默认的ï¼

spring æ•´åˆ

背景

实际使用中,比如å¯ä»¥åœ¨é¡µé¢é…置修改,然åŽå®žæ—¶ç”Ÿæ•ˆã€‚

æ•°æ®å­˜å‚¨åœ¨æ•°æ®åº“ä¸­ï¼Œä¸‹é¢æ˜¯ä¸€ä¸ªä¼ªä»£ç çš„例å­ï¼Œå¯ä»¥å‚考 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();。

å…¶ä»–ä½¿ç”¨ä¿æŒä¸å˜ï¼Œæ— éœ€é‡å¯åº”用。

Benchmark

V0.6.0 以åŽï¼Œæ·»åŠ å¯¹åº”çš„ benchmark 测试。

BenchmarkTimesTest

环境

测试环境为普通的笔记本:

处ç†å™¨	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 满足大部分场景

STAR

Stargazers over time

åŽæœŸ road-map

  • 移除å•ä¸ªæ±‰å­—çš„æ•æ„Ÿè¯ï¼Œåœ¨ä¸­å›½ï¼Œè¦æŠŠè¯ç»„当åšä¸€æ¬¡è¯ï¼Œé™ä½Žè¯¯åˆ¤çŽ‡ã€‚

  • 支æŒå•ä¸ªçš„æ•æ„Ÿè¯å˜åŒ–?

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 åœæ­¢è¯ä¼˜åŒ–与特殊符å·

04-æ•æ„Ÿè¯ä¹‹å­—典瘦身

05-æ•æ„Ÿè¯ä¹‹ DFA 算法(Trie Tree 算法)详解

06-æ•æ„Ÿè¯(è„è¯) 如何忽略无æ„义的字符?达到更好的过滤效果

v0.10.0-è„è¯åˆ†ç±»æ ‡ç­¾åˆæ­¥æ”¯æŒ

v0.11.0-æ•æ„Ÿè¯æ–°ç‰¹æ€§ï¼šå¿½ç•¥æ— æ„ä¹‰çš„å­—ç¬¦ï¼Œè¯æ ‡ç­¾å­—å…¸

v0.12.0-æ•æ„Ÿè¯/è„è¯è¯æ ‡ç­¾èƒ½åŠ›è¿›ä¸€æ­¥å¢žå¼º

v0.13.0-æ•æ„Ÿè¯ç‰¹æ€§ç‰ˆæœ¬å‘布 支æŒè‹±æ–‡å•è¯å…¨è¯åŒ¹é…

v0.16.1-æ•æ„Ÿè¯æ–°ç‰¹æ€§ä¹‹å­—典内存资æºé‡Šæ”¾

wechat

NLP å¼€æºçŸ©é˜µ

pinyin 汉字转拼音

pinyin2hanzi 拼音转汉字

segment 高性能中文分è¯

opencc4j 中文ç¹ç®€ä½“转æ¢

nlp-hanzi-similar 汉字相似度

word-checker 拼写检测

sensitive-word æ•æ„Ÿè¯

About

👮â€â™‚ï¸The sensitive word tool for java.(æ•æ„Ÿè¯/è¿ç¦è¯/è¿æ³•è¯/è„è¯ã€‚基于 DFA 算法实现的高性能 java æ•æ„Ÿè¯è¿‡æ»¤å·¥å…·æ¡†æž¶ã€‚请勿å‘å¸ƒæ¶‰åŠæ”¿æ²»ã€å¹¿å‘Šã€è¥é”€ã€ç¿»å¢™ã€è¿åå›½å®¶æ³•å¾‹æ³•è§„ç­‰å†…å®¹ã€‚é«˜æ€§èƒ½æ•æ„Ÿè¯æ£€æµ‹è¿‡æ»¤ç»„件,附带ç¹ä½“简体互æ¢ï¼Œæ”¯æŒå…¨è§’åŠè§’互æ¢ï¼Œæ±‰å­—转拼音,模糊æœç´¢ç­‰åŠŸèƒ½ã€‚)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 97.8%
  • Shell 1.6%
  • Batchfile 0.6%
0