rabbitmq+redis防止消息重复消费

背景:在用户注册或者登录的时候需要获取验证码,使用rabbitmq将需要发送验证码的消息发送到消息队列中

rabbitmq配置

@Configuration
public class RabbitMQConfig {
    public static final Logger LOGGER= LoggerFactory.getLogger(RabbitMQConfig.class);
    // 配置一个工作模型队列
    @Bean
    public Queue queueWork1() {
        return new Queue("queue_work");
    }
    @Autowired
    CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    MailSendLogService mailSendLogService;

    @Bean
    public RabbitTemplate rabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
        //成功投递消息到Broker交换机站点的回调函数
        rabbitTemplate.setConfirmCallback((data,ack,cause)->{
            String msgId = data.getId();
            if(ack){
                LOGGER.info(msgId+"消息发送成功");
                //修改数据库中的记录,消息发送成功,将status设为1
                mailSendLogService.updateMailSendLogStatus(msgId, MailConstants.SUCCESS);
            }else{
                LOGGER.error(msgId+"消息发送失败!");
            }
        });
        //消息投递到Queue队列失败的回调函数
        rabbitTemplate.setReturnCallback((msg,repCode,repText,exchange,routingKey)->{
            LOGGER.error(msg.getBody()+"----消息从交换机投递到队列失败!\n错误原因:"+repText);
            LOGGER.error("发送错误的交换机:"+exchange+",发生错误的路由key:"+routingKey);
        });
        return rabbitTemplate;
    }
    @Bean
    DirectExchange mailExchange(){
        return new DirectExchange(mailExchange,true,false);
    }

    /**
     * 验证码消息队列
     * @return
     */
    @Bean
    Queue mailQueueVerifyCode(){
        return new Queue(mailQueueVerifyCode,true);
    }

}
 @Bean
    Binding mailQueueVerifyCodeBinding(){
        return BindingBuilder.bind(mailQueueVerifyCode()).to(mailExchange()).with(mailRouteVerifyCode);
    }

邮箱发送记录实体类:

public class MailSendLog implements Serializable {
    private static final long serialVersionUID = 740872026109078508L;

    private String msgId;
    /**
    * 0:反馈,1:验证码
    */
    private Integer contentType;

    private String content;

    private Integer status;

    private String routeKey;

    private String exchange;

    private Integer count;

    private Date tryTime;

    private Date createTime;

    private Date updateTime;

}

获取验证码接口:

  @Autowired
  VerifyCodeService verifyCodeService;
/**
   * 获取邮箱验证码,并保存到本次会话
   * @param session
   */
  @GetMapping("/admin/mailVerifyCode")
  public RespBean getMailVerifyCode(HttpSession session){
      String code = verifyCodeService.getVerifyCode();
      //保存验证码到本次会话
      session.setAttribute("mail_verify_code",code);
      verifyCodeService.sendVerifyCodeMail(code);
      return RespBean.ok("验证码已发送到邮箱,请注意查看!");

}

生成验证码和使用rabbitmq发送消息:

@Service("verifyCodeService")
public class VerifyCodeServiceImpl implements VerifyCodeService {
   @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    MailSendLogService mailSendLogService;
    @Value("${mail.exchange:mail-exchange}")
    private String mailExchange;

    @Value("${mail.route.verifyCode:mail-route-verifyCode}")
    private String mailRouteVerifyCode;
    public String getVerifyCode() {
        StringBuilder code=new StringBuilder();
        for (int i = 0; i <4; i++) {
            code.append(new Random().nextInt(10));
        }
        return code.toString();
    }
public void sendVerifyCodeMail(String code) {
        //添加消息记录
        String msgId = UUID.randomUUID().toString();
        MailSendLog mailSendLog = new MailSendLog();
        mailSendLog.setMsgId(msgId);
        mailSendLog.setContent(code);
        mailSendLog.setContentType(MailConstants.VERIFY_CODE_TYPE);
        mailSendLog.setCount(1);
        mailSendLog.setCreateTime(new Date());
        mailSendLog.setTryTime(new Date(System.currentTimeMillis()+1000*10*MailConstants.MEG_TIMEOUT));
        mailSendLog.setUpdateTime(new Date());
        mailSendLog.setExchange(mailExchange);
        mailSendLog.setRouteKey(mailRouteVerifyCode);
        mailSendLog.setStatus(MailConstants.DELIVERING);
        mailSendLogService.insert(mailSendLog);

        //CorrelationData 对象,每个发送的消息都需要配备一个 CorrelationData 相关数据对象,CorrelationData 对象内部只有一个 id 属性,用来表示当前消息唯一性。
        rabbitTemplate.convertAndSend(mailExchange,mailRouteVerifyCode,code,new CorrelationData(msgId));

    }
}

处理验证码消息,防重复消费

@Component
public class VerifyCodeReceiver {
    //保证幂等性(重复消费)
    //要保证消息的幂等性,这个要结合业务的类型来进行处理。下面提供几个思路供参考:
    //
    //1、可在内存中维护一个set,只要从消息队列里面获取到一个消息,先查询这个消息在不在set里面,如果在表示已消费过,直接丢弃;如果不在,则在消费后将其加入set当中。
    //
    //2、如何要写数据库,可以拿唯一键先去数据库查询一下,如果不存在在写,如果存在直接更新或者丢弃消息。
    //
    //3、如果是写redis那没有问题,每次都是set,天然的幂等性。
    //
    //4、让生产者发送消息时,每条消息加一个全局的唯一id,然后消费时,将该id保存到redis里面。消费时先去redis里面查一下有么有,没有再消费。

    @Autowired
    JavaMailSender javaMailSender;
    @Autowired
    StringRedisTemplate redisTemplate;

    private static final Logger LOGGER= LoggerFactory.getLogger(VerifyCodeReceiver.class);

    @RabbitListener(queues = "${mail.queue.verifyCode:mail-queue-verifyCode}")
    public void getMessage(Message message, Channel channel) throws IOException {
        //获取消息内容
        String code = message.getPayload().toString();
        //获取消息头,消息标志tag
        MessageHeaders headers = message.getHeaders();
        Long tag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
        //获取消息ID
        String msgId = (String) headers.get("spring_returned_message_correlation");
        LOGGER.info("【"+msgId+"】-正在处理的消息");
        //查看消息是否已消费
        if (redisTemplate.opsForHash().entries("mail_log").containsKey(msgId)){
            //手动确认消息已消费
            channel.basicAck(tag,false);
            LOGGER.info("【"+msgId+"】消息出现重复消费");
            return;
        }
        //否则进行消息消费
        try{
            System.out.println("验证码管理");
            SimpleMailMessage msg = new SimpleMailMessage();
            msg.setSubject("微言聊天室管理端-验证码验证");
            //TODO 使用Thymeleaf,改邮件模板,添加内容:请不要泄露自己的邮箱验证码
            msg.setText("本次登录的验证码:"+code);
            msg.setFrom("发送者的邮箱地址");
            msg.setSentDate(new Date());
            msg.setTo("接受者的邮箱地址");
            javaMailSender.send(msg);
            //消息发送成功,将id放到redis中,不能这样put
            //redisTemplate.opsForHash().entries("mail_log").put(msgId,code);
            redisTemplate.opsForHash().put("mail_log",msgId,code);
            //确认消息消费成功
            channel.basicAck(tag,false);
        }catch (Exception e){
            //不批量处理,将消息重新放回到队列中
            channel.basicNack(tag,false,true);
            LOGGER.info("【"+msgId+"】消息重新放回到了队列中");
            e.printStackTrace();
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值