邮件发送 -- 消费端与生成端

以网易邮件实例:(可以选用其他邮件网站)

开通SMTP:

 

 网易免费邮箱相关服务器端口号:

 确认开启SMTP服务:

 

分为消费端与生成端:

        消费端是指写入接收邮件的操作,而生成端则是写入生成邮件的操作。

        消费端解决了消息端幂等性问题,避免多次消息投递。        

        生成端介绍了两种可靠性投递方案介绍 -- 消息入库。

消费端:

1.所需依赖 pom.xml

<dependencies>
  <!--rabbitmq 依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
  </dependency>
  <!--mail 依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
    <version>2.7.0</version>
  </dependency>
  <!--thymeleaf 依赖-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.7.0</version>
  </dependency>
  <!-- spring data redis 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- commons-pool2 对象池依赖 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
</dependencies>

2.启动类配置:

 3.配置application.yml文件:

server:
  # 端口
  port: 8082
spring:
  # 邮件配置
  mail:
    # 邮件服务器地址
    host: smtp.163.com
    # 协议
    protocol: smtp
    # 编码格式
    default-encoding: utf-8
    # 授权码(在邮箱开通服务时获取)
    password:  #你的授权码
    # 发送者邮箱地址
    username:  # 你的邮箱地址
    # 端口(不同邮箱端口号不同)
    port: 25
    # rabbitmq配置
  rabbitmq:
    # 用户名
    username: guest
    # 密码
    password: guest
    # 服务器地址
    host: 自己的服务器地址 #192.168.10.100
    # 端口
    port: 5672
    listener:
      simple:
        # 开启手动确认
        acknowledge-mode: manual

  redis:
    #超时时间
    timeout: 10000ms
    #服务器地址
    host: localhost
    #服务器端口
    port: 6379
    #数据库
    database: 0
    lettuce:
      pool:
        #最大连接数,默认8
        max-active: 1024
        #最大连接阻塞时间,默认-1
        max-wait: 10000ms
        #最大空间连接
        max-idle: 200
        #最小空间连接
        min-idle: 5

4.定义邮件模板:

 里面的数据可根据自己情况定义:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.theymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>入职欢迎邮件</title>
</head>
<body>
欢迎 <span th:text="${name}"></span>加入xxxx大家庭,您的入职信息如下:
<table border="1">
    <tr>
        <td>姓名</td>
        <td th:text="${name}"></td>
    </tr>
    <tr>
        <td>职位</td>
        <td th:text="${posName}"></td>
    </tr>
    <tr>
        <td>职称</td>
        <td th:text="${joblevelName}"></td>
    </tr>
    <tr>
        <td>部门</td>
        <td th:text="${departmentName}"></td>
    </tr>
</table>

<p>
    我们公司的工作忠旨是严格,创新,诚信,您的加入将为我们带来新鲜的血液,带来创新的思维,以及为我们树立良好的公司形象!希望在以后的工作中我们能够齐心协力,与时俱进,团结协作!同时也祝您在本公司,工作愉快,实现自己的人生价值!希望在未来的日子里,携手共进!</p>
</body>

</html>

5.消息接收类:MailReceiver

package com.nice.mail;

import com.nice.server.pojo.Employee;
import com.nice.server.utils.MailConstants;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.annotation.Resource;
import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Date;

/**
 * 消息接收者
 */
@Component
public class MailReceiver {
    //引入日志
    private static final Logger LOGGER = LoggerFactory.getLogger(MailReceiver.class);

    @Resource
    private JavaMailSender javaMailSender;  //邮件发送
    @Resource
    private MailProperties mailProperties;  //邮件配置
    @Resource
    private TemplateEngine templateEngine;  //模板引擎
    @Resource
    private RedisTemplate redisTemplate;

