SpringBoot實(shí)現(xiàn)過濾敏感詞的示例代碼
過濾敏感詞

1. 創(chuàng)建一個(gè)儲(chǔ)存要過濾的敏感詞的文本文件
首先創(chuàng)建一個(gè)文本文件儲(chǔ)存要過濾的敏感詞

在下面的工具類中我們會(huì)讀取這個(gè)文本文件,這里提前給出
@PostConstruct // 這個(gè)注解表示當(dāng)容器實(shí)例化這個(gè)bean(服務(wù)啟動(dòng)的時(shí)候)之后在調(diào)用構(gòu)造器之后這個(gè)方法會(huì)自動(dòng)的調(diào)用
public void init(){
try(
// 讀取寫有“敏感詞”的文件,getClass表示從程序編譯之后的target/classes讀配置文件,讀之后是字節(jié)流
// java7語法,在這里的句子最后會(huì)自動(dòng)執(zhí)行close語句
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
// 字節(jié)流 -> 字符流 -> 緩沖流
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
) {
String keyword;
// 從文件中一行一行讀
while ((keyword = reader.readLine()) != null){
// 添加到前綴樹
this.addKeyword(keyword);
}
} catch (IOException e) {
logger.error("加載敏感詞文件失敗: " + e.getMessage());
}
}2. 開發(fā)過濾敏感詞的工具類
開發(fā)過濾敏感詞組件
為了方便以后復(fù)用,我們把過濾敏感詞寫成一個(gè)工具類SensitiveFilter。
@Component
public class SensitiveFilter {
private static final Logger logger = LoggerFactory.getLogger(SensitiveFilter.class);
// 當(dāng)檢測(cè)到敏感詞后我們要把敏感詞替換成什么符號(hào)
private static final String REPLACEMENT = "***";
// 根節(jié)點(diǎn)
private TrieNode rootNode = new TrieNode();
@PostConstruct // 這個(gè)注解表示當(dāng)容器實(shí)例化這個(gè)bean(服務(wù)啟動(dòng)的時(shí)候)之后在調(diào)用構(gòu)造器之后這個(gè)方法會(huì)自動(dòng)的調(diào)用
public void init(){
try(
// 讀取寫有“敏感詞”的文件,getClass表示從程序編譯之后的target/classes讀配置文件,讀之后是字節(jié)流
// java7語法,在這里的句子最后會(huì)自動(dòng)執(zhí)行close語句
InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");
// 字節(jié)流 -> 字符流 -> 緩沖流
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
) {
String keyword;
// 從文件中一行一行讀
while ((keyword = reader.readLine()) != null){
// 添加到前綴樹
this.addKeyword(keyword);
}
} catch (IOException e) {
logger.error("加載敏感詞文件失敗: " + e.getMessage());
}
}
// 將一個(gè)敏感詞添加到前綴樹中
private void addKeyword(String keyword){
// 首先默認(rèn)指向根
TrieNode tempNode = rootNode;
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
TrieNode subNode = tempNode.getSubNode(c);
if(subNode == null){
// subNode為空,初始化子節(jié)點(diǎn);subNode不為空,直接用就可以了
subNode = new TrieNode();
tempNode.addSubNode(c, subNode);
}
// 指針指向子節(jié)點(diǎn),進(jìn)入下一輪循環(huán)
tempNode = subNode;
}
// 最后要設(shè)置結(jié)束標(biāo)識(shí)
tempNode.setKeywordEnd(true);
}
/**
* 過濾敏感詞
* @param text 待過濾的文本
* @return 過濾后的文本
*/
public String filter(String text){
if(StringUtils.isBlank(text)){
// 待過濾的文本為空,直接返回null
return null;
}
// 指針1,指向樹
TrieNode tempNode = rootNode;
// 指針2,指向正在檢測(cè)的字符串段的首
int begin = 0;
// 指針3,指向正在檢測(cè)的字符串段的尾
int position = 0;
// 儲(chǔ)存過濾后的文本
StringBuilder sb = new StringBuilder();
while (begin < text.length()){
char c = text.charAt(position);
// 跳過符號(hào),比如 “開票”是敏感詞 #開#票# 這個(gè)字符串中間的 '#' 應(yīng)該跳過
if(isSymbol(c)){
// 是特殊字符
// 若指針1處于根節(jié)點(diǎn),將此符號(hào)計(jì)入結(jié)果,指針2、3向右走一步
if(tempNode == rootNode){
sb.append(c);
begin++;
}
// 無論符號(hào)在開頭或中間,指針3都向下走一步
position++;
// 符號(hào)處理完,進(jìn)入下一輪循環(huán)
continue;
}
// 執(zhí)行到這里說明字符不是特殊符號(hào)
// 檢查下級(jí)節(jié)點(diǎn)
tempNode = tempNode.getSubNode(c);
if(tempNode == null){
// 以begin開頭的字符串不是敏感詞
sb.append(text.charAt(begin));
// 進(jìn)入下一個(gè)位置
position = ++begin;
// 重新指向根節(jié)點(diǎn)
tempNode = rootNode;
} else if(tempNode.isKeywordEnd()){
// 發(fā)現(xiàn)敏感詞,將begin~position字符串替換掉,存 REPLACEMENT (里面是***)
sb.append(REPLACEMENT);
// 進(jìn)入下一個(gè)位置
begin = ++position;
// 重新指向根節(jié)點(diǎn)
tempNode = rootNode;
} else {
// 檢查下一個(gè)字符
position++;
}
}
return sb.toString();
}
// 判斷是否為特殊符號(hào),是則返回true,不是則返回false
private boolean isSymbol(Character c){
// CharUtils.isAsciiAlphanumeric(c)方法:a、b、1、2···返回true,特殊字符返回false
// 0x2E80 ~ 0x9FFF 是東亞的文字范圍,東亞文字范圍我們不認(rèn)為是符號(hào)
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
}
// 前綴樹
private class TrieNode{
// 關(guān)鍵詞結(jié)束標(biāo)識(shí)
private boolean isKeywordEnd = false;
// 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)(key是下級(jí)字符、value是下級(jí)節(jié)點(diǎn))
private Map<Character, TrieNode> subNodes = new HashMap<>();
public boolean isKeywordEnd() {
return isKeywordEnd;
}
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
// 添加子節(jié)點(diǎn)
public void addSubNode(Character c, TrieNode node){
subNodes.put(c, node);
}
// 獲取子節(jié)點(diǎn)
public TrieNode getSubNode(Character c){
return subNodes.get(c);
}
}
}
上面就是過濾敏感詞工具類的全部代碼,接下來我們來解釋一下開發(fā)步驟
開發(fā)過濾敏感詞組件分為三步:
1.定義前綴樹(Tree)
我們將定義前綴樹寫為SensitiveFilter工具類的內(nèi)部類
// 前綴樹
private class TrieNode{
// 關(guān)鍵詞結(jié)束標(biāo)識(shí)
private boolean isKeywordEnd = false;
// 當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)(key是下級(jí)字符、value是下級(jí)節(jié)點(diǎn))
private Map<Character, TrieNode> subNodes = new HashMap<>();
public boolean isKeywordEnd() {
return isKeywordEnd;
}
public void setKeywordEnd(boolean keywordEnd) {
isKeywordEnd = keywordEnd;
}
// 添加子節(jié)點(diǎn)
public void addSubNode(Character c, TrieNode node){
subNodes.put(c, node);
}
// 獲取子節(jié)點(diǎn)
public TrieNode getSubNode(Character c){
return subNodes.get(c);
}
}
2.根據(jù)敏感詞,初始化前綴樹
將敏感詞添加到前綴樹中
// 將一個(gè)敏感詞添加到前綴樹中
private void addKeyword(String keyword){
// 首先默認(rèn)指向根
TrieNode tempNode = rootNode;
for (int i = 0; i < keyword.length(); i++) {
char c = keyword.charAt(i);
TrieNode subNode = tempNode.getSubNode(c);
if(subNode == null){
// subNode為空,初始化子節(jié)點(diǎn);subNode不為空,直接用就可以了
subNode = new TrieNode();
tempNode.addSubNode(c, subNode);
}
// 指針指向子節(jié)點(diǎn),進(jìn)入下一輪循環(huán)
tempNode = subNode;
}
// 最后要設(shè)置結(jié)束標(biāo)識(shí)
tempNode.setKeywordEnd(true);
}
3.編寫過濾敏感詞的方法
如何過濾文本中的敏感詞:

