SpringBoot:SpringBoot集成E-mail邮件发送功能
前言
做项目时有个需求:用公司邮箱给客户发送邮件通知,然后上网冲浪找到一些不错的文章,通过优化并实现功能后,写这篇文章记录一下,也提供给大家做参考。
前期准备
在编写代码前,我们需要获取到一些信息用于后续邮件发送功能,需要获取的信息为:协议服务器地址、邮件发送协议、客户端授权码。
名词说明
什么是邮件协议?
邮件协议主要有三种:POP3、IMAP、SMTP。简单来说,POP3和IMAP是用来从服务器上下载邮件的。SMTP适用于发送或中转信件时找到下一个目的地,所以我们发送邮件应该使用SMTP协议。
什么是协议服务器?
邮箱协议服务器是托管用户电子邮件的服务器,负责接收、存储、发送和转发电子邮件。不同厂家的邮箱,对应的邮箱服务器也不一样。
什么是邮箱客户端授权码?
邮箱客户端授权码(有的叫:客户端专用密码)是为了避免邮箱密码被盗后,盗号者通过客户端登录邮箱而设计的安防功能。这里我们获取邮箱客户端授权码就可以通过它发送邮件。
QQ邮箱获取信息
协议与服务器配置:
IMAP 协议
接收邮件服务器:imap.qq.com ,使用 SSL,端口号 993
POP3 协议
接收邮件服务器:pop.qq.com ,使用 SSL,端口号 110
SMTP 协议
发送邮件服务器:smtp.qq.com ,使用 SSL,端口号 465
客户端授权码获取:
注意:授权码获取后只显示一次,所以显示后就要复制,不然就再申请一次。
网易邮箱获取信息
协议与服务器配置:
IMAP 协议
接收邮件服务器:imap.163.com ,使用 SSL,端口号 993
POP3 协议
接收邮件服务器:pop.163.com ,使用 SSL,端口号 995
SMTP 协议
发送邮件服务器:smtp.163.com ,使用 SSL,端口号 465
客户端授权码获取:
注意:授权码获取后只显示一次,所以显示后就要复制,不然就再申请一次。
企业微信邮箱获取信息
协议与服务器配置:
IMAP 协议
接收邮件服务器:imap.exmail.qq.com ,使用 SSL,端口号 993
POP3 协议
接收邮件服务器:pop.exmail.qq.com ,使用 SSL,端口号 995
SMTP 协议
发送邮件服务器:smtp.exmail.qq.com ,使用 SSL,端口号 465
Exchange 协议
服务器:ex.exmail.qq.com
客户端授权码获取:
注意:授权码获取后只显示一次,所以显示后就要复制,不然就再申请一次。
引入依赖
<!-- 邮箱依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- freemarker页面渲染依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
配置文件
spring:
# 邮件配置
mail:
default-encoding: utf-8
# 协议服务器地址
host: smtp.exmail.qq.com
# SSL端口
#prot: 465
# 发送协议, 如果配置SSL端口,这里的发送协议改为 smtps
protocol: smtp
# 发送方的邮箱地址
username: lsjgas@dsld.com
# 授权码 (非邮箱密码)
password: 9buX4WBhfcwjksdkxaf
# HTML模板配置
freemarker:
cache: false # 缓存配置 开发阶段应该配置为false 因为经常会改
suffix: .html # 模版后缀名 默认为ftl
charset: UTF-8 # 文件编码
template-loader-path: classpath:/templates/ # 存放模板的文件夹,以resource文件夹为相对路径
编写工具类
项目结构:
EMailProperties:邮件配置类
EMailUtils:邮件工具类
email.html:HTML模板文件
邮件配置类
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "spring.mail")
public class EMailProperties {
// 字符集编码
private String defaultEncoding;
// 协议服务器地址
private String host;
// (发送方)邮箱账号
private String username;
// 授权码
private String password;
public String getDefaultEncoding() {
return defaultEncoding;
}
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
邮件工具类
import com.higentec.common.utils.file.FileUtils;
import com.higentec.email.config.EMailProperties;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.io.IOException;
import java.util.Map;
@Component
@RequiredArgsConstructor
public class EMailUtils {
// 邮箱配置类
private final EMailProperties eMailProperties;
// 邮箱发送类
@Resource
private JavaMailSender javaMailSender;
// 页面模板渲染配置类
private final FreeMarkerConfigurer freeMarkerConfigurer;
/**
* 发送文本邮件
*
* @param title 标题
* @param text 文本
* @param targetMails 目标邮箱
*/
public void sendText(String title, String text, String... targetMails) {
sendText(title, text, null, targetMails);
}
/**
* 发送文本邮件
*
* @param title 标题
* @param text 文本
* @param Cc 抄送人
* @param targetMails 目标邮箱
*/
@SneakyThrows(Exception.class)
public void sendText(String title, String text, String Cc, String... targetMails) {
SimpleMailMessage message = new SimpleMailMessage();
// 发件人邮箱地址
message.setFrom(eMailProperties.getUsername());
// 收件人邮箱地址数组(可实现批量发送)
message.setTo(targetMails);
// 邮箱标题
message.setSubject(title);
// 邮箱内容
message.setText(text);
if (StringUtils.isNotEmpty(Cc)) {
// 抄送人邮箱地址
message.setCc(Cc);
}
// 发送邮件
javaMailSender.send(message);
}
/**
* 发送HTML超文本邮件
*
* @param title 标题
* @param templateName 模板名称(带后缀)
* @param model 使用Map作为数据模型,定义属性和值
* @param targetMails 目标邮箱
*/
public void sendHtmlTemplate(String title, String templateName, Map<String, Object> model, String... targetMails) {
String html = templateHtml(templateName, model);
sendHtmlText(title, html, null, targetMails);
}
/**
* 发送HTML超文本邮件
*
* @param title 标题
* @param html HTML超文本
* @param targetMails 目标邮箱
*/
public void sendHtmlText(String title, String html, String... targetMails) {
sendHtmlText(title, html, null, targetMails);
}
/**
* 发送HTML超文本邮件
*
* @param title 标题
* @param html HTML超文本
* @param Cc 抄送人
* @param targetMails 目标邮箱
*/
@SneakyThrows(MessagingException.class)
public void sendHtmlText(String title, String html, String Cc, String... targetMails) {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper mimeHelper = new MimeMessageHelper(message, true);
// 发件人邮箱地址
mimeHelper.setFrom(eMailProperties.getUsername());
// 收件人邮箱地址数组(可实现批量发送)
mimeHelper.setTo(targetMails);
// 邮箱标题
mimeHelper.setSubject(title);
// 邮箱内容, true代表是html内容
mimeHelper.setText(html, true);
if (StringUtils.isNotEmpty(Cc)) {
// 抄送人邮箱地址
mimeHelper.setCc(Cc);
}
// 发送邮件
javaMailSender.send(message);
}
/**
* 发送携带附件文件的邮件
*
* @param title 标题
* @param text 文本
* @param fileMap 附件集合
* @param targetMails 目标邮箱
*/
public void sendAttachFileMail(String title, String text, Map<String, String> fileMap, String... targetMails) {
sendAttachFileMail(title, text, null, fileMap, targetMails);
}
/**
* 发送携带附件文件的邮件
*
* @param title 标题
* @param text 文本
* @param Cc 抄送人
* @param fileMap 附件集合
* @param targetMails 目标邮箱
*/
@SneakyThrows(MessagingException.class)
public void sendAttachFileMail(String title, String text, String Cc, Map<String, String> fileMap, String... targetMails) {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
// 构建一个带附件的邮件对象
MimeMessageHelper mimeHelper = new MimeMessageHelper(mimeMessage, true);
// 发件人邮箱地址
mimeHelper.setFrom(eMailProperties.getUsername());
// 收件人邮箱地址数组(可实现批量发送)
mimeHelper.setTo(targetMails);
// 邮箱标题
mimeHelper.setSubject(title);
// 邮箱内容
mimeHelper.setText(text);
for (String filename : fileMap.keySet()) {
//第一个参数是文件名称,第二个参数是文件流
mimeHelper.addAttachment(filename, FileUtils.urlNetToMultipartFile(filename, fileMap.get(filename)));
}
if (StringUtils.isNotEmpty(Cc)) {
// 抄送人邮箱地址
mimeHelper.setCc(Cc);
}
javaMailSender.send(mimeMessage);
}
/**
* 模板解析方法,解析出一个String的html返回
* @param templateName 模板文件名称(带后缀)
* @param model 使用Map作为数据模型,定义属性和值
* @return
*/
@SneakyThrows({IOException.class, TemplateException.class})
public String templateHtml(String templateName, Map<String, Object> model) {
// 获得模板
Template template = freeMarkerConfigurer.getConfiguration().getTemplate(templateName);
// 传入数据模型到模板,替代模板中的占位符,并将模板转化为html字符串
String templateHtml = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
// 该方法本质上还是发送html邮件,调用之前发送html邮件的方法
return templateHtml;
}
}
文件流工具类
public class FileUtils {
/**
* 解析在线图片路径转换为 MultipartFile 对象
* @param fileName 携带后缀的文件名称
* @param urlNet 文件在线访问链接(minio永久访问链接)
* @return
*/
public static MultipartFile urlNetToMultipartFile(String fileName, String urlNet) {
try {
// 将在线图片地址转换为URL对象
URL url = new URL(urlNet);
// 打开URL连接
URLConnection connection = url.openConnection();
// 转换为HttpURLConnection对象
HttpURLConnection httpURLConnection = (HttpURLConnection) connection;
// 获取输入流
InputStream inputStream = httpURLConnection.getInputStream();
// 读取输入流中的数据,并保存到字节数组中
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
// 将字节数组转换为字节数组
byte[] bytes = byteArrayOutputStream.toByteArray();
// 创建ByteArrayInputStream对象,将字节数组传递给它
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
String suffix = fileName.split(".")[1];
FILE_CONTENT_TYPE content_type = FILE_CONTENT_TYPE.parseContentType(suffix);
// 创建MultipartFile对象,将ByteArrayInputStream对象作为构造函数的参数
MultipartFile multipartFile = new MultipartFileDto("file", fileName, content_type.getContentType(), byteArrayInputStream);
return multipartFile;
}catch (IOException ex){
ex.printStackTrace();
throw new UtilException("附件无效");
}
}
}
HTML模板文件
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no,
initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>邮件发送</title>
</head>
<body>
<div>
<div align="center">
<div class="open_email" style="margin-left: 8px; margin-top: 8px; margin-bottom: 8px; margin-right: 8px;">
<div>
<br>
<span class="genEmailContent">
<div id="cTMail-Wrap"
style="word-break: break-all;box-sizing:border-box;text-align:center;min-width:320px; max-width:660px; border:1px solid #f6f6f6; background-color:#f7f8fa; margin:auto; padding:20px 0 30px; font-family:'helvetica neue',PingFangSC-Light,arial,'hiragino sans gb','microsoft yahei ui','microsoft yahei',simsun,sans-serif">
<div class="main-content" style="">
<table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse">
<tbody>
<tr style="font-weight:300">
<td style="width:3%;max-width:30px;"></td>
<td style="max-width:600px;">
<!-- {{-- LOGO --}} -->
<div id="cTMail-logo" style="width:92px; height:25px;">
<!-- {{-- 替换跳转链接 --}} -->
<a href="">
<!-- {{-- 替换LOGO图片 --}} -->
<img border="0" src="https://imgcache.qq.com/open_proj/proj_qcloud_v2/mc_2014/cdn/css/img/mail/logo-pc.png"
style="width:92px; height:25px;display:block">
</a>
</div>
<!-- {{-- 页面上边的蓝色分割线 --}} -->
<p style="height:2px;background-color: #00a4ff;border: 0;font-size:0;padding:0;width:100%;margin-top:20px;"></p>
<div id="cTMail-inner" style="background-color:#fff; padding:23px 0 20px;box-shadow: 0px 1px 1px 0px rgba(122, 55, 55, 0.2);text-align:left;">
<table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse;text-align:left;">
<tbody>
<!-- {{-- 第一个单元格 --}} -->
<tr style="font-weight:300">
<!-- {{-- 左侧表格,设置左边距用的 --}} -->
<td style="width:3.2%;max-width:30px;"></td>
<!-- {{-- 中间表格,正文使用 --}} -->
<td style="max-width:480px;text-align:left;">
<!-- {{-- 以下是正文 --}} -->
<!-- {{-- 可以是标题 --}} -->
<h1 id="cTMail-title" style="font-size: 20px; line-height: 36px; margin: 0px 0px 22px;">
【XX平台】欢迎注册XXXXXX
</h1>
<p id="cTMail-userName" style="font-size:14px;color:#333; line-height:24px; margin:0;">
尊敬的${username},您好!
</p>
<p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
<span style="color: rgb(51, 51, 51); font-size: 14px;">
欢迎注册巴拉巴拉一大堆话。
</span>
</p>
<p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
<span style="color: rgb(51, 51, 51); font-size: 14px;">完成注册,请点击下面按钮验证邮箱。
<span style="font-weight: bold;">非本人操作可忽略。</span>
</span>
</p>
<!-- {{-- 按钮 --}} -->
<p class="cTMail-content"
style="font-size: 14px; color: rgb(51, 51, 51); line-height: 24px; margin: 6px 0px 0px; word-wrap: break-word; word-break: break-all;">
<!-- {{-- 下面替换成自己的链接 --}} -->
<a id="cTMail-btn" href="" title=""
style="font-size: 16px; line-height: 45px; display: block; background-color: rgb(0, 164, 255); color: rgb(255, 255, 255); text-align: center; text-decoration: none; margin-top: 20px; border-radius: 3px;">
点击此处验证邮箱
</a>
</p>
<p class="cTMail-content" style="line-height: 24px; margin: 6px 0px 0px; overflow-wrap: break-word; word-break: break-all;">
<span style="color: rgb(51, 51, 51); font-size: 14px;">
<br>
无法正常显示?请复制以下链接至浏览器打开:
<br>
<a href="" title=""
style="color: rgb(0, 164, 255); text-decoration: none; word-break: break-all; overflow-wrap: normal; font-size: 14px;">
这里是激活账号的链接
</a>
</span>
</p>
<!-- {{-- 来个署名 --}} -->
<dl style="font-size: 14px; color: rgb(51, 51, 51); line-height: 18px;">
<dd style="margin: 0px 0px 6px; padding: 0px; font-size: 12px; line-height: 22px;">
<p id="cTMail-sender" style="font-size: 14px; line-height: 26px; word-wrap: break-word; word-break: break-all; margin-top: 32px;">
此致
<br>
<strong>XXX团队</strong>
</p>
</dd>
</dl>
</td>
<!-- {{-- 右侧表格,设置右边距用的 --}} -->
<td style="width:3.2%;max-width:30px;"></td>
</tr>
</tbody>
</table>
</div>
<!-- {{-- 页面底部的推广 --}} -->
<div id="cTMail-copy" style="text-align:center; font-size:12px; line-height:18px; color:#999">
<table style="width:100%;font-weight:300;margin-bottom:10px;border-collapse:collapse">
<tbody>
<tr style="font-weight:300">
{<!-- {-- 左,左边距 --}} -->
<td style="width:3.2%;max-width:30px;"></td>
<!-- {{-- 中,正文 --}} -->
<td style="max-width:540px;">
<p style="text-align:center; margin:20px auto 14px auto;font-size:12px;color:#999;">
此为系统邮件,请勿回复。
<!-- {{-- 可以加个链接 --}} -->
<a href=""
style="text-decoration:none;word-break:break-all;word-wrap:normal; color: #333;" target="_blank">
取消订阅
</a>
</p>
<!-- {{-- 可以加个图片,公众号二维码之类的 --}} -->
<p id="cTMail-rights" style="max-width: 100%; margin:auto;font-size:12px;color:#999;text-align:center;line-height:22px;">
<img border="0" src="http://imgcache.qq.com/open_proj/proj_qcloud_v2/tools/edm/css/img/wechat-qrcode-2x.jpg"
style="width:64px; height:64px; margin:0 auto;">
<br>
关注服务号,移动管理云资源
<br>
<img src="https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/mail/cr.svg" style="margin-top: 10px;">
</p>
</td>
<!-- {{-- 右,右边距 --}} -->
<td style="width:3.2%;max-width:30px;"></td>
</tr>
</tbody>
</table>
</div>
</td>
<td style="width:3%;max-width:30px;"></td>
</tr>
</tbody>
</table>
</div>
</div>
</span>
</div>
</div>
</div>
</div>
</body>
</html>
测试发送
/**
* 测试发送邮件
*/
@Operation(summary = "测试发送邮件")
@GetMapping("/send/email")
public void sendEmail() {
String title = "测试发送邮件";
String templateName = "email.html";
Map<String, Object> model = new HashMap<>();
model.put("username", "客户01");
String[] targetMails = new String[]{"dhdaf@hrfdtec.com"};
eMailUtils.sendHtmlTemplate(title, templateName, model, targetMails);
}