前言:
敏感詞文字過濾是一個網站必不可少的功能,如何設計一個好的、高效的過濾算法是非常有必要的。作爲一般開發人員來說首先考慮的肯定是簡單的匹配,這樣是可以實現功能,但效率比較慢,在高級一點的就是正則表達式,比前一個好一點,但終究還是一丘之貉,非常遺憾,兩種方法都不可取。當然,在我意識裏沒有我也沒有認知到那個算法可以解決問題,但是百度知道,以下就是學習的DFA算法簡單介紹和功能實現。
一、DFA算法簡介
DFA全稱爲:Deterministic Finite Automaton,即確定有窮自動機。其特徵爲:有一個有限狀態集合和一些從一個狀態通向另一個狀態的邊,每條邊上標記有一個符號,其中一個狀態是初態,某些狀態是終態。但不同於不確定的有限自動機,DFA中不會有從同一狀態出發的兩條邊標誌有相同的符號。
簡單點說就是,它是是通過event和當前的state得到下一個state,即event+state=nextstate。理解爲系統中有多個節點,通過傳遞進入的event,來確定走哪個路由至另一個節點,而節點是有限的。
二、代碼示例
1、敏感詞庫初始化(將敏感詞用DFA算法的原理封裝到敏感詞庫中,敏感詞庫採用HashMap保存)
package com.fintech.modules.base.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fintech.modules.sms.black.dto.SmsBlackDict;
/**
* @Description: 敏感詞庫初始化
* @author lc
* @date 2018年10月9日
*/
public class SensitiveWordInit {
private static Logger logger = LoggerFactory.getLogger(SensitiveWordInit.class);
/**
* 敏感詞庫
*/
public HashMap sensitiveWordMap;
/**
* 初始化敏感詞
* @return
*/
public Map initKeyWord(List<SmsBlackDict> sensitiveWords)
{
try
{
// 從敏感詞集合對象中取出敏感詞並封裝到Set集合中
Set<String> keyWordSet = new HashSet<String>();
for (SmsBlackDict s : sensitiveWords)
{
keyWordSet.add(s.getName().trim());
}
// 將敏感詞庫加入到HashMap中
addSensitiveWordToHashMap(keyWordSet);
}
catch (Exception e)
{
e.printStackTrace();
}
return sensitiveWordMap;
}
/**
* 封裝敏感詞庫
* @param keyWordSet
*/
@SuppressWarnings("rawtypes")
private void addSensitiveWordToHashMap(Set<String> keyWordSet)
{
// 初始化HashMap對象並控制容器的大小
sensitiveWordMap = new HashMap(keyWordSet.size());
// 敏感詞
String key = null;
// 用來按照相應的格式保存敏感詞庫數據
Map nowMap = null;
// 用來輔助構建敏感詞庫
Map<String, String> newWorMap = null;
// 使用一個迭代器來循環敏感詞集合
Iterator<String> iterator = keyWordSet.iterator();
while (iterator.hasNext())
{
key = iterator.next();
// 等於敏感詞庫,HashMap對象在內存中佔用的是同一個地址,所以此nowMap對象的變化,sensitiveWordMap對象也會跟着改變
nowMap = sensitiveWordMap;
for (int i = 0; i < key.length(); i++)
{
// 截取敏感詞當中的字,在敏感詞庫中字爲HashMap對象的Key鍵值
char keyChar = key.charAt(i);
// 判斷這個字是否存在於敏感詞庫中
Object wordMap = nowMap.get(keyChar);
if (wordMap != null)
{
nowMap = (Map) wordMap;
}
else
{
newWorMap = new HashMap<String, String>();
newWorMap.put("isEnd", "0");
nowMap.put(keyChar, newWorMap);
nowMap = newWorMap;
}
// 如果該字是當前敏感詞的最後一個字,則標識爲結尾字
if (i == key.length() - 1)
{
nowMap.put("isEnd", "1");
}
logger.warn("封裝敏感詞庫過程:"+sensitiveWordMap);
}
logger.warn("查看敏感詞庫數據:" + sensitiveWordMap);
}
}
}
2、敏感詞過濾工具類
package com.fintech.modules.base.util;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @Description: 敏感詞過濾工具類
* @author lc
* @date 2018年10月10日
*/
public class SensitiveWordEngine {
/**
* 敏感詞庫
*/
public static Map sensitiveWordMap = null;
/**
* 只過濾最小敏感詞
*/
public static int minMatchTYpe = 1;
/**
* 過濾所有敏感詞
*/
public static int maxMatchType = 2;
/**
* 敏感詞庫敏感詞數量
*
* @return
*/
public int getWordSize()
{
if (SensitiveWordEngine.sensitiveWordMap == null)
{
return 0;
}
return SensitiveWordEngine.sensitiveWordMap.size();
}
/**
* 是否包含敏感詞
*
* @param txt
* @param matchType
* @return
*/
public static boolean isContaintSensitiveWord(String txt, int matchType)
{
boolean flag = false;
for (int i = 0; i < txt.length(); i++)
{
int matchFlag = checkSensitiveWord(txt, i, matchType);
if (matchFlag > 0)
{
flag = true;
}
}
return flag;
}
/**
* 獲取敏感詞內容
*
* @param txt
* @param matchType
* @return 敏感詞內容
*/
public static Set<String> getSensitiveWord(String txt, int matchType)
{
Set<String> sensitiveWordList = new HashSet<String>();
for (int i = 0; i < txt.length(); i++)
{
int length = checkSensitiveWord(txt, i, matchType);
if (length > 0)
{
// 將檢測出的敏感詞保存到集合中
sensitiveWordList.add(txt.substring(i, i + length));
i = i + length - 1;
}
}
return sensitiveWordList;
}
/**
* 替換敏感詞
*
* @param txt
* @param matchType
* @param replaceChar
* @return
*/
public static String replaceSensitiveWord(String txt, int matchType, String replaceChar)
{
String resultTxt = txt;
Set<String> set = getSensitiveWord(txt, matchType);
Iterator<String> iterator = set.iterator();
String word = null;
String replaceString = null;
while (iterator.hasNext())
{
word = iterator.next();
replaceString = getReplaceChars(replaceChar, word.length());
resultTxt = resultTxt.replaceAll(word, replaceString);
}
return resultTxt;
}
/**
* 替換敏感詞內容
*
* @param replaceChar
* @param length
* @return
*/
private static String getReplaceChars(String replaceChar, int length)
{
String resultReplace = replaceChar;
for (int i = 1; i < length; i++)
{
resultReplace += replaceChar;
}
return resultReplace;
}
/**
* 檢查敏感詞數量
*
* @param txt
* @param beginIndex
* @param matchType
* @return
*/
public static int checkSensitiveWord(String txt, int beginIndex, int matchType)
{
boolean flag = false;
// 記錄敏感詞數量
int matchFlag = 0;
char word = 0;
Map nowMap = SensitiveWordEngine.sensitiveWordMap;
for (int i = beginIndex; i < txt.length(); i++)
{
word = txt.charAt(i);
// 判斷該字是否存在於敏感詞庫中
nowMap = (Map) nowMap.get(word);
if (nowMap != null)
{
matchFlag++;
// 判斷是否是敏感詞的結尾字,如果是結尾字則判斷是否繼續檢測
if ("1".equals(nowMap.get("isEnd")))
{
flag = true;
// 判斷過濾類型,如果是小過濾則跳出循環,否則繼續循環
if (SensitiveWordEngine.minMatchTYpe == matchType)
{
break;
}
}
}
else
{
break;
}
}
if (!flag)
{
matchFlag = 0;
}
return matchFlag;
}
}
3、調用
/**
* @Description: 是否包含敏感詞
* @author lc
*/
public Boolean isContaintSensitiveWord(String message){
logger.info("[通用短信請求]是否包含敏感詞驗證, 短信內容爲: {}", message);
Map<String, Object> paramMap = new HashMap<String, Object>();
Boolean boo = false;
try {
paramMap.put("valid", PublicEnum.YES.getCode());
paramMap.put("type", PublicEnum.N.getCode());
// 從數據庫中獲取敏感詞對象集合
List<SmsBlackDict> list = blackDictMapper.listBlack(paramMap);
// 初始化敏感詞庫對象
SensitiveWordInit sensitiveWordInit = new SensitiveWordInit();
// 構建敏感詞庫
Map sensitiveWordMap = sensitiveWordInit.initKeyWord(list);
// 傳入SensitivewordEngine類中的敏感詞庫
SensitiveWordEngine.sensitiveWordMap = sensitiveWordMap;
boo = SensitiveWordEngine.isContaintSensitiveWord(message, 2);
} catch (Exception e) {
logger.error("[通用短信請求]是否包含敏感詞驗證異常!{}", e.getMessage());
}
return boo;
}
/**
* @Description: 獲取敏感詞內容
* @author lc
*/
public Set<String> getSensitiveWord(BaseMessage baseMessage){
logger.info("[通用短信請求]獲取敏感詞內容, 短信內容爲: {}", baseMessage.getMessage());
Map<String, Object> paramMap = new HashMap<String, Object>();
Set<String> sensitiveWordList = null;
try {
paramMap.put("valid", PublicEnum.YES.getCode());
paramMap.put("type", PublicEnum.N.getCode());
// 從數據庫中獲取敏感詞對象集合
List<SmsBlackDict> list = blackDictMapper.listBlack(paramMap);
// 初始化敏感詞庫對象
SensitiveWordInit sensitiveWordInit = new SensitiveWordInit();
// 構建敏感詞庫
Map sensitiveWordMap = sensitiveWordInit.initKeyWord(list);
// 傳入SensitivewordEngine類中的敏感詞庫
SensitiveWordEngine.sensitiveWordMap = sensitiveWordMap;
sensitiveWordList = SensitiveWordEngine.getSensitiveWord(baseMessage.getMessage(), 2);
logger.info("[通用短信請求]獲取敏感詞內容, 敏感詞爲: {}", sensitiveWordList);
// 留存攔截記錄
SmsBlackInterceptRecord record = new SmsBlackInterceptRecord(baseMessage, sensitiveWordList.toString());
interceptRecordService.insert(record);
} catch (Exception e) {
logger.error("[通用短信請求]獲取敏感詞內容異常!{}", e.getMessage());
}
return sensitiveWordList;
}
註釋:代碼中包含自己的業務邏輯代碼,只要改成自己的就可以使用