进行词汇过滤的布隆类型,目前采用了2种方式:
- 一种是redssion的布隆过滤器和hankcs分词,实现简单,在redis实现快,但是在分词效率和包含判断上如果文本内容过多,查询效率低下,并且布隆过滤器存在误差;
- 另外一种更高效的方式是:从Mysql里面加载数据put进去ConcurrentHashMap,结合DFA算法进行处理,查询判断效率高
- 本项目结合和RabbitMq,在实现消息定时和即时发送,保障消息的可靠性,多种字段属性不可重复消费的配置,和对内容的过滤判断,并且返回的数据中,包含了过滤词汇的下标索引,这样就可以进行高亮显示
1. 集成redisson和Rabbitmq和其它工具
-
hankcs
的引入是为进行内容的简单分词 -
redisson
布隆过滤器的实现和其它使用 -
hutool
工具包和雪花算法 -
mybatis-plus
集成使用 -
amqp
RabbitMq的集成
2. 核心代码
2.1 生产者
- 生产者发送消息的VO
@Data
@ToString
public class MsgVO extends MsgDTO{
/***
* 校验的字段逗号隔开
*/
private String checkField;
/***
* 是否进行不要词汇的过滤
*/
private Boolean checkWord;
}
@Data
@ToString
public class MsgDTO<T>{
private Long messageId;
private T info;
}
2.1.1 对于定时发送需要按照github的插件
因为最近在用docker,所以提供docker安装插件的方式,但是大同小异:
- 下载rabbitmq_delayed_message_exchange-*.ez插件并上传到指定文件夹中
下载地址:延迟插件下载 - 插件拷贝至容器中
docker cp rabbitmq_delayed_message_exchange-3.8.0.ez rabbitmq:/plugins
- 进入容器,并查看插件启动插件
# 进入
docker exec -it rabbitmq /bin/bash
# 查询插件
rabbitmq-plugins list
# 启动
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
- 重启rabbitmq
2.1.2 生产者代码
docker restart rabbitmq
/**
* 延时发送map格式数据
*
* @param msgVO
*/
@Override
public HashMap<String, Object> sendDelayTestMap(MsgVO msgVO) {
HashMap<String, Object> result = new HashMap<>(8);
// 是否进行词汇过滤
Boolean checkWord = msgVO.getCheckWord();
if (ObjectUtil.isNotEmpty(checkWord) && checkWord){
HashMap<String, Object> info = (HashMap<String, Object>) msgVO.getInfo();
String content = (String) info.get("content");
HashMap<String, Object> resultWord = indecentService.getSensitiveWordsByDFA(content, true, 1);
if (MapUtil.isNotEmpty(resultWord)){
return resultWord;
}
}
rabbitTemplate.convertAndSend(RabbitMqConfig.DELAY_EXCHANGE, RabbitMqConfig.DELAY_ROUTING_KEY, msgVO, message -> {
Object t = msgVO.getInfo();
String pushTime = MapUtil.getStr((Map<?, ?>) t, "pushTime");
// 计算时间差
LocalDateTime pushLocalDateTime = DateUtil.parseLocalDateTime(pushTime);
LocalDateTime nowLocalDate = DateUtil.toLocalDateTime(new Date());
Duration duration = Duration.between(nowLocalDate,pushLocalDateTime);
long time = duration.toMillis();
log.info("需要延迟发布的时间:[{}]",time);
message.getMessageProperties().setHeader("x-delay", time);
return message;
});
log.info("map格式的数据发送成功 发送时间为【{}】", DateUtil.now());
result.put("code",200);
result.put("msg","发送成功!");
return result;
}
2.2 消费者
2.2.1消息的可靠性
- 保证消息发送到消息队列中去
- 保证消费成功接受消息
- 保证生产者收到到mq消费者确认应答
- 消息持久化,重试
手动
ACK
不及时,或者忘记了会导致当Consumer
退出时,Message
会重新分发,RabbitMQ
会占用越来越多的内存,长时间运行,最终内存“内存泄漏”,所以在finally 进行ack
这里代码checkField
是列出的进行重复发送属性的校验,通过逗号隔开可以配置多个字段属性,采用反射获取字段的值,结合redis和MD5的加密来判断是否消费过,未消费可以进行业务进行
/****
*
* @param message 消息
* @param channel 渠道
* @throws IOException
*/
@Override
public void receiverJson(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 将JSON格式数据转换为实体对象
byte[] body = message.getBody();
MsgVO msgVO = JSON.parseObject(body, MsgVO.class);
// 是否校验重复字段参数 目前之前pushTime,content,msgId 如果为空这不进行校验 按照顺序凭借
String checkField = msgVO.getCheckField();
if (StringUtils.isNotBlank(checkField)){
String[] field = checkField.split(",");
StringBuilder checkContent = new StringBuilder();
for (String property : field) {
String fieldValue;
if (StringUtils.startsWith(property,"info")){
fieldValue = String.valueOf(ReflectUtil.getFieldValue(msgVO.getInfo(), property));
}else {
fieldValue = String.valueOf(ReflectUtil.getFieldValue(msgVO, property));
}
checkContent.append(fieldValue);
}
String msg = SecureUtil.md5(msgVO.toString());
String key = RedisKey.PRE_MSG.concat(msg);
Boolean isExist = redisTemplate.hasKey(key);
if (isExist) {
log.info("已经发送过的重复消息");
} else {
redisTemplate.opsForValue().set(key, "1", 20, TimeUnit.MINUTES);
log.info("第一次发送的消息");
}
}else{
log.info("收到发送的消息不校验重复");
}
log.info("消费收到JSON格式消息:");
log.info("[{}]", JSON.toJSONString(msgVO));
} catch (Exception e) {
channel.basicReject(deliveryTag, false);
e.printStackTrace();
} finally {
/**
* 确认消息,参数说明:
* long deliveryTag:唯一标识 ID。
* boolean multiple:是否批处理,当该参数为 true 时,
* 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
*/
channel.basicAck(deliveryTag, true);
}
}
3. 0 结果
- 调用词汇过滤,不通过不发送
- 定时发送
详情仓库地址目前还在完善的demo
{
"messageId": 1,
"checkWord": false,
"checkField": "info.content",
"info": {
"pushTime": "2022-08-20 17:41:20",
"content": "选定K个散列函数,用于对元素进行K次散列
}
}