    @RabbitListener(queues = MailConstants.MAIL_QUEUE_NAME)
    public void handler(Message message, Channel channel){//Employee employee改为这两形参
        Employee employee = (Employee) message.getPayload();
        MessageHeaders headers = message.getHeaders();
        //消息序号
        long tag = (long) headers.get(AmqpHeaders.DELIVERY_TAG);
        String msgId = (String) headers.get("spring_returned_message_correlation");//固定
        HashOperations hashOperations = redisTemplate.opsForHash();

        try {
            if (hashOperations.entries("mail_log").containsKey(msgId)){
                LOGGER.error("消息已经被消费============>{}",msgId);
                /**
                 * 手动确认消息
                 * tag:消息序号
                 * multiple:是否确认多条
                 */
                channel.basicAck(tag,false);
                return;
            }
            MimeMessage msg = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(msg);
            //发件人
            helper.setFrom(mailProperties.getUsername());
            //收件人
            helper.setTo(employee.getEmail());
            //主题
            helper.setSubject("入职欢迎邮件");
            //发送日期
            helper.setSentDate(new Date());
            //邮件内容
            Context context = new Context();
            context.setVariable("name",employee.getName());
            context.setVariable("posName",employee.getPosition().getName());
            context.setVariable("joblevelName",employee.getJoblevel().getName());
            context.setVariable("departmentName",employee.getDepartment().getName());
            String mail = templateEngine.process("mail", context);
            helper.setText(mail,true);
            //发送邮件
            javaMailSender.send(msg);
            LOGGER.info("邮件发送成功");
            //将消息id存入redis
            hashOperations.put("mail_log",msgId,"OK");
            //手动确认消息
            channel.basicAck(tag,false);
        } catch (Exception e) {
            /**
             * 手动确认消息
             * tag: 消息序号
             * multiple: 是否确认多条
             * requeue: 是否退回到队列
             */
            try {
                channel.basicNack(tag,false,true);
            } catch (IOException ex) {
                LOGGER.error("邮件发送失败========>{}",e.getMessage());
            }
            LOGGER.error("邮件发送失败========>{}",e.getMessage());
        }
    }

}

6.消费端幂等性操作:

        避免多次消息投递

        使用最简单的方法,利用缓存Redis但不考虑其原子性问题:

        开启手动确认:

 修改消息接收的地方:

 

生成端:

功能实现流程:

        1).发送消息时,将当前消息数据存入数据库,,投递状态为消息投递中

        2).开启消息确认回调机制。确认成功,更新投递状态为消息投递成功。

        3).开启定时任务,重新投递失败信息。重试超过3次,更新投递状态为投递失败

1.先添加消息状态类:

package com.nice.server.utils;

/**
 * 消息状态
 */
public class MailConstants {
    //消息投递中
    public static final Integer DELIVERING = 0;

    //消息投递成功
    public static final Integer SUCCESS = 1;

    //消息投递失败
    public static final Integer FAILURE = 2;

    //最大重试次数
    public static final Integer MAX_TRY_COUNT = 3;

    //消息超时时间
    public static final Integer MSG_TIMEOUT = 1;

    //队列
    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";
}

2.开启消息回调机制:(消息落户)

        RabbitMQ配置类

package com.nice.server.config;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.nice.server.pojo.MailLog;
import com.nice.server.service.IMailLogService;
import com.nice.server.utils.MailConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;


/**
 * RabbitMQ配置类
 */
@Configuration
public class RabbitMQConfig {

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

    @Resource
    private CachingConnectionFactory cachingConnectionFactory;
    @Resource
    private IMailLogService mailLogService;

    @Bean
    public RabbitTemplate rabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
        /**
         * 消息确认回调,确认消息是否到达broker
         * data: 消息唯一标识
         * ack: 确认结果
         * couse: 失败原因
         */
        rabbitTemplate.setConfirmCallback((data,ack,cause) -> {
            String msgId = data.getId();
            if (ack){
                LOGGER.info("{}========>消息发送成功",msgId);
                mailLogService.update(new UpdateWrapper<MailLog>().set("status",1).eq("msgId",msgId));
            }else {
                LOGGER.error("{}========>消息发送失败",msgId);
            }
        });
        /**
         * 消息失败回调,比如router不到queue时回调
         * msg:消息主题
         * repCode:响应码
         * repText: 相应描述
         * exchange: 交换机
         * routingkey: 路由键
         */
        rabbitTemplate.setReturnCallback((msg,repCode,repText,exchange,routingkey) -> {
            LOGGER.error("{}=======>消息发送queue时失败",msg.getBody());
        });
        return rabbitTemplate;
    }

    @Bean
    public Queue queue(){
        return new Queue(MailConstants.MAIL_QUEUE_NAME);
    }

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(MailConstants.MAIL_EXCHANGE_NAME);
    }

    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(directExchange()).with(MailConstants.MAIL_ROUTING_KEY_NAME);
    }

}

