作者:ChenZhen
本人不常看CSDN消息,有问题通过下面的方式联系:
- 邮箱:[email protected]
- vx: ChenZhen_7
我的个人博客地址:https://www.chenzhen.space/
版权:本文为博主的原创文章,本文版权归作者所有,转载请附上原文出处链接及本声明。
如果对你有帮助,请给一个小小的star⭐
邮件提醒功能:当你收到某个人的回复时,会给你发送一封提醒邮件,并展示回复的内容。
我觉得对于一个博客,邮件回复的功能是必不可少的,能让你及时的回复别人的评论,还能让我更方便的和网上的人对线
其实这个功能还是蛮好实现的,我们先演示怎么用java发送一封简单的邮件
以QQ邮箱为例:
经过身份认证过后,会获得一串授权码,请务必保存下来
由于Spring推出了关于Mail的JavaMailSender类,基于该类Spring Boot又对其进行了进一步封装,从而实现了轻松发送邮件的集成。而且JavaMailSender类提供了强大的邮件发送能力,支持各种类型的邮件发送。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-mailartifactId>
dependency>
spring:
mail:
host: smtp.qq.com # 配置 smtp 服务器地址
port: 25 # smtp 服务器端口
username: xxxxx #配置你的邮箱地址
password: xxxxx #配置申请到的授权码
protocol: smtp #协议
thymeleaf-html: mail #设置要解析发送的html模板(需要你将.html文件放到/resources/templates下面)
配置完后,我们来测试发送一封普通的邮件
由于Spring Boot的starter模块提供了自动化配置,在引入了spring-boot-starter-mail依赖之后,会根据配置文件中的内容去创建JavaMailSender实例并交给spring管理,因此我们可以直接在需要使用的地方直接@Autowired来引入 JavaMailSender 邮件发送对象
注意:使用@Autowired注解的类必须交由spring管理,即加上@Component注解
使用SimpleMailMessage对象来构建一封邮件
@Test
public void test5(){
//构建邮件内容对象
SimpleMailMessage msg = new SimpleMailMessage();
//邮件发送者
msg.setFrom("[email protected]");
//邮件接收者
msg.setTo("[email protected]");
//邮件主题
msg.setSubject("测试邮件主题");
//邮件正文
msg.setText("测试邮件内容");
//邮件发送时间
msg.setSentDate(new Date());
//邮件发送
javaMailSender.send(msg);
}
运行测试方法,需要等待一会便能收到
上面演示了如何发送普通邮件,接下来我们要实现发送一封静态邮件模板,就像开头的实例一样
这里我们使用Thymeleaf作为模板
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
这里可以自己自定义好看的邮件模板,这里我就简单的测试一下
DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml"><head>
<meta charset="UTF-8">
<title>Thymeleaf邮件模板title>
head>
<body>
<p>这是一封Thymeleaf邮件模板p>
<table border="1">
<tr>
<td>姓名td>
<td th:text="${name}">td>
tr>
<tr>
<td>年龄td>
<td th:text="${age}">td>
tr>
table>
<div style="color: red;">这是一封Thymeleaf邮件模板div>
body>
html>
将该html文件放到templates目录下,这是springboot放置模板文件的默认路径
如果觉得这个模板不好看的话,也可以去我的项目源码使用我的邮件模板,就是开头那一个。
运行测试方法
@Test
public void test4() throws MessagingException {
MimeMessage msg = javaMailSender.createMimeMessage();//构建邮件
MimeMessageHelper helper = new MimeMessageHelper(msg, true);//设置可选文本或添加内联元素或附件,
helper.setFrom("[email protected]");//发件人
helper.setSentDate(new Date());//发送日期
helper.setSubject("这是测试主题(thymeleaf)");//发送主题
helper.setTo("[email protected]");//收件人
Context context = new Context();//构建上下文环境
context.setVariable("name","高级工程师");
context.setVariable("age", 19);
String process = springTemplateEngine.process("table", context);//将模板解析成静态字符串
helper.setText(process,true);//内容是否设置成html,true代表是
javaMailSender.send(msg);//发送
}
成功收到
package com.chenzhen.blog.pojo;
import java.util.Date;
/**
* @author ChenZhen
* @Description
* @create 2022/9/27 11:52
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
public class Mail {
//发件人邮箱账号(固定为我自己 即博主本人)
private String sendMailAccount;
//收件人邮箱账号
private String acceptMailAccount;
//收件人姓名
private String name;
//收件人评论的内容
private String comment;
//回复收件人的 回复者的姓名
private String respondent;
//回复者的回复内容
private String reply;
//评论发生的地方链接(回复者是在哪里回复收件人的)
private String address;
//邮件主题
private String theme;
//发送时间
private Date sendTime = new Date();
public Mail() {
}
public Mail(String sendMailAccount, String acceptMailAccount, String name, String comment, String respondent, String reply, String address, String theme) {
this.sendMailAccount = sendMailAccount;
this.acceptMailAccount = acceptMailAccount;
this.name = name;
this.comment = comment;
this.respondent = respondent;
this.reply = reply;
this.address = address;
this.theme = theme;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSendMailAccount() {
return sendMailAccount;
}
public void setSendMailAccount(String sendMailAccount) {
this.sendMailAccount = sendMailAccount;
}
public String getAcceptMailAccount() {
return acceptMailAccount;
}
public void setAcceptMailAccount(String acceptMailAccount) {
this.acceptMailAccount = acceptMailAccount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getRespondent() {
return respondent;
}
public void setRespondent(String respondent) {
this.respondent = respondent;
}
public String getReply() {
return reply;
}
public void setReply(String reply) {
this.reply = reply;
}
public String getTheme() {
return theme;
}
public void setTheme(String theme) {
this.theme = theme;
}
public Date getSendTime() {
return sendTime;
}
public void setSendTime(Date sendTime) {
this.sendTime = sendTime;
}
}
package com.chenzhen.blog.util;
import com.chenzhen.blog.pojo.Mail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
/**
* @author ChenZhen
* @Description
* @create 2022/9/27 11:59
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@Component
public class MailUtil {
@Autowired
private JavaMailSender javaMailSender;//引入 JavaMailSender 邮件发送对象 来实现发送邮件的功能
@Autowired
private SpringTemplateEngine springTemplateEngine;//Spring 模板引擎
@Value("${spring.mail.username}") //从yaml配置文件中获取
private String from; //发送方邮箱地址
@Value("${spring.mail.thymeleaf-html}")//从yaml配置文件中获取
private String html;
/**
* 发送 thymeleaf 页面邮件
*/
public void sendThymeleafEmail(Mail mail) throws MessagingException {
MimeMessage msg = javaMailSender.createMimeMessage();//构建邮件
MimeMessageHelper helper = new MimeMessageHelper(msg, true);//构建邮件收发信息。
helper.setFrom(from);//发件人(默认固定为自己)
helper.setSentDate(mail.getSendTime());//发送日期
helper.setSubject(mail.getTheme());//发送主题
Context context = new Context();//将mail中的值设置进context交由模板引擎渲染
// WebContext context = new WebContext(request, response, request.getServletContext(), request.getLocale());
context.setVariable("name",mail.getName());
context.setVariable("theme", mail.getTheme());
context.setVariable("comment", mail.getComment());
context.setVariable("respondent", mail.getRespondent());
context.setVariable("reply", mail.getReply());
context.setVariable("address", mail.getAddress());
String process = springTemplateEngine.process(html, context);
helper.setText(process,true);//内容是否设置成html,true代表是
helper.setTo(mail.getAcceptMailAccount());//收件人
javaMailSender.send(msg);//发送
}
}
定义EmailService
接口类,接口方法sendMail
public interface EmailService {
/**
* 新增邮件回复功能,有回复消息会有邮件提醒
*/
void sendMail(User user, Message message) throws MessagingException;
}
实现接口方法sendMail
对用户进行判断:
package com.chenzhen.blog.service.impl;
import com.chenzhen.blog.mapper.MessageMapper;
import com.chenzhen.blog.pojo.Mail;
import com.chenzhen.blog.pojo.Message;
import com.chenzhen.blog.pojo.User;
import com.chenzhen.blog.service.EmailService;
import com.chenzhen.blog.util.MailUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
@Service
public class EmailServiceImpl implements EmailService {
@Autowired
private MailUtil mailUtil;
@Autowired
private MessageMapper messageMapper;
@Override
public void sendMail(User user, Message message) throws MessagingException {
if (user!=null){
//如果是管理员发的评论
if (message.getParentMessage().getId()==null || message.getParentMessage()==null){
//如果是根评论
//不需要发给自己邮件
return;
}else {
//如果不是根评论,则给[我回复的对象]发一封提醒邮件
Message parentMessage = messageMapper.selectById(message.getParentMessage().getId());//获取父评论
Mail mail = new Mail(null, parentMessage.getEmail(), parentMessage.getNickname(), parentMessage.getContent(),
message.getNickname(), message.getContent(),
"/message", "您在《ChenZhen的客栈-留言板》中的评论有了新的回复!");
mailUtil.sendThymeleafEmail(mail);
}
}else {
//如果不是管理员发的评论
if (message.getParentMessage().getId()==null || message.getParentMessage()==null){
//如果是根评论
//发给我自己,提醒有人在留言板留言了
Mail mail = new Mail(null, "[email protected]", "ChenZhen", null,
message.getNickname(), message.getContent(),
"/message","在《ChenZhen的客栈-留言板》中有了新的留言!");
mailUtil.sendThymeleafEmail(mail);
}else{
//如果不是根评论
//给回复者[回复的对象]发一份提醒邮件
Message parentMessage = messageMapper.selectById(message.getParentMessage().getId());//获取父评论
Mail mail = new Mail(null,parentMessage.getEmail(),parentMessage.getNickname(),
parentMessage.getContent(),message.getNickname(),message.getContent(),
"/message","您在《ChenZhen的客栈-留言板》中的评论有了新的回复!");
mailUtil.sendThymeleafEmail(mail);
}
}
}
}
以上准备做好之后下面是两种通知的方式。
接下来我们展示异步编程的方式实现邮件提醒功能:
我们将MailUtil
中发送邮件的方法sendThymeleafEmail()
加上@Async
注解,Springboot会将该方法标记为一个异步方法,这样在执行该方法的时候springboot会为我们开辟一个另外的线程来运行邮件的发送功能。这样不会造成线程的堵塞。
@Async("asyncThreadPoolTaskExecutor") //设置为一个异步方法
package com.chenzhen.blog;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author ChenZhen
* @Description 自定义线程池
* @create 2022/9/28 18:31
* @QQ 1583296383
* @WeXin(WeChat) ShockChen7
*/
@Configuration
public class AsyncPoolConfig {
@Bean(name = "asyncThreadPoolTaskExecutor")
public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(25);
executor.setKeepAliveSeconds(200);
executor.setThreadNamePrefix("asyncThread");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
只要在新增评论接口处添加代码,在评论信息保存到数据库后,调用Service层的方法,异步地给用户发送提醒邮件
这里默认你已经在云服务器上安装好了MQ,如果你还没安装可以参考我的另一篇文章,安装好Rabbitmq:
https://www.chenzhen.space/blog/28
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.83version>
dependency>
第一个是rabbitmq整合springboot的启动器,第二个是用来序列化对象的
spring:
rabbitmq:
host: xxx.xxx.xxx.xxx # RabbitMQ服务器主机名
port: 5672 # RabbitMQ服务器端口号
username: admin # 连接用户名
password: xxxxx # 连接密码
virtual-host: / # 默认虚拟主机
publisher-returns: true # 启用发布者返回确认
publisher-confirm-type: correlated # 发布者确认类型 correlated 意味着生产者将使用带有关联 ID 的回调来确认发布。
template:
mandatory: true # 强制消息必须被路由到一个队列
connection-timeout: 1000ms # 连接超时时间
listener:
simple:
acknowledge-mode: manual # 手动消息确认模式
prefetch: 10 # 预取消息数
concurrency: 1 # 并发消费者数量
max-concurrency: 10 # 最大并发消费者数量
rabbitmq:
email:
queue: email-queue #这个配置项是自定义配置项,配置队列的名称值而已 ,不是rabbitmq的官方配置,不要搞混了
package com.chenzhen.blog.config;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
@Value("${rabbitmq.email.queue}")
private String emailQueue;
// 声明emailQueue队列
@Bean
public Queue emailQueue() {
return new Queue(emailQueue);
}
}
EmailSender
:package com.chenzhen.blog.sender;
import com.alibaba.fastjson.JSON;
import com.chenzhen.blog.pojo.EmailMessage;
import com.chenzhen.blog.pojo.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
//邮件生产者
@Component
public class EmailSender {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private RabbitTemplate rabbitTemplate;
// 从配置文件读取邮件队列的名称
@Value("${rabbitmq.email.queue}")
private String emailQueue;
/**
* 发送邮件消息到队列
*
* @param user 收件人信息
* @param message 邮件消息内容
*/
public void send(User user, com.chenzhen.blog.pojo.Message message) {
// 1.创建 EmailMessage 对象,封装收件人和邮件消息
EmailMessage emailMessage = new EmailMessage(user,message);
// 2.将 EmailMessage 对象转换为字节数组
byte[] bytes = JSON.toJSONBytes(emailMessage);
// 3.创建消息对象,并设置消息体和持久化属性
Message rabbitMessage = MessageBuilder.withBody(bytes)
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
// 4.发送消息到邮件队列
rabbitTemplate.send(emailQueue,rabbitMessage);
}
// init 方法被标记为 @PostConstruct,这意味着它会在 目前该Bean 被创建并完成依赖注入后调用。
// 当 Spring 容器创建 MyBean 时,会自动调用 initialize 方法。
@PostConstruct
public void init() {
// 设置发送成功消息确认回调函数
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
// 发送成功的处理逻辑
logger.info("邮件消息投递成功!");
} else {
// 发送失败的处理逻辑
logger.error("邮件消息投递失败!");
logger.error("ConfirmCallback: 相关数据 :{}",correlationData);
logger.error("ConfirmCallback: 确认情况 :{}",ack);
logger.error("ConfirmCallback: 原因 :{}",cause);
}
}
});
// 设置消息退回回调函数
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
// 处理退回的消息
String returnedMessage = new String(message.getBody());
logger.error("消息退回到生产者!");
logger.error("退回的消息 : {}",returnedMessage);
logger.error("回复代码 : {}",replyCode);
logger.error("回复文本 : {}",replyText);
logger.error("交换机 : {}",exchange);
logger.error("路由键 : {}",routingKey);
// 在这里进行退回消息的处理逻辑
}
});
}
}
EmailConsumer
package com.chenzhen.blog.consumer;
import com.alibaba.fastjson.JSON;
import com.chenzhen.blog.pojo.EmailMessage;
import com.chenzhen.blog.pojo.User;
import com.chenzhen.blog.service.EmailService;
import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class EmailConsumer implements ChannelAwareMessageListener {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private EmailService emailService;
@Override
@RabbitListener(queues = "${rabbitmq.email.queue}")
public void onMessage(Message message, Channel channel) throws Exception {
try {
// 从消息中获取消息体(消息的字节数组)
byte[] messageBody = message.getBody();
// 将消息体转换为 EmailMessage 对象
EmailMessage emailMessage = JSON.parseObject(messageBody, EmailMessage.class);
// 从 EmailMessage 对象中提取用户和邮件消息内容
User user = emailMessage.getUser();
com.chenzhen.blog.pojo.Message userMessage = emailMessage.getMessage();
emailService.sendMail(user, userMessage);
// 手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 发生异常时选择拒绝消息
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
logger.error("邮件发送时发生异常!丢弃该消息!");
e.printStackTrace();
}
}
}
最终同样只要在新增评论接口处添加代码,在评论信息保存到数据库后,调用生产者的方法,将消息投递到队列即可。
作者:ChenZhen
本人不常看CSDN消息,有问题通过下面的方式联系:
- 邮箱:[email protected]
- vx: ChenZhen_7
我的个人博客地址:https://www.chenzhen.space/
版权:本文为博主的原创文章,本文版权归作者所有,转载请附上原文出处链接及本声明。
如果对你有帮助,请给一个小小的star⭐