场景描述
以官方的注册通知邮件为例,用户在官网成功注册后,平台会自动给用户发送一条成功注册通知邮件。为了实现这一功能,我们可以集成电子邮件服务,通过项目发送邮件。
添加邮件依赖
在 pom.xml
中添加邮件发送依赖:
js
<!-- 邮件发送-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
在 xiaomayi-common/xiaomayi-email
模块中已经引入此依赖,在实际使用时直接引入以下依赖即可:
js
<!-- 邮件依赖模块 -->
<dependency>
<groupId>com.xiaomayi</groupId>
<artifactId>xiaomayi-email</artifactId>
</dependency>
邮件 YML
文件配置解析文件:
js
package com.xiaomayi.email.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* <p>
* 电子邮件配置类
* </p>
*
* @author 小蚂蚁云团队
* @since 2024-05-30
*/
@Configuration
@ConfigurationProperties(prefix = "spring.mail")
public class EmailConfig {
/**
* 邮件主机
*/
private static String host;
/**
* 邮件账号
*/
private static String username;
/**
* 邮件密码
*/
private static String password;
/**
* 邮件端口
*/
private static Integer port;
public static String getHost() {
return host;
}
public void setHost(String host) {
EmailConfig.host = host;
}
public static String getUsername() {
return username;
}
public void setUsername(String username) {
EmailConfig.username = username;
}
public static String getPassword() {
return password;
}
public void setPassword(String password) {
EmailConfig.password = password;
}
public static Integer getPort() {
return port;
}
public void setPort(Integer port) {
EmailConfig.port = port;
}
}
配置邮件服务
在 application-email.yml
中配置邮件服务的参数:
yml
spring:
#邮件配置
mail:
# 邮箱服务器地址
host: smtp.163.com
# 邮箱授权码,开启邮箱POP3/SMTP服务,获取客户端授权码(注意并不是邮箱密码,而是授权码)
password: 您的授权码
# 邮箱的用户名
username: 您的邮箱
# 使用SMTPS协议465端口,本地可以使用25
port: 25
# ssl 配置
properties:
# 开启邮件debug
mail.debug: true
# STARTTLS[1] 是对纯文本通信协议的扩展。它提供一种方式将纯文本连接升级为加密连接(TLS或SSL),而不是另外使用一个端口作加密通信。
mail.smtp.starttls.enable: true
mail.smtp.starttls.required: true
encoding: UTF-8
# 开启SSL
mail.smtp.ssl.enable: true
# 设置是否需要认证,如果为true,那么用户名和密码就必须的。如果设置false,可以不设置用户名和密码,当然也得看你的对接的平台是否支持无密码进行访问的。
mail.smtp.auth: true
mail.smtp.socketFactory.port: 465
mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback: false
发送邮件工具
在 xiaomayi-common/xiaomayi-email
邮件模块中,创建一个工具类 EmailUtils
,用于发送邮件:
js
package com.xiaomayi.email.utils;
import com.xiaomayi.core.utils.StringUtils;
import com.xiaomayi.email.config.EmailConfig;
import com.xiaomayi.email.dto.EmailSendDTO;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.StringSubstitutor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.io.*;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* <p>
* 电子邮件工具类
* </p>
*
* @author 小蚂蚁云团队
* @since 2024-06-16
*/
@Slf4j
@Component
@AllArgsConstructor
public class EmailUtils {
private final JavaMailSenderImpl mailSender;
private final TemplateEngine templateEngine;
/**
* 发送邮件
*
* @param emailSendDTO 参数
*/
public boolean sendEMail(EmailSendDTO emailSendDTO) {
// 邮件主题
String subject = emailSendDTO.getTitle();
// 邮件内容
String content = emailSendDTO.getContent();
// 接收人邮箱
String to = emailSendDTO.getEmail();
log.info("发送Template邮件开始:{},{}", to, subject);
if (StringUtils.isNotEmpty(to)) {
log.info("=================== 发送邮件开始 ================");
// 模板参数
Map<String, Object> params = emailSendDTO.getParams();
if (StringUtils.isNotEmpty(params)) {
// 模板文件
String tplPath = emailSendDTO.getTplPath();
// 模板邮件
if (StringUtils.isNotEmpty(tplPath)) {
// 利用 Thymeleaf 模板构建 html 文本
Context context = new Context();
for (Map.Entry<String, Object> entry : params.entrySet()) {
// 给模板参数变量赋值
context.setVariable(entry.getKey(), entry.getValue());
}
// 执行模板引擎,执行模板引擎需要传入模板名、上下文对象
// Thymeleaf的默认配置期望所有HTML文件都放在 **resources/templates ** 目录下,以.html扩展名结尾。
content = templateEngine.process(tplPath, context);
}
// 普通文本邮件
else {
// 短信参数格式化
StringSubstitutor stringSubstitutor = new StringSubstitutor(params);
// 短信内容参数替换,此处基于依赖commons-text
content = stringSubstitutor.replace(content);
}
// 反向赋值便于邮件日志存储记录
emailSendDTO.setContent(content);
}
// 使用MimeMessage,MIME协议
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper;
try {
messageHelper = new MimeMessageHelper(message, true, "UTF-8");
messageHelper.setFrom(EmailConfig.getUsername());
messageHelper.setTo(to);
messageHelper.setSubject(subject);
// true代表支持html,false,显示原始html代码,无效果
messageHelper.setText(content, true);
// 抄送人
if (StringUtils.isNotEmpty(emailSendDTO.getCcEmail())) {
messageHelper.setCc(emailSendDTO.getCcEmail());
}
// 附件资源处理
List<String> attachment = emailSendDTO.getAttachment();
if (StringUtils.isNotEmpty(attachment)) {
// 遍历附件资源
for (String filePath : attachment) {
// 创建附件资源
FileSystemResource file = new FileSystemResource(filePath);
// 文件不存在,直接跳过
if (!file.exists()) {
continue;
}
// 获取附件文件名
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
// 加入附件
messageHelper.addAttachment(fileName, file);
}
}
mailSender.send(message);
log.info("邮件发送成功");
return true;
} catch (MessagingException e) {
log.error("邮件发送失败:{}", e.getMessage());
}
}
return false;
}
/**
* 发送纯文字邮件
*
* @param to 收件人
* @param subject 邮件主题
* @param content 邮件内容
*/
public void sendSimpleMail(String to, String subject, String content) {
// 开启线程异步发送邮件,防止发送请求时间过长造成阻塞
new Thread(new Runnable() {
@Override
public void run() {
// 创建SimpleMailMessage对象
SimpleMailMessage message = new SimpleMailMessage();
// 邮件发送人
message.setFrom(EmailConfig.getUsername());
// 邮件接收人
message.setTo(to);
// 邮件主题
message.setSubject(subject);
// 邮件内容
message.setText(content);
// 发送邮件
try {
mailSender.send(message);
log.info("邮件接收人" + to + "主题" + subject + "内容" + content + "邮件发送成功");
} catch (Exception e) {
log.error("邮件接收人" + to + "主题" + subject + "内容" + content + "邮件发送出现异常");
log.error("异常信息为" + e.getMessage());
}
}
}).start();
}
/**
* 发送简单邮件
*
* @param to 收件人
* @param subject 邮件主题
* @param content 邮件内容
*/
public void sendMail(String to, String subject, String content) {
MimeMessage message;
try {
// 创建邮件消息
message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
// 发送者
helper.setFrom(EmailConfig.getUsername());
// 接收者
helper.setTo(to);
// 邮件主题
helper.setSubject(subject);
// 设置邮件内容
helper.setText(content, true);
// 发送邮件
mailSender.send(message);
log.info("邮件发送成功");
} catch (MessagingException e) {
log.error("邮件发送失败:{}", e.getMessage());
}
}
/**
* 发送邮件
*
* @param username 用户名
* @param code 验证码
* @param tplPath 邮件模板路径
* @return 返回结果
* @throws IOException 异常处理
*/
public String buildContent(String username, String code, String tplPath) {
try {
// 加载邮件html模板
Resource resource = new ClassPathResource(String.format("/templates/%s", tplPath));
// 创建输入流
InputStream inputStream = resource.getInputStream();
BufferedReader fileReader = new BufferedReader(new InputStreamReader(inputStream));
// 实例化构建器
StringBuilder buffer = new StringBuilder();
// 逐行读取
String line = "";
try {
while ((line = fileReader.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
log.error("读取文件失败:{}", e.getMessage());
} finally {
inputStream.close();
fileReader.close();
}
// 返回结果
return MessageFormat.format(buffer.toString(), username, code);
} catch (Exception e) {
log.error("发送邮件异常:{}", e.getMessage());
}
return null;
}
/**
* 发送带附件的邮件
*
* @param to 收件人
* @param subject 邮件主题
* @param content 邮件内容
* @param filePath 附件路径
*/
public void sendAttachmentMail(String to, String subject, String content, String filePath) {
log.info("发送带附件的邮件:{},{},{},{}", to, subject, content, filePath);
// 开启线程异步发送邮件,防止发送请求时间过长造成阻塞
new Thread(new Runnable() {
@Override
public void run() {
if (StringUtils.isNotEmpty(to)) {
log.info("=================== 发送邮件开始 ================");
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper;
try {
messageHelper = new MimeMessageHelper(message, true, "UTF-8");
messageHelper.setFrom(EmailConfig.getUsername());
messageHelper.setTo(to);
messageHelper.setSubject(subject);
messageHelper.setText(content, true);
// 创建附件资源
FileSystemResource file = new FileSystemResource(filePath);
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
messageHelper.addAttachment(fileName, file);
// 发送邮件
mailSender.send(message);
log.info("邮件发送成功!");
} catch (MessagingException e) {
log.error("邮件发送失败:{}" + e.getMessage());
}
log.info("=================== 发送邮件结束 ================");
}
}
}).start();
}
/**
* 发送图片邮件
*
* @param to 收件人
* @param subject 邮件主题
* @param content 邮件内容
* @param rscPath 图片路径
* @param rscId 图片ID
*/
public void sendInlineMail(String to, String subject, String content, String rscPath, String rscId) {
// 开启线程异步发送 防止发送请求时间过长
new Thread(new Runnable() {
@Override
public void run() {
log.info("发送带图片邮件开始:{},{},{},{},{}", to, subject, content, rscPath, rscId);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, true);
helper.setFrom(EmailConfig.getUsername());
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
helper.setSentDate(new Date());
FileSystemResource res = new FileSystemResource(new File(rscPath));
// 重复使用添加多个图片
helper.addInline(rscId, res);
mailSender.send(message);
log.info("发送带图片邮件成功");
} catch (MessagingException e) {
log.error("发送带图片邮件失败:", e);
}
}
}).start();
}
/**
* 发送HTML模板邮件
*
* @param to 收件人
* @param subject 邮件主题
* @param tplPath 模板路径
* @param map 模板数据
*/
public void sendTemplateMail(String to, String subject, String tplPath, Map<String, Object> map) {
// 开启线程异步发送 防止发送请求时间过长
new Thread(new Runnable() {
@Override
public void run() {
log.info("发送Template邮件开始:{},{},{}", to, subject, tplPath);
// 利用 Thymeleaf 模板构建 html 文本
Context context = new Context();
for (Map.Entry<String, Object> entry : map.entrySet()) {
// 给模板参数变量赋值
context.setVariable(entry.getKey(), entry.getValue());
}
// 执行模板引擎,执行模板引擎需要传入模板名、上下文对象
// Thymeleaf的默认配置期望所有HTML文件都放在 **resources/templates ** 目录下,以.html扩展名结尾。
String content = templateEngine.process(tplPath, context);
// 使用MimeMessage,MIME协议
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, true);
helper.setFrom(EmailConfig.getUsername());
helper.setTo(to);
helper.setSubject(subject);
// true代表支持html
helper.setText(content, true);
mailSender.send(message);
log.info("发送模板邮件成功");
} catch (MessagingException e) {
log.error("发送模板邮件失败:", e);
}
}
}).start();
}
}
测试邮件发送
编写一个简单的测试类,验证邮件发送功能:
js
package com.xiaomayi.admin.controller.demo;
import com.alibaba.fastjson2.JSON;
import com.xiaomayi.core.utils.R;
import com.xiaomayi.email.annotation.EmailLog;
import com.xiaomayi.email.dto.EmailSendDTO;
import com.xiaomayi.email.enums.EmailType;
import com.xiaomayi.ratelimiter.annotation.RateLimiter;
import com.xiaomayi.system.service.EmailTemplateService;
import com.xiaomayi.system.service.SmsTemplateService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* 案例测试 前端控制器
* </p>
*
* @author 小蚂蚁云团队
* @since 2024-05-26
*/
@Slf4j
@RestController
@RequestMapping("/example")
@AllArgsConstructor
public class ExampleController {
private final EmailTemplateService emailTemplateService;
/**
* 邮件发送测试
*
* @return 返回结果
*/
@EmailLog(title = "注册邮件", type = EmailType.REGISTER)
@PostMapping("/sendEmail")
public R sendSms() {
try {
String receiveEmail = "123456789@qq.com";
// // 自定纯文本邮件
// EmailSendDTO emailSendDTO = new EmailSendDTO();
// emailSendDTO.setEmail(receiveEmail);
// emailSendDTO.setTitle("邮件发送测试");
// emailSendDTO.setContent("纯文本邮件测试内容");
// emailService.sendEmail(emailSendDTO);
// // 纯文本模板文件
// EmailSendDTO emailSendDTO2 = new EmailSendDTO();
// emailSendDTO2.setCode("EMAIL_00001");
// emailSendDTO2.setEmail(receiveEmail);
// emailSendDTO2.setTitle("邮件发送测试");
// // 模板参数
// Map<String, Object> params = new HashMap<>();
// params.put("code", "123456");
// emailSendDTO2.setParams(params);
// emailService.sendEmail(emailSendDTO2);
// 模板文件发送
EmailSendDTO emailSendDTO3 = new EmailSendDTO();
emailSendDTO3.setCode("EMAIL_00002");
emailSendDTO3.setEmail(receiveEmail);
// emailSendDTO3.setTitle("邮件发送测试");
// 模板参数
Map<String, Object> params2 = new HashMap<>();
params2.put("username", "admin");
params2.put("code", "123456");
emailSendDTO3.setParams(params2);
// 附件处理
List<String> fileList = new ArrayList<>();
fileList.add("D:\\test.html");
emailSendDTO3.setAttachment(fileList);
emailTemplateService.sendEmail(emailSendDTO3);
System.out.println("11");
// // 开始发送邮件
// emailUtils.sendSimpleMail(receiveEmail, "验证码", "123456");
// 开始发送邮件
// String content = emailUtils.buildContent("VIP用户", "123456", "email.html");
// emailUtils.sendMail(receiveEmail, "验证码", content);
// // 发送带附件邮件
// File file2 = ResourceUtils.getFile("classpath:email.zip");
// emailUtils.sendAttachmentMail(receiveEmail, "验证码", content, file2.getPath());
// File file = ResourceUtils.getFile("classpath:demo.png");
// String rscId = "001";
// String rscPath = file.getPath();
// String content2 = "<html><body style=\"text-align:center;\"><h3><font color=\"red\">" + "大家好,这是springboot发送的HTML邮件,有图片哦" + "</font></h3>"
// + "<img src=\'cid:" + rscId + "\'></body></html>";
// emailUtils.sendInlineMail("123456789@qq.com", "发送邮件测试", content2, rscPath, rscId);
// // 发送模板邮件
// Map<String, Object> map = new HashMap<>();
// map.put("username", "收件人姓名");
// map.put("code", "12345678");
// emailUtils.sendTemplateMail(receiveEmail, "这是一个模板文件", "test.html", map);
} catch (Exception e) {
log.error("邮件发送异常:{}" + e.getMessage());
}
return R.ok();
}
}
总结
通过以上步骤,我们成功在项目中集成了邮件发送功能,实现了用户注册后的欢迎邮件和课程购买确认邮件的发送。集成邮件发送功能的好处包括:
提升用户体验:通过邮件通知用户重要信息,提升用户体验。
灵活配置:支持发送纯文本和 HTML 格式的邮件,满足不同需求。
易于集成:提供了开箱即用的邮件发送支持,减少开发工作量。
成本低廉:使用现有的邮件服务器(如 Gmail、QQ 邮箱等),成本低廉。