进行词汇过滤的布隆类型,目前采用了2种方式:

  1. 一种是redssion的布隆过滤器和hankcs分词,实现简单,在redis实现快,但是在分词效率和包含判断上如果文本内容过多,查询效率低下,并且布隆过滤器存在误差;
  2. 另外一种更高效的方式是:从Mysql里面加载数据put进去ConcurrentHashMap,结合DFA算法进行处理,查询判断效率高
  3. 本项目结合和RabbitMq,在实现消息定时和即时发送,保障消息的可靠性,多种字段属性不可重复消费的配置,和对内容的过滤判断,并且返回的数据中,包含了过滤词汇的下标索引,这样就可以进行高亮显示

1. 集成redisson和Rabbitmq和其它工具

  1. hankcs的引入是为进行内容的简单分词
  2. redisson布隆过滤器的实现和其它使用
  3. hutool 工具包和雪花算法
  4. mybatis-plus 集成使用
  5. 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安装插件的方式,但是大同小异:

  1. 下载rabbitmq_delayed_message_exchange-*.ez插件并上传到指定文件夹中
    下载地址:延迟插件下载
  2. 插件拷贝至容器中
    docker cp rabbitmq_delayed_message_exchange-3.8.0.ez rabbitmq:/plugins
  3. 进入容器,并查看插件启动插件
# 进入
docker exec -it rabbitmq /bin/bash 
# 查询插件
rabbitmq-plugins list
# 启动
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
  1. 重启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消息的可靠性
  1. 保证消息发送到消息队列中去
  2. 保证消费成功接受消息
  3. 保证生产者收到到mq消费者确认应答
  4. 消息持久化,重试

手动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 结果

  1. 调用词汇过滤,不通过不发送
  2. 定时发送

支付系统防止重复支付 为什么用redis实现幂等性 redis解决重复消费_java-rabbitmq

详情仓库地址目前还在完善的demo

{
	"messageId": 1,
	"checkWord": false,
	"checkField": "info.content",
	"info": {
		"pushTime": "2022-08-20 17:41:20",
		"content": "选定K个散列函数,用于对元素进行K次散列
	}
}