• Stars
    star
    1,966
  • Rank 22,577 (Top 0.5 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created over 4 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

👮‍♂️The sensitive word tool for java.(敏感词/违禁词/违法词/脏词。基于 DFA 算法实现的高性能 java 敏感词过滤工具框架。请勿发布涉及政治、广告、营销、翻墙、违反国家法律法规等内容。高性能敏感词检测过滤组件,附带繁体简体互换,支持全角半角互换,汉字转拼音,模糊搜索等功能。)

sensitive-word

sensitive-word 基于 DFA 算法实现的高性能敏感词工具。

Maven Central Open Source Love

在线体验

创作目的

实现一款好用敏感词工具。

基于 DFA 算法实现,目前敏感词库内容收录 6W+(源文件 18W+,经过一次删减)。

后期将进行持续优化和补充敏感词库,并进一步提升算法的性能。

希望可以细化敏感词的分类,感觉工作量比较大,暂时没有进行。

特性

全角半角互换、英文大小写互换、数字常见形式的互换、中文繁简体互换、英文常见形式的互换、忽略重复词等

变更日志

CHANGE_LOG.md

快速开始

准备

  • JDK1.7+

  • Maven 3.x+

Maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>sensitive-word</artifactId>
    <version>0.8.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 结果处理类 字符串 返回字符串中第一个敏感词

IWordResultHandler 结果处理类

IWordResultHandler 可以对敏感词的结果进行处理,允许用户自定义。

内置实现见 WordResultHandlers 工具类:

  • WordResultHandlers.word()

只保留敏感词单词本身。

  • WordResultHandlers.raw()

保留敏感词相关信息,包含敏感词的开始和结束下标。

使用实例

所有测试案例参见 SensitiveWordHelperTest

判断是否包含敏感词

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

WordResultHandlers.raw() 可以保留对应的下标信息:

final String text = "五星红旗迎风飘扬,毛主席的画像屹立在天安门前。";

IWordResult word = SensitiveWordHelper.findFirst(text, WordResultHandlers.raw());
Assert.assertEquals("WordResult{startIndex=0, endIndex=4}", word.toString());

返回所有敏感词

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<IWordResult> wordList = SensitiveWordHelper.findAll(text, WordResultHandlers.raw());
Assert.assertEquals("[WordResult{startIndex=0, endIndex=4}, WordResult{startIndex=9, endIndex=12}, WordResult{startIndex=18, endIndex=21}]", wordList.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('*');
            }
        }
    }

}

我们针对其中的部分词做固定映射处理,其他的默认转换为 *

更多特性

后续的诸多特性,主要是针对各种针对各种情况的处理,尽可能的提升敏感词命中率。

这是一场漫长的攻防之战。

样式处理

忽略大小写

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 = SensitiveWordHelper.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 = "楼主好人,邮箱 [email protected]";

List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[[email protected]]", wordList.toString());

连续数字检测

一般用于过滤手机号/QQ等广告信息。

V0.2.1 之后,支持通过 numCheckLen(长度) 自定义检测的长度。

final String text = "你懂得:12345678";

// 默认检测 8 位
List<String> wordList = SensitiveWordBs.newInstance().init().findAll(text);
Assert.assertEquals("[12345678]", wordList.toString());

// 指定数字的长度,避免误杀
List<String> wordList2 = SensitiveWordBs.newInstance()
        .numCheckLen(9)
        .init()
        .findAll(text);
Assert.assertEquals("[]", wordList2.toString());

网址检测

用于过滤常见的网址信息。

final String text = "点击链接 www.baidu.com查看答案";

List<String> wordList = SensitiveWordBs.newInstance().init().findAll(text);
Assert.assertEquals("[链接, www.baidu.com]", wordList.toString());

Assert.assertEquals("点击** *************查看答案", SensitiveWordBs
                .newInstance()
                .init()
                .replace(text));

引导类特性配置

说明

上面的特性默认都是开启的,有时业务需要灵活定义相关的配置特性。

所以 v0.0.14 开放了属性配置。

配置方法

为了让使用更加优雅,统一使用 fluent-api 的方式定义。

用户可以使用 SensitiveWordBs 进行如下定义:

SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
        .ignoreCase(true)
        .ignoreWidth(true)
        .ignoreNumStyle(true)
        .ignoreChineseStyle(true)
        .ignoreEnglishStyle(true)
        .ignoreRepeat(false)
        .enableNumCheck(true)
        .enableEmailCheck(true)
        .enableUrlCheck(true)
        .enableWordCheck(true)
        .numCheckLen(8)
        .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 是否启用数字检测。 true
8 enableEmailCheck 是有启用邮箱检测 true
9 enableUrlCheck 是否启用链接检测 true
10 enableWordCheck 是否启用敏感单词检测 true
11 numCheckLen 数字检测,自定义指定长度。 8

动态加载(用户自定义)

情景说明

有时候我们希望将敏感词的加载设计成动态的,比如控台修改,然后可以实时生效。

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.system())
        .wordAllow(WordAllows.system())
        .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.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());

这里都是同时使用了系统默认配置,和自定义的配置。

注意:我们初始化了新的 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.system(), 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

  • wordData 的内存占用对比 + 优化

  • 用户指定自定义的词组,同时允许指定词组的组合获取,更加灵活