特殊符號(hào)怎么處理:

敏感詞前綴樹初始化完畢之后,過濾文本中的敏感詞的算法應(yīng)該如下:
定義三個(gè)指針:
- 指針1指向Tree樹
- 指針2指向待過濾字符串段的頭
- 指針3指向待過濾字符串段的尾
/**
* 過濾敏感詞
* @param text 待過濾的文本
* @return 過濾后的文本
*/
public String filter(String text){
if(StringUtils.isBlank(text)){
// 待過濾的文本為空,直接返回null
return null;
}
// 指針1,指向樹
TrieNode tempNode = rootNode;
// 指針2,指向正在檢測(cè)的字符串段的首
int begin = 0;
// 指針3,指向正在檢測(cè)的字符串段的尾
int position = 0;
// 儲(chǔ)存過濾后的文本
StringBuilder sb = new StringBuilder();
while (begin < text.length()){
char c = text.charAt(position);
// 跳過符號(hào),比如 “開票”是敏感詞 #開#票# 這個(gè)字符串中間的 '#' 應(yīng)該跳過
if(isSymbol(c)){
// 是特殊字符
// 若指針1處于根節(jié)點(diǎn),將此符號(hào)計(jì)入結(jié)果,指針2、3向右走一步
if(tempNode == rootNode){
sb.append(c);
begin++;
}
// 無論符號(hào)在開頭或中間,指針3都向下走一步
position++;
// 符號(hào)處理完,進(jìn)入下一輪循環(huán)
continue;
}
// 執(zhí)行到這里說明字符不是特殊符號(hào)
// 檢查下級(jí)節(jié)點(diǎn)
tempNode = tempNode.getSubNode(c);
if(tempNode == null){
// 以begin開頭的字符串不是敏感詞
sb.append(text.charAt(begin));
// 進(jìn)入下一個(gè)位置
position = ++begin;
// 重新指向根節(jié)點(diǎn)
tempNode = rootNode;
} else if(tempNode.isKeywordEnd()){
// 發(fā)現(xiàn)敏感詞,將begin~position字符串替換掉,存 REPLACEMENT (里面是***)
sb.append(REPLACEMENT);
// 進(jìn)入下一個(gè)位置
begin = ++position;
// 重新指向根節(jié)點(diǎn)
tempNode = rootNode;
} else {
// 檢查下一個(gè)字符
position++;
}
}
return sb.toString();
}
// 判斷是否為特殊符號(hào),是則返回true,不是則返回false
private boolean isSymbol(Character c){
// CharUtils.isAsciiAlphanumeric(c)方法:a、b、1、2···返回true,特殊字符返回false
// 0x2E80 ~ 0x9FFF 是東亞的文字范圍,東亞文字范圍我們不認(rèn)為是符號(hào)
return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF);
}
最后:建議在測(cè)試類中測(cè)試一下

