Files
sensitive-word/README.md
2021-07-07 10:46:51 +08:00

419 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.0.14</version>
</dependency>
```
## 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<String> 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 = " the bad words.";
String word = SensitiveWordHelper.findFirst(text);
Assert.assertEquals("", word);
```
## 忽略数字的写法
这里实现了数字常见形式的转换。
```java
final String text = "这个是我的微信9⓿二肆⁹₈③⑸⒋➃㈤㊄";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[9⓿二肆⁹₈③⑸⒋➃㈤㊄]", wordList.toString());
```
## 忽略繁简体
```java
final String text = "我爱我的祖国和五星紅旗。";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[五星紅旗]", wordList.toString());
```
## 忽略英文的书写格式
```java
final String text = "Ⓕⓤc⒦ the bad words";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[Ⓕⓤc⒦]", wordList.toString());
```
## 忽略重复词
```java
final String text = "ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦ the bad words";
List<String> wordList = SensitiveWordHelper.findAll(text);
Assert.assertEquals("[ⒻⒻⒻfⓤuⓤ⒰cⓒ⒦]", wordList.toString());
```
## 邮箱检测
```java
final String text = "楼主好人,邮箱 sensitiveword@xx.com";
List<String> 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<String> deny();
}
```
比如:
```java
public class MyWordDeny implements IWordDeny {
@Override
public List<String> deny() {
return Arrays.asList("我的自定义敏感词");
}
}
```
### IWordAllow
接口如下,可以自定义自己的实现。
返回的列表,表示这个词不是一个敏感词。
```java
/**
* 允许的内容-返回的内容不被当做敏感词
* @author binbin.hou
* @since 0.0.13
*/
public interface IWordAllow {
/**
* 获取结果
* @return 结果
* @since 0.0.13
*/
List<String> allow();
}
```
如:
```java
public class MyWordAllow implements IWordAllow {
@Override
public List<String> 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)