FormatCombine/CheckCombine/AllowDenyCombine 组合策略,允许用户自定义。

  • word check 策略的优化,统一遍历+转换

  • 添加 ThreadLocal 等性能优化

  • 敏感词标签支持

拓展阅读

敏感词工具实现思路

DFA 算法讲解

敏感词库优化流程

java 如何实现开箱即用的敏感词控台服务?

wechat

NLP 开源矩阵

pinyin 汉字转拼音

pinyin2hanzi 拼音转汉字

segment 高性能中文分词

opencc4j 中文繁简体转换

nlp-hanzi-similar 汉字相似度

word-checker 拼写检测

sensitive-word 敏感词

More Repositories

1

sensitive

🔐Sensitive log tool for java, based on java annotation. (基于注解的 java 日志脱敏工具框架,更加优雅的日志打印。支持自定义哈希、支持基于 log4j2 插件的统一脱敏、支持 logback 插件统一脱敏)
Java
491
star
2

opencc4j

🇨🇳Open Chinese Convert is an opensource project for conversion between Traditional Chinese and Simplified Chinese.(java 中文繁简体转换)
Java
437
star
3

awesome-metaverse-zh

Awesome metaverse projects (元宇宙精选资源汇总)
Batchfile
340
star
4

cache

The basic cache tool for java.(java 手写实现渐进式 redis 缓存工具)
Shell
314
star
5

word-checker

🇨🇳🇬🇧Chinese and English word spelling corrector.(中文易错别字检测,中文拼写检测纠正。英文单词拼写校验工具)
Java
209
star
6

pinyin

The high performance pinyin tool for java.(java 高性能中文转拼音工具。支持同音字。)
Java
203
star
7

nlp-hanzi-similar

The hanzi similar tool.(汉字相似度计算工具,中文形近字算法。可用于手写汉字识别纠正,文本混淆等。)
Java
186
star
8

junitperf

⛵️Junit performance rely on junit5 and jdk8+.(java 性能测试框架。性能测试。压测。测试报告生成。)
Java
174
star
9

sisyphus

The java retry framework.(支持注解的 java 重试框架)
Java
172
star
10

markdown-toc

🎄Generate toc for github markdown file.(为 markdown 文件生成 toc 目录)
Java
167
star
11

segment

The jieba-analysis tool for java.(基于结巴分词词库实现的更加灵活优雅易用,高性能的 java 分词实现。支持词性标注。)
Java
128
star
12

mq

The message queue in java.(java 简易版本 mq 实现)
Java
86
star
13

rpc

Java rpc framework based on netty4.(java 手写 dubbo rpc 框架)
Java
86
star
14

data-factory

🏭Auto generate mock data for java test.(便于 Java 测试自动生成对象信息)
Java
85
star
15

resubmit

The repeat submit tool for java.(java 防止重复提交框架,支持注解。支持 spring, springboot 整合。)
Java
65
star
16

iexcel

📚IExcel is a java tool for create and read excel easy.(excel 读取和写入,解决 excel OOM 问题。)
Java
47
star
17

auto-log

The auto log tool for java.(java 自动日志输出,基于字节码,兼容 spring)
Java
45
star
18

idoc

🌾Generate doc for java maven project.(java 项目自动生成文档)
Java
44
star
19

lombok-ex

Java compile time annotation, lombok extension framework.(java 编译时注解框架,对 lombok 进行扩展)
Java
40
star
20

validator

🚥Fluent-validation framework for java extend from hibernate-validator.(java 对象属性流式验证框架,下一代校验框架。兼容实现 jakarta bean validation、jsr 303、hibernate-validator)
Java
39
star
21

ioc

The java ioc framework(从零开始手写模拟 spring Ioc 框架)
Java
33
star
22

heaven

⭐Collect basic useful tools for java dev.(收集开发中常用的工具类)
Java
32
star
23

word-cloud

The word cloud tool for java.(java 好用的词云工具-云图)
Java
28
star
24

async

🦄The async tool for java.(Java 多线程异步并行框架,基于 java 字节码,支持注解。)
Java
26
star
25

csv

The csv read/write tool based on java annotation.(基于 java 注解的 CSV 文件读写框架工具。)
Java
22
star
26

houbb.github.io

🧀The blog for github pages.
JavaScript
21
star
27

mybatis

The simple mybatis.(手写简易版 mybatis)
Java
19
star
28

jdbc-pool

The jdbc pool for java.(java 手写 jdbc 数据库连接池实现)
Java
17
star
29

rate-limit

🎃Rate limit for java.(java 渐进式限流工具框架,支持 spring springboot 字节码)
Java
16
star
30

lock

The distributed lock tool for java.(java 实现开箱即用基于 redis 的分布式锁,支持可重入锁获取。内置整合 spring、springboot。)
Java
8
star
31

sensitive-word-admin

The sensitive tool admin.(敏感词工具对应的控台)
HTML
8
star
32

sandglass

The java time schedule framework.(java 任务调度时间工具框架)
Java
7
star
33

leetcode

The leetcode problem solutions.(力扣算法个人学习笔记) 查询、排序、五大算法汇总
Java
6
star
34

houbb

my readme
1
star
35

logstash4j

log stash in java.(简易版 logstash 实现)
Java
1
star