SpringBoot + JavaMail + RabbitMQ实现异步邮件发送功能

您所在的位置:网站首页 java邮件接收 SpringBoot + JavaMail + RabbitMQ实现异步邮件发送功能

SpringBoot + JavaMail + RabbitMQ实现异步邮件发送功能

2023-08-11 01:44| 来源: 网络整理| 查看: 265

在实际开发中经常会遇到“发送邮件”的场景。这个功能的开发非常的简单,我们可以引入JavaMail组件进行开发,编码简单,功能强大,可以实现多种邮件发送功能(纯文本、单附件、多附件...)。但是邮件发送需要调用第三方邮件提供商的服务,这一过程往往需要消耗大量时间。而在传统的业务层中开发者又是通过同步的方式来实现功能。这就不能让用户获得一个良好的用户体验。所以我们可以考虑使用异步的方式实现邮件的发送。

说道异步,立马可以想到MQ消息中间件。市面上比较主流的MQ有,RabbitMQ、kafka、ActiveMQ以及“阿里爸爸”旗下大名鼎鼎的RocketMQ等等。RabbitMQ是一款开源且高效的AMQP消息队列实现,也是市面上比较主流的消息队列组件。这次我就选择RabbitMQ来实现异步邮件通知的功能。

废话不多说,先上代码。

pom.xml文件中引入java mail 以及amqp的相关jar包。

org.springframework.boot spring-boot-starter-amqp org.springframework.boot spring-boot-starter-mail org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.amqp spring-rabbit-test test com.google.code.gson gson

application.yml

server: port: 8080 spring: mail: host: smtp.163.com username: xxxxx #这里写发送邮件的邮箱地址 password: xxxxx #这里是第三方授权码,需要去邮件设置中申请 default-encoding: utf-8 properties: mail: smtp: auth: true starttls: enable: true required: true thymeleaf: encoding: UTF-8 cache: false servlet: content-type: text/html suffix: .html

首先先写一个邮件发送类,代码如下。

@Service(value = "myMailSender") public class MailSender { private static Logger logger = LoggerFactory.getLogger(MailSender.class); @Resource JavaMailSender javaMailSender; @Value("${spring.mail.username}") private String fromMailAddress; public void send(MailMsg msg) { SimpleMailMessage simpleMailMessage = new SimpleMailMessage(); simpleMailMessage.setFrom(fromMailAddress); simpleMailMessage.setTo(msg.getReceiver()); simpleMailMessage.setSubject(msg.getSubject()); simpleMailMessage.setText(msg.getContent()); long begin = System.currentTimeMillis(); javaMailSender.send(simpleMailMessage); long end = System.currentTimeMillis(); logger.info("use time: {} ms", (end - begin)); } }

然后在controller层中调用。

@Controller public class MailController { @Resource private MailSendService mailSendService; @GetMapping("/mail/sendMail") @ResponseBody public String send() { String msg = null; try { Long useTime = mailSendService.send(new MailMsg("[email protected]", "邮件测试", "这是一测试邮件")); msg = "success, uset time " + useTime + " ms"; } catch (Exception e) { msg = e.getMessage(); } return msg; } }

新增一个html ,加入一个a标签。我这里使用了thymeleaf模板引擎,将href 属性直接路由到了 /mail/sendMail 路径下。

Title 点击同步发送邮件

打开浏览器,访问http://localhost:8080。点击同步发送邮件。

经过了几十秒的等待,我们看到我接受到了邮件。并且页面上也有了返回结果。可以看到,邮件发送的功能是完成了,但是我让用户等了40多秒钟,可以说用户体验非常差,不够友好。

接下来,我换一种方式,使用消息队列先将邮件对象存储起来。然后再由消费者程序来发送邮件。

需要完成这一功能,首先应该在application.yml中加入RabbitMQ的配置。

rabbitmq: host: localhost port: 5672 username: itsu password: itsu virtual-host: /app publisher-returns: true publisher-confirm-type: correlated template: mandatory: true connection-timeout: 1000ms listener: simple: acknowledge-mode: manual prefetch: 10 concurrency: 1 max-concurrency: 10