經(jīng)測(cè)試,過濾敏感詞的工具類開發(fā)完成,這個(gè)工具會(huì)在接下來的發(fā)布帖子的功能中用到。
以上就是SpringBoot實(shí)現(xiàn)過濾敏感詞的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot過濾敏感詞的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java實(shí)現(xiàn)的AES加密算法完整實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)的AES加密算法,結(jié)合完整實(shí)例形式分析了AES加密類的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
淺談spring-boot-rabbitmq動(dòng)態(tài)管理的方法
這篇文章主要介紹了淺談spring-boot-rabbitmq動(dòng)態(tài)管理的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12
SpringBoot2 整合 ClickHouse數(shù)據(jù)庫案例解析
這篇文章主要介紹了SpringBoot2 整合 ClickHouse數(shù)據(jù)庫案例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
SpringBoot使用Jasypt對(duì)配置文件和數(shù)據(jù)庫密碼加密
在做數(shù)據(jù)庫敏感信息保護(hù)時(shí),應(yīng)加密存儲(chǔ),本文就來介紹一下SpringBoot使用Jasypt對(duì)配置文件和數(shù)據(jù)庫密碼加密,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02
SpringBoot整合mybatis-plus進(jìn)階詳細(xì)教程
本文主要對(duì)mybatis-plus的條件構(gòu)造器、AR模式、插件、逆向工程、自定義全局操作、公共字段自動(dòng)填充等知識(shí)點(diǎn)進(jìn)行講解,需要的朋友參考下吧2021-09-09

