提示:以下是本篇文章正文内容,下面案例可供参考
一、自管理敏感词库实现和原理
比如媒体公司,除了使用第三方的敏感词审核,每个平台也会有自己的敏感词库。
1.实现流程:
二、使用步骤
1.算法实现:引入工具类
代码如下(示例):拿走直接用。算法实现没有依赖。
package com.ciels.utils.common;
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;
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("法轮");
list.add("法轮功");
list.add("冰毒");
initMap(list);
String content="我是一个好人,并不会卖冰毒,也不操练法轮功,我真的不卖冰毒";
Map<String, Integer> map = matchWords(content);
System.out.println(map);
}
}
2.如何使用工具类
因为敏感词使用的是非常频繁的,所以我们把自管理的敏感词加入到Redis中。提高效率,同时在修改数据库中的自管理敏感词的时候,需要同时去更新Redis缓存中的数据。(这些操作没做)
代码如下(示例):
String text 传入要审核的文本
WmNews wmNews 传入要审核的文章对象。
后续需要更新其状态-->status文章发布状态、reason 审核不通过原因
private boolean scanSensitive(String text, WmNews wmNews) {
//得到所有子管理敏感词的列表,从数据库里查询性能低。使用redis的set结构
//.members 得到所有数据
Set<String> sensitives = redisTemplate.opsForSet().members("wmnews:sensitive");
if (CollectionUtils.isEmpty(sensitives)){
//没有缓存从数据库查询
List<WmSensitive> wmSensitivesList = wmSensitiveMapper.selectList(null);
if (CollectionUtils.isEmpty(wmSensitivesList)){
//没有敏感词 审核通过
return true;
}
//把wmSensitivesList转换成 敏感词汇的列表
sensitives=wmSensitivesList.stream().map(WmSensitive::getSensitives).collect(Collectors.toSet());
//添加到redis缓存里:因为添加到set需要数组类型的参数,所以把敏感词列表转换成数组,再添加到redis里
String[] array = sensitives.toArray(new String[sensitives.size()]);
redisTemplate.opsForSet().add("wmnews:sensitive",array);
}
//在使用自管理敏感词进行审核:检查一下文本里面是否包含了任意一个敏感词。
//使用DFA有穷自动机实现。不用一个词一个词的去str.contains()来判断,提高了效率 -----使用工具类来实现。
if (CollectionUtils.isEmpty(SensitiveWordUtil.dictionaryMap)){
SensitiveWordUtil.initMap(sensitives);//优化不用每次都初始化这个集合。但是敏感词修改 后需要重新加载集合。
}
Map<String, Integer> resultMap = SensitiveWordUtil.matchWords(text);
if (resultMap.size()>0){
//如果有敏感词,就不通过。需要更新WnNews的状态和原因
wmNews.setStatus(WmNews.Status.FAIL.getCode());
wmNews.setReason("文本中有敏感词:"+resultMap.keySet());
wmNewsMapper.updateById(wmNews);
return false;
}
return true;
}
三、使用的表如示例:
四、一些API总结
1.CollectionUtils.isEmpty(集合);//判断集合是否为空
2.SetOperations<String, String> set = redisTemplate.opsForSet();//得到操作redis set
类型的对象
Set<String> setMembers = set.members("wmnews:sensitive");//得到以key的set集合所有值
String[] strings = sensitives.toArray(new String[sensitives.size()]);
set.add("wmnews:sensitive",strings);//存储set的时候可以传入一个数组。
add(String key,String...value)可变参数可以传入数组使用