Springboot整合异步邮件服务

  • 在很多场景都需要使用到用户和系统的其他交互如:验证码。。。此时可以选择短信或者邮件的形式,但是短信业务需要收费,所以我们就近选择邮件服务

1.导入依赖

1
2
3
4
5
<!--spring boot mail 集成jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

2.配置邮件服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 邮件设置
spring:
mail:
host: smtp.qq.com
username: 1447250889@qq.com # 邮箱账号
password: dsadadasdada # 授权码(在邮箱开启pop3和smtp后会给你)
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
# 邮箱信息
mail:
fromMail:
addr: 1447250889@qq.com

3.邮箱工具类

  • 提供各种邮件服务:纯文本邮件,html邮件…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package kid1999.upload.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;

/**
* @author kid1999
* @title: MailUtil
* @date 2019/12/22 15:32
*/

@Service
@Slf4j
public class MailUtil {

@Autowired
private JavaMailSender mailSender;

// 注入常量
@Value("${mail.fromMail.addr}")
private String from;

/**
* 发送文本邮件
* @param toAddr
* @param title
* @param content
*/

@Async("taskExecutor")
public void sendTextMail(String toAddr, String title, String content) {
// 纯文本邮件对象
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(toAddr);
message.setSubject(title);
message.setText(content);

try {
mailSender.send(message);
log.info("Text邮件已经发送。");
} catch (Exception e) {
log.error("发送Text邮件时发生异常!", e);
}

}

/**
* 发送html邮件
* @param toAddr
* @param title
* @param content
*/
@Async("taskExecutor")
public void sendHtmlMail(String toAddr, String title, String content) {
// html 邮件对象
MimeMessage message = mailSender.createMimeMessage();

try {
//true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(toAddr);
helper.setSubject(title);
helper.setText(content, true);

mailSender.send(message);
log.info("html邮件发送成功");
} catch (MessagingException e) {
log.error("发送html邮件时发生异常!", e);
}
}


/**
* 发送带附件的邮件
* @param toAddr
* @param title
* @param content
* @param filePath
*/
@Async("taskExecutor")
public void sendAttachmentsMail(String toAddr, String title, String content, String filePath){
MimeMessage message = mailSender.createMimeMessage();

try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(toAddr);
helper.setSubject(title);
helper.setText(content, true);

FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
//helper.addAttachment("test"+fileName, file);

mailSender.send(message);
log.info("带附件的邮件已经发送。");
} catch (MessagingException e) {
log.error("发送带附件的邮件时发生异常!", e);
}
}


/**
* 发送正文中有静态资源(图片)的邮件
* @param toAddr
* @param title
* @param content
* @param rscPath
* @param rscId
*/
@Async("taskExecutor")
public void sendInlineResourceMail(String toAddr, String title, String content, String rscPath, String rscId){
MimeMessage message = mailSender.createMimeMessage();

try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(toAddr);
helper.setSubject(title);
helper.setText(content, true);

FileSystemResource res = new FileSystemResource(new File(rscPath));
helper.addInline(rscId, res);

mailSender.send(message);
log.info("嵌入静态资源的邮件已经发送。");
} catch (MessagingException e) {
log.error("发送嵌入静态资源的邮件时发生异常!", e);
}
}


/**
* 发送邮箱验证码
*/

@Async("taskExecutor")
public void sendMailCode(String toAddr, String title, String mailCode) {
log.info("发送邮箱验证码");
// html 邮件对象
MimeMessage message = mailSender.createMimeMessage();

String content="<html>\n" +
"<body>\n" +
" <h3>你的验证码是:"+ mailCode +"</h3>\n" +
"</body>\n" +
"</html>";

try {
//true表示需要创建一个multipart message
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(toAddr);
helper.setSubject(title);
helper.setText(content, true);
mailSender.send(message);
log.info("html邮件发送成功");
} catch (MessagingException e) {
log.error("发送html邮件时发生异常!", e);
}
}
}

4.发送邮件:

  • 本处以申请修改密码时,需要核对账号-邮箱,然后发送验证码到邮箱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 填写表单 (核对账号,邮箱)
@PostMapping("/checktable")
@ResponseBody
Result checkTable(HttpServletRequest request,
@RequestParam(value = "name") String name,
@RequestParam(value = "email") String email,
@RequestParam(value = "code") String code){
log.info("核对账户,邮箱");
if(checkVerificationCode(code,request)){
if(name.equals("") || email.equals("")){
return Result.fail(400,"用户名或邮箱不能为空");
}else{
User user = userService.findUserByName(name);
if( user == null){
return Result.fail(400,"用户名不存在");
}
if(!user.getEmail().equals(email)){
return Result.fail(400,"用户,邮箱不匹配");
}
String mailCode = RandomUtil.randomString(6);
mailUtil.sendMailCode(email,"你的验证码",mailCode);
request.getSession().setAttribute("mailCode",mailCode);
return Result.success("请填写邮箱验证码");
}
}else{
return Result.fail(400,"验证码不对");
}
}

异步事件优化

  • 由于同步事件,发送邮件的时间会很长,如果使用同步,前端会得不到返回一直在等待,影响客户体验
  • 使用异步即可修复这个问题

1.在springboot中配置异步线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package kid1999.upload.config;

import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
* @author kid1999
* @title: TaskPoolConfig
* @date 2019/12/22 16:09
*/

public class TaskPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor () {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数10:线程池创建时候初始化的线程数
executor.setCorePoolSize(10);
// 最大线程数20:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
executor.setMaxPoolSize(15);
// 缓冲队列200:用来缓冲执行任务的队列
executor.setQueueCapacity(200);
// 允许线程的空闲时间60秒:当超过了核心线程数之外的线程在空闲时间到达之后会被销毁
executor.setKeepAliveSeconds(60);
// 线程池名的前缀:设置好了之后可以方便定位处理任务所在的线程池
executor.setThreadNamePrefix("taskExecutor-");
/*
线程池对拒绝任务的处理策略:这里采用了CallerRunsPolicy策略,
当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;
如果执行程序已关闭,则会丢弃该任务
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(600);
return executor;
}
}

2.在程序入口类添加 @EnableAsync注解

3.在需要异步的方法上注入 @Async(“taskExecutor”) 异步事件

  • 此处的注解使用的 “taskExecutor” 就是我们刚才定义的 异步线程池