【JavaMail】JavaMail整合RabbitMq发送邮件案例

前言

Linux安装RabbitMQ:https://www.cnblogs.com/jxd283465/p/11975094.html

SpringBoot整合RabbitMQ:https://www.cnblogs.com/jxd283465/p/11975136.html

流程

代码

数据库表

CREATE TABLE `msg_log` (
  `msg_id` varchar(255) NOT NULL DEFAULT ‘‘ COMMENT ‘消息唯一标识‘,
  `msg` text COMMENT ‘消息体, json格式化‘,
  `exchange` varchar(255) NOT NULL DEFAULT ‘‘ COMMENT ‘交换机‘,
  `routing_key` varchar(255) NOT NULL DEFAULT ‘‘ COMMENT ‘路由键‘,
  `status` int(11) NOT NULL DEFAULT ‘0‘ COMMENT ‘状态: 0投递中 1投递成功 2投递失败 3已消费‘,
  `try_count` int(11) NOT NULL DEFAULT ‘0‘ COMMENT ‘重试次数‘,
  `next_try_time` datetime DEFAULT NULL COMMENT ‘下一次重试时间‘,
  `create_time` datetime DEFAULT NULL COMMENT ‘创建时间‘,
  `update_time` datetime DEFAULT NULL COMMENT ‘更新时间‘,
  PRIMARY KEY (`msg_id`),
  UNIQUE KEY `unq_msg_id` (`msg_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘消息投递日志‘;

邮件发送类

package cc.mrbird.febs.common.utils;
/**
 * 邮件发送工具类。
 * 以下邮件中的配置参数,请在实际环境中,根据需要采取合适的配置方式。
 * 发送邮件依赖 com.sun.mail(1.6.1) 包、javax.mail(1.5.0-b01) 包。
 * 如果使用 Idea 运行,请将这两个包(可以直接到Maven目录下面去找)添加到项目的 Libraries 里面(快捷键:Ctrl + Alt + Shift + S)
 *
 * @author Zebe
 */
public class SendEmailUtil {

    /**
     * 发件人别名(可以为空)
     */
    private final static String fromAliasName = "***";

    /**
     * 登录用户名
     */
    private String ACCOUNT;

    /**
     * 登录密码
     */
    private String PASSWORD;

    /**
     * 邮件服务器地址
     */
    //QQ企业邮箱:smtp.exmail.qq.com
    //网易企业邮箱:smtphz.qiye.163.com
    private String HOST;

    /**
     * 发信端口
     */
    //QQ企业邮箱:465
    //网易企业邮箱:994
    private String PORT;

    /**
     * 发信协议
     */
    private final static String PROTOCOL = "ssl";

    /**
     * 收件人
     */
    private String to;

    /**
     * 收件人名称
     */
    private String toName;

    /**
     * 主题
     */
    private String subject;

    /**
     * 内容
     */
    private String content;

    /**
     * 附件列表(可以为空)
     */
    private List<String> attachFileList;

    /**
     * 构造器
     *
     * @param attachFileList 附件列表
     */
    public SendEmailUtil(MailTemplate mailTemplate, List<String> attachFileList) {
        this.to = mailTemplate.getTo();
        this.toName = mailTemplate.getToName();
        this.subject = mailTemplate.getSubject();
        this.content = mailTemplate.getContent();
        this.attachFileList = attachFileList;
        this.ACCOUNT = mailTemplate.getAccount();
        this.PASSWORD = mailTemplate.getPassword();
        switch (mailTemplate.getSendType()) {
            case "qq":
                this.HOST = "smtp.exmail.qq.com";
                this.PORT = "465";
                break;
            case "163":
                this.HOST = "smtp.ym.163.com";
                this.PORT = "994";
                break;
        }
    }

    /**
     * 认证信息
     */
    static class MyAuthenticator extends Authenticator {

        /**
         * 用户名
         */
        String username = null;

        /**
         * 密码
         */
        String password = null;

        /**
         * 构造器
         *
         * @param username 用户名
         * @param password 密码
         */
        public MyAuthenticator(String username, String password) {
            this.username = username;
            this.password = password;
        }

        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication(username, password);
        }
    }

    /**
     * 发送邮件
     */
    public boolean send() {
        // 设置邮件属性
        Properties prop = new Properties();
        prop.setProperty("mail.transport.protocol", PROTOCOL);
        prop.setProperty("mail.smtp.host", HOST);
        prop.setProperty("mail.smtp.port", PORT);
        prop.setProperty("mail.smtp.auth", "true");
        MailSSLSocketFactory sslSocketFactory = null;
        try {
            sslSocketFactory = new MailSSLSocketFactory();
            sslSocketFactory.setTrustAllHosts(true);
        } catch (GeneralSecurityException e1) {
            e1.printStackTrace();
        }
        if (sslSocketFactory == null) {
            System.err.println("开启 MailSSLSocketFactory 失败");
        } else {
            prop.put("mail.smtp.ssl.enable", "true");
            prop.put("mail.smtp.ssl.socketFactory", sslSocketFactory);
            // 创建邮件会话(注意,如果要在一个进程中切换多个邮箱账号发信,应该用 Session.getInstance)
            Session session = Session.getDefaultInstance(prop, new MyAuthenticator(ACCOUNT, PASSWORD));
            // 开启调试模式(生产环境中请不要开启此项)
            session.setDebug(true);
            try {
                MimeMessage mimeMessage = new MimeMessage(session);
                // 设置发件人别名(如果未设置别名就默认为发件人邮箱)
                mimeMessage.setFrom(new InternetAddress(ACCOUNT, fromAliasName));
                // 设置主题和收件人、发信时间等信息
                mimeMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to, toName));
                mimeMessage.setSubject(subject);
                mimeMessage.setSentDate(new Date());
                // 如果有附件信息,则添加附件
                if (!attachFileList.isEmpty()) {
                    Multipart multipart = new MimeMultipart();
                    MimeBodyPart body = new MimeBodyPart();
                    body.setContent(content, "text/html; charset=UTF-8");
                    multipart.addBodyPart(body);
                    // 添加所有附件(添加时判断文件是否存在)
                    for (String filePath : attachFileList) {
                        if (Files.exists(Paths.get(filePath))) {
                            MimeBodyPart tempBodyPart = new MimeBodyPart();
                            tempBodyPart.attachFile(filePath);
                            multipart.addBodyPart(tempBodyPart);
                        }
                    }
                    mimeMessage.setContent(multipart);
                } else {
                    Multipart multipart = new MimeMultipart();
                    MimeBodyPart body = new MimeBodyPart();
                    body.setContent(content, "text/html; charset=UTF-8");
                    multipart.addBodyPart(body);
                    mimeMessage.setContent(multipart);
                    //mimeMessage.setText(content);
                }
                // 开始发信
                mimeMessage.saveChanges();
                Transport.send(mimeMessage);
                return true;
            } catch (MessagingException | IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }
}

邮件模板

@Data
@NoArgsConstructor
public class MailTemplate implements Serializable {

    private String msgId;
    /**
     * 收件人
     */
    private String to;

    /**
     * 收件人名称
     */
    private String toName;

    /**
     * 主题
     */
    private String subject;

    /**
     * 内容
     */
    private String content;

    /**
     * 附件列表
     */
    private List<String> attachFileList;

    /**
     * 邮箱账号
     */
    private String account;

    /**
     * 邮箱密码
     */
    private String password;

    /**
     * 邮箱类型
     */
    private String sendType;

    /**
     * 构造器
     *
     * @param to      收件人
     * @param subject 主题
     * @param content 内容
     */
    public MailTemplate(String account, String password, String sendType, String to, String toName, String subject, String content) {
        this.account = account;
        this.password = password;
        this.sendType = sendType;
        this.to = to;
        this.toName = toName;
        this.subject = subject;
        this.content = content;
    }

    /**
     * 构造器
     *
     * @param to             收件人
     * @param subject        主题
     * @param content        内容
     * @param attachFileList 附件列表
     */
    public MailTemplate(String account, String password, String sendType, String to, String toName, String subject, String content, List<String> attachFileList) {
        this(account, password, sendType, to, toName, subject, content);
        this.attachFileList = attachFileList;
    }
}

rabbit mq配置类

@Configuration
@Slf4j
public class RabbitConfig {

    // 发送邮件
    public static final String MAIL_QUEUE_NAME = "mail.queue";
    public static final String MAIL_EXCHANGE_NAME = "mail.exchange";
    public static final String MAIL_ROUTING_KEY_NAME = "mail.routing.key";
    public final static Integer MAIL_DELIVER_SUCCESS = 1;
    public final static Integer MAIL_DELIVER_FAIL = 2;
    public final static Integer MAIL_CONSUMED_SUCCESS = 3;
    public static boolean ENABLE_SCHEDULED = false;
    private final CachingConnectionFactory connectionFactory;

    @Autowired
    private IMsgLogService iMsgLogService;

    public RabbitConfig(CachingConnectionFactory connectionFactory) {
        this.connectionFactory = connectionFactory;
    }

    @Bean
    public RabbitTemplate rabbitTemplate() {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(converter());

        // 消息是否成功发送到Exchange
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                log.info("消息成功发送到Exchange");
                String msgId = correlationData.getId();
                UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("msg_id",msgId);
                MsgLog msgLog = new MsgLog();
                msgLog.setStatus(MAIL_DELIVER_SUCCESS);
                iMsgLogService.update(msgLog, updateWrapper);
            } else {
                log.info("消息发送到Exchange失败, {}, cause: {}", correlationData, cause);
            }
        });

        // 触发setReturnCallback回调必须设置mandatory=true, 否则Exchange没有找到Queue就会丢弃掉消息, 而不会触发回调
        rabbitTemplate.setMandatory(true);
        // 消息是否从Exchange路由到Queue, 注意: 这是一个失败回调, 只有消息从Exchange路由到Queue失败才会回调这个方法
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            log.info("消息从Exchange路由到Queue失败: exchange: {}, route: {}, replyCode: {}, replyText: {}, message: {}", exchange, routingKey, replyCode, replyText, message);
        });

        return rabbitTemplate;
    }

    @Bean
    public Jackson2JsonMessageConverter converter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    public Queue mailQueue() {
        return new Queue(MAIL_QUEUE_NAME, true);
    }

    @Bean
    public DirectExchange mailExchange() {
        return new DirectExchange(MAIL_EXCHANGE_NAME, true, false);
    }

    @Bean
    public Binding mailBinding() {
        return BindingBuilder.bind(mailQueue()).to(mailExchange()).with(MAIL_ROUTING_KEY_NAME);
    }

}

生产者

@RestController
@RequestMapping("mail")
public class MailController {

    @Autowired
    private IMsgLogService iMsgLogService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/test")
    public void test(String account, String password, String sendType) {
        try {
            for (int j = 1; j <= 3; j++) {
                for (int i = 1; i <= 15; i++) {
                    for (int num = 1; num <= 73; num++) {
                        // 设置发信参数
                        final String toName = "我是" + num + "号";
                        final String to = "test" + num + "@forexgwg.com";
                        String subject = num + " 第" + num + "次发送测试邮件标题";
                        final String content = "<p style=‘color:red‘>这是邮件内容正文。</p></br>";
                        MailTemplate mailTemplate = new MailTemplate();
                        String msgId = UUID.randomUUID().toString();
                        mailTemplate.setMsgId(msgId);
                        mailTemplate.setAccount(account);
                        mailTemplate.setPassword(password);
                        mailTemplate.setSendType(sendType);
                        mailTemplate.setToName(toName);
                        mailTemplate.setTo(to);
                        mailTemplate.setSubject(subject);
                        mailTemplate.setContent(content);
                        mailTemplate.setAttachFileList(new ArrayList<>());

                        MsgLog msgLog = new MsgLog(msgId, JSON.toJSONString(mailTemplate), RabbitConfig.MAIL_EXCHANGE_NAME, RabbitConfig.MAIL_ROUTING_KEY_NAME, LocalDateTime.now());
                        iMsgLogService.save(msgLog);
                        CorrelationData correlationData = new CorrelationData(msgId);
                        Thread.sleep(1000);
                        rabbitTemplate.convertAndSend(RabbitConfig.MAIL_EXCHANGE_NAME, RabbitConfig.MAIL_ROUTING_KEY_NAME, JSON.toJSONString(mailTemplate), correlationData);// 发送消息
                    }
                }
            }
            RabbitConfig.ENABLE_SCHEDULED = true;
        } catch (Exception e) {
            System.out.println("错误: " + e);
        }
    }
}

消费者

@Component
@Slf4j
public class MailConsumer {

    @Autowired
    private IMsgLogService iMsgLogService;

    @RabbitListener(queues = RabbitConfig.MAIL_QUEUE_NAME)
    public void consume(Message message, Channel channel) throws IOException {
        String msg = new String(message.getBody());
        msg = msg.replaceAll("\\\\", "");
        msg = msg.substring(1, msg.length() - 1);
        MailTemplate mailTemplate = JSON.parseObject(msg, MailTemplate.class);
        log.info("收到消息: {}", mailTemplate.toString());

        String msgId = mailTemplate.getMsgId();
        QueryWrapper<MsgLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("msg_id", msgId);
        UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("msg_id", msgId);
        MsgLog msgLog = iMsgLogService.getOne(queryWrapper);

        // 消费幂等性
        if (null == msgLog || msgLog.getStatus().equals(RabbitConfig.MAIL_CONSUMED_SUCCESS)) {
            log.info("重复消费, msgId: {}", msgId);
            return;
        }

        msgLog.setStatus(3);
        msgLog.setUpdateTime(LocalDateTime.now());
        iMsgLogService.update(msgLog, updateWrapper);

        MessageProperties properties = message.getMessageProperties();
        long tag = properties.getDeliveryTag();

        boolean success = new SendEmailUtil(mailTemplate, new ArrayList<>()).send();
        if (success) {
            msgLog.setStatus(RabbitConfig.MAIL_CONSUMED_SUCCESS);
            msgLog.setUpdateTime(LocalDateTime.now());
            iMsgLogService.update(msgLog, updateWrapper);
            log.info("消费成功!");
            channel.basicAck(tag, false);// 消费确认
        } else {
            channel.basicNack(tag, false, true);
        }
    }
}

重新发送

@Component
@Slf4j
public class ResendMsg {

    @Autowired
    private IMsgLogService iMsgLogService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 最大投递次数
    private static final int MAX_TRY_COUNT = 3;

    /**
     * 每30s拉取投递失败的消息, 重新投递
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void resend() {
        log.info("开始执行定时任务(重新投递消息)");
        UpdateWrapper<MsgLog> updateWrapper = new UpdateWrapper<>();
        QueryWrapper<MsgLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.ne("status", RabbitConfig.MAIL_DELIVER_FAIL).ne("status", RabbitConfig.MAIL_CONSUMED_SUCCESS);

        List<MsgLog> msgLogs = iMsgLogService.list(queryWrapper);
        if (msgLogs.size() == 0){
            RabbitConfig.ENABLE_SCHEDULED = false;
        }
        msgLogs.forEach(msgLog -> {
            String msgId = msgLog.getMsgId();
            updateWrapper.eq("msg_id", msgId);
            if (msgLog.getTryCount() >= MAX_TRY_COUNT) {
                msgLog.setStatus(RabbitConfig.MAIL_DELIVER_FAIL);
                msgLog.setUpdateTime(LocalDateTime.now());
                iMsgLogService.update(msgLog, updateWrapper);
                log.info("超过最大重试次数, 消息投递失败, msgId: {}", msgId);
            } else {
                msgLog.setTryCount(msgLog.getTryCount() + 1);
                msgLog.setUpdateTime(LocalDateTime.now());
                msgLog.setNextTryTime(LocalDateTime.now().plusSeconds(60));
                iMsgLogService.update(msgLog, updateWrapper);// 投递次数+1

                CorrelationData correlationData = new CorrelationData(msgId);
                rabbitTemplate.convertAndSend(msgLog.getExchange(), msgLog.getRoutingKey(), msgLog.getMsg(), correlationData);// 重新投递
                log.info("第 " + (msgLog.getTryCount() + 1) + " 次重新投递消息");
            }
        });
        log.info("定时任务执行结束(重新投递消息)");
    }

}

原文地址:https://www.cnblogs.com/jxd283465/p/11982076.html

时间: 2024-10-12 23:23:10

【JavaMail】JavaMail整合RabbitMq发送邮件案例的相关文章

JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件

一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔. 邮件头包含的内容有: from字段  --用于指明发件人 to字段      --用于指明收件人 subject字段  --用于说明邮件主题 cc字段     -- 抄送,将邮件发送给收件人的同时抄送给另一个收件人,收件人可以看到邮件抄送给了谁 bcc字段   -- 密送,将邮件发送给收件人的同时将邮件秘密发送给另一个收件人,收件人

深入分析JavaWeb Item42 -- JavaMail创建邮件和发送邮件

一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔. 邮件头包含的内容有: from字段 –用于指明发件人 to字段 –用于指明收件人 subject字段 –用于说明邮件主题 cc字段 – 抄送,将邮件发送给收件人的同时抄送给另一个收件人,收件人可以看到邮件抄送给了谁 bcc字段 – 密送,将邮件发送给收件人的同时将邮件秘密发送给另一个收件人,收件人无法看到邮件密送给了谁 邮件体指的就

(转载)JavaWeb学习总结(五十二)——使用JavaMail创建邮件和发送邮件

博客源地址:http://www.cnblogs.com/xdp-gacl/p/4216311.html 一.RFC882文档简单说明 RFC882文档规定了如何编写一封简单的邮件(纯文本邮件),一封简单的邮件包含邮件头和邮件体两个部分,邮件头和邮件体之间使用空行分隔. 邮件头包含的内容有: from字段  --用于指明发件人 to字段      --用于指明收件人 subject字段  --用于说明邮件主题 cc字段     -- 抄送,将邮件发送给收件人的同时抄送给另一个收件人,收件人可以看

SpringBoot整合RabbitMQ之典型应用场景实战一

实战前言RabbitMQ 作为目前应用相当广泛的消息中间件,在企业级应用.微服务应用中充当着重要的角色.特别是在一些典型的应用场景以及业务模块中具有重要的作用,比如业务服务模块解耦.异步通信.高并发限流.超时业务.数据延迟处理等. RabbitMQ 官网拜读首先,让我们先拜读 RabbitMQ 官网的技术开发手册以及相关的 Features,感兴趣的朋友可以耐心的阅读其中的相关介绍,相信会有一定的收获,地址可见:http://www.rabbitmq.com/getstarted.html 在阅

SpringBoot整合RabbitMQ之发送接收消息实战

实战前言 前几篇文章中,我们介绍了SpringBoot整合RabbitMQ的配置以及实战了Spring的事件驱动模型,这两篇文章对于我们后续实战RabbitMQ其他知识要点将起到奠基的作用的.特别是Spring的事件驱动模型,当我们全篇实战完毕RabbitMQ并大概了解一下RabbitMQ相关组件的源码时,会发现其中的ApplicationEvent.ApplicationListener.ApplicationEventPublisher跟RabbitMQ的Message.Listener.R

Spring Boot (5) 整合 RabbitMQ

一.前言 RabbitMQ是实现了AMQP(高级消息队列协议)的开源消息中间件,RabbitMQ服务器是用Erlang(面向并发的编程语言)编写的. RabbitMQ官网下载地址:https://www.rabbitmq.com/download.html Docker部署则执行如下命令即可 # RABBITMQ_DEFAULT_USER:用户名,RABBITMQ_DEFAULT_PASS:密码 这里修改为自己的即可 docker run -d --name rabbitmq -p 5672:5

springboot学习笔记-6 springboot整合RabbitMQ

一 RabbitMQ的介绍 RabbitMQ是消息中间件的一种,消息中间件即分布式系统中完成消息的发送和接收的基础软件.这些软件有很多,包括ActiveMQ(apache公司的),RocketMQ(阿里巴巴公司的,现已经转让给apache). 消息中间件的工作过程可以用生产者消费者模型来表示.即,生产者不断的向消息队列发送信息,而消费者从消息队列中消费信息.具体过程如下: 从上图可看出,对于消息队列来说,生产者,消息队列,消费者是最重要的三个概念,生产者发消息到消息队列中去,消费者监听指定的消息

springboot系列-springboot整合RabbitMQ

一 RabbitMQ的介绍 RabbitMQ是消息中间件的一种,消息中间件即分布式系统中完成消息的发送和接收的基础软件.这些软件有很多,包括ActiveMQ(apache公司的),RocketMQ(阿里巴巴公司的,现已经转让给apache). 消息中间件的工作过程可以用生产者消费者模型来表示.即,生产者不断的向消息队列发送信息,而消费者从消息队列中消费信息.具体过程如下: 从上图可看出,对于消息队列来说,生产者,消息队列,消费者是最重要的三个概念,生产者发消息到消息队列中去,消费者监听指定的消息

SpringBoot整合RabbitMQ之整合配置篇

实战背景:RabbitMQ实战第一阶段-RabbitMQ的官网拜读已经结束了,相信诸位童鞋或多或少都能入了个门,如果还是觉得迷迷糊糊似懂非懂的,那我建议诸位可以亲自去拜读拜读官网的技术手册或者看多几篇我的视频跟源码!因为接下来我们将进入第二阶段,即应用实战阶段(备注:第一阶段的内容主要以视频的形式分享,感兴趣的童鞋可以加QQ群:583522159 自行获取) 实战分析:应用实战,当然是指真正的在企业级项目中的应用.在这一阶段中,我将以目前流行的微服务架构为奠基,整合RabbitMQ实现项目中常见