要在application.yml 文件中设置rabbitmq的消息确认回调与消息失败回调

3.生成端可靠性投递方案介绍:

        方案:(业务消息入库)

 

 第二个方案中数据库操作减少了

此处用第二种方法会比较好,减少对数据库的操作

实现:邮件发送定时任务

package com.nice.server.task;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.nice.server.pojo.Employee;
import com.nice.server.pojo.MailLog;
import com.nice.server.service.IEmployeeService;
import com.nice.server.service.IMailLogService;
import com.nice.server.utils.MailConstants;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 邮件发送定时任务
 */
@Component
public class MailTask {

    @Resource
    private IMailLogService mailLogService;
    @Resource
    private IEmployeeService employeeService;
    @Resource
    private RabbitTemplate rabbitTemplate;
    /**
     * 邮件发送定时任务
     * 10秒执行一次
     */
    @Scheduled(cron = "0/10 * * * * ?")
    public void mailTask(){
        List<MailLog> list = mailLogService.list(new QueryWrapper<MailLog>()
                .eq("status", 0)
                .lt("tryTime", LocalDateTime.now()));
        list.forEach(mailLog -> {
            //如果重试次数超过3次,更新状态为投递失败,不再重试
            if (3<=mailLog.getCount()){
                mailLogService.update(new UpdateWrapper<MailLog>()
                        .set("status",2).eq("msgId",mailLog.getMsgId()));
            }
            mailLogService.update(new UpdateWrapper<MailLog>().set("count",mailLog.getCount()+1)
                    .set("updateTime",LocalDateTime.now()).set("tryTime",LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT))
                    .eq("msgId",mailLog.getMsgId()));
            Employee emp = employeeService.getEmployee(mailLog.getEid()).get(0);
            //发送消息
            rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,MailConstants.MAIL_ROUTING_KEY_NAME,emp,
                    new CorrelationData(mailLog.getMsgId()));

        });
    }
}

在启动类中开启定时任务:

生成端是在功能实现模块中一起写的,所以在添加员工操作中实现了把邮件发送给新入职的员工:

/**
     * 添加员工
     * @param employee
     * @return
     */
    @Override
    public Result addEmp(Employee employee) {
        //处理合同期限,保留2位小数
        LocalDate beginContract = employee.getBeginContract();
        LocalDate endContract = employee.getEndContract();
        long days = beginContract.until(endContract, ChronoUnit.DAYS);
        DecimalFormat decimalFormat = new DecimalFormat("##.00");
        employee.setContractTerm(Double.parseDouble(decimalFormat.format(days / 365.00)));
        if (1 == employeeMapper.insert(employee)) {
            Employee emp = employeeMapper.getEmployee(employee.getId()).get(0);
            //数据库记录发送的消息
            String msgId = UUID.randomUUID().toString();
//            String msgId = "123456";
            MailLog mailLog = new MailLog();
            mailLog.setMsgId(msgId);
            mailLog.setEid(employee.getId());
            mailLog.setStatus(0);
            mailLog.setRouteKey(MailConstants.MAIL_ROUTING_KEY_NAME);
            mailLog.setExchange(MailConstants.MAIL_EXCHANGE_NAME);
            mailLog.setCount(0);
            mailLog.setTryTime(LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT));
            mailLog.setCreateTime(LocalDateTime.now());
            mailLog.setUpdateTime(LocalDateTime.now());
            mailLogMapper.insert(mailLog);

            //发送信息
            rabbitTemplate.convertAndSend(MailConstants.MAIL_EXCHANGE_NAME,
                    MailConstants.MAIL_ROUTING_KEY_NAME,emp, new CorrelationData(msgId));
            return Result.success("添加成功!");
        }
        return Result.error("添加失败!");
    }

 功能邮件发送实现仅供大家学习参考。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值