RabbitMQ异步发送邮件

1.邮件发送接口(伪代码):

      public void toSendMsg(HttpServletRequest request, HttpServletResponse response) {
            //1.校验是否为非法请求
            //xxx
            //2.接收参数
              String emails = request.getParameter("emails");//多封邮件(可接收分隔符拼接的emails,也可以选择接受email数组)
              String emailContent = request.getParameter("emailContent");//邮件的固定格式内容
             // ...
                try {                    
                        new  SendEmailThread(email,emailContent).start(); //实例化发送邮件的线程。此处可以用局部内部类或者java8的lambda代替第二步,不过上面的局部变量要用final修饰                                          
                        //操作间隔时间限制
                        //xxx                                    
                } catch (Exception e) 
                    e.printStackTrace();
                } finally {
                    //xxx
                }
            }

2.构建线程对象

    class SendEmailThread extends Thread{    
                private String emails;
                private String emailContent;
                SendEmailThread(String emails,String emailContent){
                    this.emails=emails;
                    this.emailContent=emailContent;
                }
                public void run(){
                        try {
                            sendEmail(emails,emailContent);//将参数传入负责执行发送逻辑的方法
                        }catch (Exception e) {
                            e.printStackTrace();
                        }
                }
            }

3.发送逻辑方法sendEmail  

 

//此处根据自己的业务逻辑自行设计,可将邮件及其内容写入redis队列,或者有MQ软件环境的写入MQ,再用定时任务进行发送。发送之后将发送结果写入数据库或者logger记录,方便查询发送是否成功。具体实现待发布

 ----------------------------------------------------我是分隔符---------------------------------------------------------------------------------------------------------------

22年由于项目需要接触了RabbitMQ,并用于异步发送邮件,可以更快地响应用户请求

基于springboot项目,相关配置:

spring:
rabbitmq:
host: #机器ip
port: #端口号5672
username: xxx
password: xxx
virtual-host: /
# 开启confirms回调 P -> Exchange 解决消息没有到达交换机问题
publisher-confirm-type: correlated
# 开启returnedMessage回调 Exchange -> Queue 解决消息没从交换机路由到队列问题
publisher-returns: true
# 设置手动确认(ack) Queue -> C
listener:
simple:
acknowledge-mode: manual #消费者确认后,消息才会从队列中删除
prefetch: 100
retry: #重试机制
enabled: true #是否开启消费者重试
max-attempts: 3 #最大重试次数
initial-interval: 200 #重试间隔时间(单位毫秒)
max-interval: 10000 #重试最大时间间隔(单位毫秒)
multiplier: 2 #间隔时间乘子,间隔时间*乘子=下一次的间隔时间,最大不能超过设置的最大间隔时间
 

声明一个消息队列的配置类RabbitMqConfig,本次采用的消息模型是Routing直连,使用了DirectExchange,并初始化绑定了交换机和队列

package com.xx;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class RabbitMqConfig2 {
    @Value("${spring.rabbitmq.host}")
    private String host;

    @Value("${spring.rabbitmq.port}")
    private int port;

    @Value("${spring.rabbitmq.username}")
    private String username;

    @Value("${spring.rabbitmq.password}")
    private String password;

    @Value("${spring.rabbitmq.virtual-host}")
    private String virtualHost;

    public static final String QUEUE_MAIL = "QUEUE_MAIL";//邮件队列
    public static final String ROUTINGKEY_MAIL = "ROUTINGKEY_MAIL";
    public static final String EXCHANGE_MAIL = "EXCHANGE_MAIL";

    //建立一个连接
    @Bean
    public ConnectionFactory connectionFactory() {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host, port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualHost);
        //connectionFactory.setPublisherConfirms(true);//确认机制
        connectionFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
        connectionFactory.setPublisherReturns(true);
        //发布确认,template要求CachingConnectionFactory的publisherConfirms属性设置为true
        return connectionFactory;
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    //必须是prototype类型
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate template = new RabbitTemplate(this.connectionFactory());
        template.setMessageConverter(this.jsonMessageConverter());
        template.setMandatory(true);
        return template;
    }

    @Bean
    public Queue queueMail() {
        /*
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         */
        return new Queue(QUEUE_MAIL, true, false, false); //队列持久
    }

    /**
     * 针对消费者配置
     * 1. 设置交换机类型
     * 2. 将队列绑定到交换机
     * FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念
     * HeadersExchange :通过添加属性key-value匹配
     * DirectExchange:按照routingkey分发到指定队列
     * TopicExchange:多关键字匹配
     */
    @Bean
    public DirectExchange exchangeMail() {
        return new DirectExchange(EXCHANGE_MAIL, true, false);
    }

    //将队列和交换机绑定, 并设置用于匹配键
    @Bean
    public Binding bindDirect() {
        return BindingBuilder.bind(queueMail()).to(exchangeMail()).with(ROUTINGKEY_MAIL);
    }
    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

开发生产者

@Slf4j
@Service
public class MailProduceServiceImpl implements MailProduceService {

    //RabbitMqConfig配置类
    private final RabbitMqConfig rabbitMqConfig;

    @Autowired
    public MailProduceServiceImpl(RabbitMqConfig rabbitMqConfig) {
        this.rabbitMqConfig = rabbitMqConfig;
    }

