SpringBoot 通过 hutool 方式发送邮件
本文的码云地址:点击传送
本文项目效果:
- 发送普通邮件
- 发送随机验证码邮件并把验证码缓存到redis
- 发送带格式模板的邮件
本项目代码都在码云里,传送门在上面,我这里把几个重点的地方详讲一下,所贴代码会删减一部分不重要的,以便阅读
1. 项目结构
2. 项目配置
application.yml
【注意】如果是腾讯个人qq邮箱,这里的密码不是邮箱登录密码,而是授权码,否则将会出现密码不对的情况。
授权码:网页QQ邮箱 > 设置 > 账户 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 > 开启STMP并获得授权码
server:
port: 8080
#邮箱验证码有效时间/秒
code:
expiration: 300
# 电子邮件配置
# 如果是腾讯个人qq邮箱,这里的密码不是邮箱登录密码,而是授权码:
# 授权码:网页QQ邮箱 > 设置 > 账户 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 > 开启STMP并获得授权码
email:
host: smtp.qiye.aliyun.com # SMTP地址
port: 465
from: 昵称<xxx@xxx.com> # 发件人昵称
pass: xxxxxx
user: xxx@xxx.com # 邮箱地址
# 验证码有效时长(秒)
verification:
expiration-time: 600
spring:
redis:
database: 2
host: xxx.xxx.xxx.xxx
port: xxxxx
password: xxxxxx
timeout: 10000ms
#是否开启 swagger-ui
swagger:
enabled: true
3. 邮件配置类 EmailConfig.java
这个配置类从上面的配置文件中读取数据填入到属性中。
import io.swagger.annotations.ApiModelProperty;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.persistence.Entity;
@Component
public class EmailConfig {
/**
* 发送方 邮件服务器 SMTP 地址
*/
@Value("${email.host}")
@ApiModelProperty(value = "发送方 邮件服务器SMTP地址")
private String host;
/**
* 发送方 邮件服务器 SMTP 端口
*/
@Value("${email.port}")
@ApiModelProperty(value = "发送方 邮件服务器 SMTP 端口")
private String port;
/**
* 发送方 昵称
*/
@Value("${email.from}")
@ApiModelProperty(value = "发送方 昵称")
private String from;
/**
* 发送方 密码
*/
@Value("${email.pass}")
@ApiModelProperty(value = "发送方 密码")
private String pass;
/**
* 发送方 邮箱地址
*/
@Value("${email.user}")
@ApiModelProperty(value = "发送方 邮箱地址")
private String user;
//--TODO 此处省略 Get、Set 方法
}
【注意1】这个类需要加 @Component
注解才能被 Spring 扫描到
【注意2】这个类直接 new 时,属性中的值是空的,是因为被 new 是我们主动创建而不是被 Spring 创建出来的,所以属性值并没有被注入。所以我们需要在使用时使用 @Autowired
注解,被 Spring 托管。
本项目中,采用了一个巧妙的方式,即:该配置类可外部设置并以参数形式传入,如果该参数为null,那么这个配置就有 Spring 负责注入配置文件的数据,等会儿在下方的 4【EmailServiceImpl.java】就能看到。
4. 邮件逻辑层实现层 EmailServiceImpl.java
- 当邮件发送模块没有问题并作为工具被使用时,建议使用异步执行,这样有效的降低请求的响应时间。
- 发件邮箱配置可外部参数传入,是为了方便上层使用其他邮箱作为发送方预留的
@Service
public class EmailServiceImpl implements EmailService {
/**
* 验证码有效时长(秒)
*/
@Value("${verification.expiration-time}")
private Long expirationTime;
@Autowired
private RedisUtils redisUtils;
// 这里就是交由 Spring 托管的配置类,为默认配置
@Autowired
private EmailConfig defaultConfig;
/**
* 发送邮件
* @param emailVo 邮件发送的内容
* @param emailConfig 邮件配置,null:使用默认配置
* @throws Exception /
*/
@Override
@Async // 发送邮件为异步执行
@Transactional(rollbackFor = Exception.class)
public void send(EmailVo emailVo, EmailConfig emailConfig) {
// 这里可以选择使用外部传入的配置,如果外部传入为null,那么便使用默认配置。
if(emailConfig == null) {
emailConfig = defaultConfig;
} else if (StringUtils.isBlank(emailConfig.getHost()) || StringUtils.isBlank(emailConfig.getPort())
|| StringUtils.isBlank(emailConfig.getFrom()) || StringUtils.isBlank(emailConfig.getPass())
|| StringUtils.isBlank(emailConfig.getUser())) {
throw new BadConfigurationException("请先配置邮箱数据,再操作!");
}
MailAccount account = new MailAccount();
// 发件人昵称
account.setFrom(emailConfig.getFrom());
// 发件人邮箱地址
account.setUser(emailConfig.getUser());
account.setHost(emailConfig.getHost());
account.setPort(Integer.parseInt(emailConfig.getPort()));
account.setAuth(true);
account.setPass(emailConfig.getPass());
// ssl 方式发送
account.setSslEnable(true);
// 使用STARTTLS 安全连接
account.setStarttlsEnable(true);
// 发送
try {
int size = emailVo.getTos().size();
Mail.create(account)
.setTos(emailVo.getTos().toArray(new String[size]))
.setTitle(emailVo.getSubject())
.setContent(emailVo.getContent())
.setHtml(true)
// 关闭session
.setUseGlobalSession(false)
.send();
} catch (Exception e) {
throw new BadRequestException(e.getMessage());
}
}
/**
* 获取验证码的邮件参数类
* @param toEmail 目标邮箱
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public EmailVo getVerificationCodeEmailVo(String toEmail) {
String content;
String redisKey = EmailCodeEnum.VERIFICATION_CODE.getKey() + toEmail;
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email/verificationCode.ftlh");
Object oldCode = redisUtils.get(redisKey);
// 如果Redis不存在有效验证码,就创建一个新的
// 存在就使用原来的验证码,并将有效期更新
if (oldCode == null) {
String code = RandomUtil.randomNumbers(6); // 6位验证码,修改这里可以更改位数
// 存入缓存
boolean saveResult = redisUtils.set(redisKey, code, expirationTime);
if(!saveResult) {
throw new RedisException("Redis服务异常! 验证码未保存成功!");
}
content = template.render(Dict.create().set("code", code));
} else {
// 重置Redis中这个key的有效期
redisUtils.expire(redisKey, expirationTime);
content = template.render(Dict.create().set("code", oldCode));
}
return new EmailVo(Collections.singletonList(toEmail), "Hutool-" + EmailCodeEnum.VERIFICATION_CODE.getDescription(), content);
}
/**
* 获取简单通知的邮件参数类
* @param toEmail 目标邮箱
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public EmailVo getSimpleNotificationEmailVo(String toEmail, String msg) {
String redisKey = EmailCodeEnum.VERIFICATION_CODE.getKey() + toEmail;
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email/simple_notification.ftlh");
String content = template.render(Dict.create().set("msg", msg));
return new EmailVo(Collections.singletonList(toEmail), "Hutool-" + EmailCodeEnum.NOTIFICATION.getDescription(), content);
}
}
5. 邮件控制层 EmailController.java
很显然,控制器没什么好讲的~
如果代码写的很完善了,那么代码中的很多校验都可以删除或者放到前端,比如:判断目标邮箱、邮件标题等不为空等。
@RestController
@RequestMapping("api/email")
@Api(tags = "工具:邮件管理")
public class EmailController {
@Autowired
private EmailService emailService;
/**
* 发送邮件
* @param toEmail 目标邮箱
* @param subject 邮件标题
* @param content 邮件内容
* @return
*/
@PostMapping("/sendMail")
public String SendEmail(@RequestParam String toEmail, @RequestParam String subject, @RequestParam String content){
if(StringUtils.isBlank(toEmail)) {
throw new BadRequestException("目标邮箱不能为空!");
}
if(StringUtils.isBlank(subject)) {
throw new BadRequestException("邮件标题不能为空!");
}
if(StringUtils.isBlank(content)) {
throw new BadRequestException("邮件内容不能为空!");
}
List<String> toEmails = new ArrayList<>();
toEmails.add(toEmail);
EmailVo emailVo = new EmailVo(toEmails, subject, content);
emailService.send(emailVo, null);
return "邮件已发送!";
}
/**
* 发送验证码
* @param toEmail 目标邮箱
* @return
*/
@PostMapping("/sendVerificationCode")
public String SendVerificationCode(@RequestParam String toEmail) {
EmailVo emailVo = emailService.getVerificationCodeEmailVo(toEmail);
emailService.send(emailVo, null);
return "邮件已发送!";
}
/**
* 发送验证码
* @param toEmail 目标邮箱
* @param msg 通知的信息
* @return
*/
@PostMapping("/sendSimpleNotification")
public String SendSimpleNotification(@RequestParam String toEmail, @RequestParam String msg) {
if(StringUtils.isBlank(msg)) {
throw new BadRequestException("通知信息为空!");
}
EmailVo emailVo = emailService.getSimpleNotificationEmailVo(toEmail, msg);
emailService.send(emailVo, null);
return "邮件已发送!";
}
}