然后我来定义一个生产者, 创建一个exchange交换机“email_exchange”, 并定义了一个route key 为test.email。 值得一说的是CorrelationData 对象是用来定义附加的参数。一般用来保存生产者用户自定义的唯一标识。我这里用UUID来实现。confirmCallback 和 returnCallBack 分别代表了RabbitMQ的 confirm - return 机制。这里不细说,有需要的小伙伴可以去访问RabbitMQ的官网了解。

@Resource RabbitTemplate rabbitTemplate; RabbitTemplate.ConfirmCallback confirmCallback = (correlationData, ack, cause) -> { Gson gson = new Gson(); logger.info(gson.toJson(correlationData)); if (!ack) { logger.warn(cause); } }; RabbitTemplate.ReturnCallback returnCallback = (message, replyCode, replyText, exchange, routingKey) -> { Gson gson = new Gson(); logger.info(gson.toJson(message)); System.err.println(replyCode); System.err.println(replyText); System.err.println(exchange); System.err.println(routingKey); }; public Long sendToQueue(MailMsg mailMsg) { long begin = System.currentTimeMillis(); rabbitTemplate.setConfirmCallback(confirmCallback); rabbitTemplate.setReturnCallback(returnCallback); CorrelationData cd = new CorrelationData(); cd.setId(UUID.randomUUID().toString()); rabbitTemplate.convertAndSend("email_exchange", "test.email", mailMsg, cd); long end = System.currentTimeMillis(); return end - begin; }

接下来定义消费者。@RabbitListener用来监听绑定交换机和队列,并且可以在其中定义交换机的类型,route key的值。在消费者这一边需要将生产者定义的route key用来接收消息。@RabbitHandler注解是指被这个注解标记的方法是用来处理消费者接受到的消息对象的。@Payload 表示消息队列需要被反序列化到MailMsg对象中。 @Headers 可以和Http请求中的HttpHeaders类比。是用来存储RabbitMQ 返回给消费者的一些基本信息。 

@Service public class MailConsumer { private static Logger logger = LoggerFactory.getLogger(MailConsumer.class); @Resource(name = "myMailSender") private MailSender mailSender; @RabbitListener( bindings = { @QueueBinding(value = @Queue( value = "email_queue", durable = "false", autoDelete = "false" ), exchange = @Exchange( value = "email_exchange", durable = "false", autoDelete = "false", type = "topic" ), key = "test.email" ) } ) @RabbitHandler public void realSendMail(@Payload MailMsg mailMsg, Channel channel, @Headers Map headers) { Gson gson = new Gson(); logger.info("===============================received msg ==========================================="); logger.info(gson.toJson(mailMsg)); mailSender.send(mailMsg); try { channel.basicAck((Long) headers.get(AmqpHeaders.DELIVERY_TAG), false); } catch (IOException e) { e.printStackTrace(); } } }

在controller中加上一个方法用来处理异步邮件通知。

@GetMapping("/mail/sendMailByMq") @ResponseBody public String sendByMq() { String msg = null; try { Long useTime = mailSendService.sendToQueue(new MailMsg("[email protected]", "邮件测试", "这是一测试邮件")); msg = "success, use time " + useTime; } catch (Exception e) { msg = e.getMessage(); } return msg; }

然后我们在页面上加上一个a标签。

Title 点击同步发送邮件 点击异步发送邮件

然后重启应用,测试一下。可以看到这一次仅仅用了9ms就返回结果给前端了。相比于同步处理时让用户等待40多秒,这样的用户体验肯定是大大提高了。

我们打开Rabbitmq management 可以看到,刚刚的邮件发送功能。MQ 作为Broker接收到了一个消息,并且通过Queue派发给了一个消费者。

 

 

40 秒后,我又收到了邮件提醒。需要一提的是,RabbitMQ 并不会真正加快你的邮件发送速度。只是通过异步的方式,提前返回结果给到前端。实际邮件发送的时间任然需要40秒,只是在用户体验上提高了等级。消息队列比较常用的使用场景有,高并发场景下的流量削峰,分布式系统中的信息传递等等。

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3