    @Override
    public void productMessage(Message message) { //message是自定义封装的类,存的是一些业务信息(例如邮件收发人、邮件内容等)

        //交换机确认 回调接口
        //消息没有到达交换机问题:对应yml配置:开启生产者确认publisher-confirm-type: correlated
        // correlationData  存储消息的ID和自己存储的关于该条消息的信息
        // boolean ack      判断是否发送成功
        // cause            发送失败是失败的原因
        rabbitMqConfig.rabbitTemplate().setConfirmCallback((correlationData, ack, cause) -> {
            if (!ack) {
                log.warn("消息发送到exchange失败,correlationData={}, cause={}", correlationData, cause);
                // 消息确认失败的处理操作
                message.setFailureCause(cause);
                message.setStatus("2");
            } else {
                message.setStatus("1");
                if (log.isDebugEnabled()) {
                    log.debug("消息{},交换机已收到", correlationData);
                }
            }
           //根据需要此处可将message入库,方便以后追踪
        });


        //队列消息回退接口
        //消息没从交换机路由到队列问题:yml开启生产回执publisher-returns: true
        rabbitMqConfig.rabbitTemplate().setReturnsCallback(returned -> {
            log.info("未找到路由为{}的队列", returned.getRoutingKey());
            message.setStatus("2");
            message.setFailureCause(returned.getReplyText());
           // 根据需要此处可将message入库,方便以后追踪
        });
        //创建uuid
        String msgId = UUID.randomUUID().toString().replaceAll("-", "");
        message.setCid(msgId);// CorrelationData ID  (消息ID)

        //避免重复消息:CorrelationData 对象,每个发送的消息都需要配备一个 CorrelationData 相关数据对象,
        //CorrelationData 对象内部只有一个 id 属性,用来表示当前消息唯一性。
        CorrelationData correlationData = new CorrelationData(msgId);


        //发送消息到rabbitMQ
        rabbitMqConfig.rabbitTemplate().convertAndSend("EXCHANGE_MAIL", "ROUTINGKEY_MAIL", message, correlationData);
        log.warn("productMail message:{}" , message);
        // 根据需要此处可将message入库,方便以后追踪
    }
}

开发消费者

/**
 * 消费消息
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class RabbitListenterServiceImpl2 {
  //发送邮件的业务类
    private final MailSenderService mailSenderService;
    
    @RabbitListener(queues = RabbitMqConfig.QUEUE_FACULTY_MAIL)
    public void sendFacultyFundProcess(Message message, Channel channel) throws IOException {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MessageProperties properties = message.getMessageProperties();
        long tag = properties.getDeliveryTag();
        log.warn("消费者获取消息:{}"
                , MessageHelper.msgToObj(message, com.xxMessage.class).toString());

        //发送邮件业务代码
        mailSenderService.send(message);
        channel.basicAck(tag, false);//消息确认
    }
}

其他,使用javaMail发送邮件相关代码

@Slf4j
@Primary
@Service
@RequiredArgsConstructor
public class DefaultMailServiceImpl implements MailService {

    private final JavaMailSender javaMailSender;
    @Value("${spring.mail.username}")
    private String from;
    @Value("${spring.mail.nickname}")
    private String nickname;

    /**
     * @param to      接收者
     * @param subject 标题
     * @param content 内容
     * @param isHTML  是否是HTML字段
     * @desc 方法功能: 发送邮件
     */
    @Override
    public void sendMail(String to, String subject, String content, boolean isHTML) {
        SendMailDTO dto = new SendMailDTO();
        dto.setToList(Collections.singletonList(to));
        dto.setSubject(subject);
        dto.setContent(content);
        dto.setHtml(isHTML);
        sendMail(dto);
    }

    @Override
    public void sendMail(SendMailDTO dto) {
        MimeMessage message = javaMailSender.createMimeMessage();
        if (CollectionUtil.isEmpty(dto.getToList())) {
            throw new CustomException(BUSINESS.getCode(), BUSINESS.getMessage());
        }
        try {
            //true表示需要创建一个multipart message
            MimeMessageHelper helper = new MimeMessageHelper(message, true);
            helper.setFrom(from, nickname);
            helper.setTo(dto.getToList().toArray(new String[0]));
            if (CollectionUtil.isNotEmpty(dto.getCcList())) {
                helper.setCc(dto.getCcList().toArray(new String[0]));
            }
            if (CollectionUtil.isNotEmpty(dto.getBccList())) {
                helper.setBcc(dto.getBccList().toArray(new String[0]));
            }
            helper.setSubject(dto.getSubject());
            helper.setText(dto.getContent(), dto.isHtml());
            javaMailSender.send(message);
        } catch (Exception e) {
            String contextStr = String.format("to=%s,subject=%s,content=%s,ishtml=%s"
                    , dto.getToList(), dto.getSubject(), dto.getContent(), dto.isHtml());
            log.info(String.format("send_mail_error!errorMsg=%s, context=%s"
                    , e.getMessage(), contextStr), e);
            throw new CustomException(BUSINESS.getCode(), BUSINESS.getMessage());
        }
    }

}

 

posted @ 2020-06-22 15:20  DaDa~  阅读(358)  评论(0编辑  收藏  举报