内容安全校验—DFA算法的简单使用

使用阿里云进行内容安全校验,可以过滤一些色情、广告、灌水、渉政、辱骂等内容。
但是在企业中的业务,可能有些敏感词是阿里云第三方接口所无法检测到的,又或者是公司需要节省开销,这样的话就需要自己维护一套敏感词。在内容审核的时候,会先校验自己维护的敏感词库是否包含此内容,如果出现了就不需要再用阿里云第三方接口来校验内容安全了,就省了一笔开销。

敏感词过滤有四种方案:

数据库模糊查询,这种方式效率太低
String.indexOf(“”)查找,这种方式 如果数据库量大的话也是比较慢
全文检索,这种方式需要创建ES索引库,分词再匹配
DFA算法,确定有穷自动机(一种数据结构),下面主要演示此方案
DFA简介:
DFA实现原理:https://www.cnblogs.com/twoheads/p/11349541.html
DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。
存储:一次性的把所有的敏感词存储到了多个map中,下图就是表示这种结构

检索的过程:


DFA的使用:
在数据库中创建一个敏感词表:

创建敏感词表对应的实体类:
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

/**
* 敏感词信息表
* @author xiaobai
*/
@Data
@TableName("wm_sensitive")
public class WmSensitive implements Serializable {
private static final long serialVersionUID = 1L;
//主键
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
//敏感词
@TableField("sensitives")
private String sensitives;
//创建时间
@TableField("created_time")
private Date createdTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
敏感词校验的工具类:
使用的时候,需要先查询数据库敏感词表中的所有数据,获取所有敏感词数据的集合,然后调用此工具类的initMap方法,用所有数据的集合作为参数传入,目的是为了初始化关键词字典库。
当需要对内容进行检测的时候,调用此工具类的matchWords方法,将需要检测的内容传入,就可以检测文本内容是否是敏感词了。返回的是一个map集合,会返回匹配的关键词和命中次数。也就是说如果map.size>0就表示校验的文本是违规的,在敏感词库中存在。
import java.util.*;
public class SensitiveWordUtil {

public static Map<String, Object> dictionaryMap = new HashMap<>();

/**
* 生成关键词字典库
* @param words
* @return
*/
public static void initMap(Collection<String> words) {
if (words == null) {
System.out.println("敏感词列表不能为空");
return ;
}
// map初始长度words.size(),整个字典库的入口字数(小于words.size(),因为不同的词可能会有相同的首字)
Map<String, Object> map = new HashMap<>(words.size());
// 遍历过程中当前层次的数据
Map<String, Object> curMap = null;
Iterator<String> iterator = words.iterator();

while (iterator.hasNext()) {
String word = iterator.next();
curMap = map;
int len = word.length();
for (int i =0; i < len; i++) {
// 遍历每个词的字
String key = String.valueOf(word.charAt(i));
// 当前字在当前层是否存在, 不存在则新建, 当前层数据指向下一个节点, 继续判断是否存在数据
Map<String, Object> wordMap = (Map<String, Object>) curMap.get(key);
if (wordMap == null) {
// 每个节点存在两个数据: 下一个节点和isEnd(是否结束标志)
wordMap = new HashMap<>(2);
wordMap.put("isEnd", "0");
curMap.put(key, wordMap);
}
curMap = wordMap;
// 如果当前字是词的最后一个字,则将isEnd标志置1
if (i == len -1) {
curMap.put("isEnd", "1");
}
}
}
dictionaryMap = map;
}

/**
* 搜索文本中某个文字是否匹配关键词
* @param text
* @param beginIndex
* @return
*/
private static int checkWord(String text, int beginIndex) {
if (dictionaryMap == null) {
throw new RuntimeException("字典不能为空");
}
boolean isEnd = false;
int wordLength = 0;
Map<String, Object> curMap = dictionaryMap;
int len = text.length();
// 从文本的第beginIndex开始匹配
for (int i = beginIndex; i < len; i++) {
String key = String.valueOf(text.charAt(i));
// 获取当前key的下一个节点
curMap = (Map<String, Object>) curMap.get(key);
if (curMap == null) {
break;
} else {
wordLength ++;
if ("1".equals(curMap.get("isEnd"))) {
isEnd = true;
}
}
}
if (!isEnd) {
wordLength = 0;
}
return wordLength;
}

/**
* 获取匹配的关键词和命中次数
* @param text
* @return
*/
public static Map<String, Integer> matchWords(String text) {
Map<String, Integer> wordMap = new HashMap<>();
int len = text.length();
for (int i = 0; i < len; i++) {
int wordLength = checkWord(text, i);
if (wordLength > 0) {
String word = text.substring(i, i + wordLength);
// 添加关键词匹配次数
if (wordMap.containsKey(word)) {
wordMap.put(word, wordMap.get(word) + 1);
} else {
wordMap.put(word, 1);
}
i += wordLength - 1;
}
}
return wordMap;
}
}
————————————————
版权声明:本文为CSDN博主「bai259257」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bai259257/article/details/124993817

posted @ 2023-07-10 10:39  GaoYanbing  阅读(92)  评论(0编辑  收藏  举报