一开始自己做项目的时候,有一个登录和注册的功能,这个功能也是老生常谈了,之前一直是用用户名 + 密码做的。然后突然想用手机号 + 验证码来玩一玩,当时还去阿里云买了个短信服务,结果买是买完了,但是接入短信服务需要进行企业认证,就不了了之了。
既然手机号 + 验证码行不通,那就降级用邮箱号 + 验证码。不过由于个人原因,就只能用 QQ 邮箱了。
配置配置
用 SpringBoot 发送邮件,有官方 Starter:spring-boot-starter-mail,底层是 JavaMailSender。
添加依赖:JDK17 + SpringBoot 3.0.0。
XML
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
添加 yml 配置:
bash
server:
port: 8080
spring:
application:
name: email
mail:
host: smtp.qq.com
port: 465
username: xxx@qq.com # 你的邮箱地址
password: xxx # 授权码,不是登录密码
protocol: smtp
default-encoding: UTF-8
properties:
mail:
smtp:
auth: true
ssl:
enable: true
socketFactory:
port: 465
class: javax.net.ssl.SSLSocketFactory
这里的 username 就是要用哪个邮箱发送,比如我有两个邮箱:A@qq.com,B@qq.com,我用 A 邮箱给 B 邮箱发消息,这里的 username 就填 A@qq.com。
password 可不是这个邮箱账号的密码,而是这个邮箱账号的授权码。QQ 邮箱网页 > 设置 > 账号与安全 > 安全设置 > 开启服务,获取授权码。
username + password 是邮件服务器的登录凭证,让 Spring Boot 以这个邮箱的身份发送邮件。
发送文本消息邮件
用邮件发送验证码,就是直接写文本。
java
@RestController
public class EmailController {
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String from;
@PostMapping("/sendText")
public Integer sendText(String to) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
// 邮件主题
message.setSubject("Steam - 登录验证码");
// 邮件内容
message.setText("您正在登录steam,验证码是:406406,5分钟内有效!");
javaMailSender.send(message);
// TODO:将验证码保存到Redis中,设置ttl=5分钟
return 200;
}
}

from 是发送方,to 是接收方,subject 就是邮件主题,text 是邮件内容。
完整的验证码功能,还是把验证码保存起来,可以用数据库也可以用 Redis 或者 ConcurrentHashMap 都行。
现在发送验证码是成功了,但是还有其他类型的邮件:

这种邮件可比纯文本好看多了,这些邮件是怎么发送的呢?
发送HTML邮件
这种邮件看起来这么漂亮,一看就是老HTML了。
那我也写一个HTML页面发送过去。
java
@PostMapping("/sendHtml")
public Integer sendHtml(String to) throws MessagingException, IOException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject("欢迎来到我的第一个页面");
String str = Files.readString(Paths.get("C:\\Users\\admin\\Downloads\\hello-world.html"));
// 把html文件读取出来,保存为字符串
// public void setText(String text, boolean html)
helper.setText(str, true);
javaMailSender.send(message);
return 200;
}
这个邮件的内容是什么呢?我放在文章的最后了,其实我也是让 AI 写的。


邮件是可以看到图片的,但是点击按钮不会显示 "hello world",因为邮件是禁用 js 的。
为什么邮件要禁用 js 呢?
因为邮件要保证安全,而 js 可以实现很多逻辑,如果邮件可以运行 js,那你点击一个垃圾邮件,这个邮件就可以通过 js 获取你的个人信息,给恶意接口发送请求等等。
所以,邮件里面的交互是很少的,更多的是放一个超链接跳转浏览器页面。
不仅如此,邮件也不支持外部 css,像是在 html 里面通过 <link> 标签引入 css 是不允许的。也就是说,一个邮件就是一个 html 文件,在这个 html 文件里通过 <style> 和行内方式添加样式。
而且,邮件不支持很多现代的 css 样式,甚至 html 推荐用 table、tr、td 来建立结构。如果你在邮件中看到了视频,一般是 gif,such as this:

不过至少 QQ 邮箱是支持外部链接加载图片的。
但是用 html 写邮件就折磨人了:HTML 邮件兼容问题与解决方案 - fecoder - SegmentFault 思否。
发送附件文件
最近也是秋招季节,有些公司是要你上传个人简历,发送邮件给 HR 的。
java
@PostMapping("/sendFile")
public Integer sendWithAttachment(String to) throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject("个人简历");
helper.setText("HR您好,这是我的个人简历,请你查阅", false);
File file = new File("C:\\Users\\admin\\Downloads\\简历.pdf");
helper.addAttachment(file.getName(), file);
javaMailSender.send(message);
return 200;
}

代码
EmailController.java
java
@RestController
public class EmailController {
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String from;
@PostMapping("/sendText")
public Integer sendText(String to) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject("Steam - 登录验证码");
message.setText("您正在登录steam,验证码是:406406,5分钟内有效!");
javaMailSender.send(message);
return 200;
}
@PostMapping("/sendHtml")
public Integer sendHtml(String to) throws MessagingException, IOException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject("欢迎来到我的第一个页面");
String str = Files.readString(Paths.get("C:\\Users\\admin\\Downloads\\hello-world.html"));
// 把html文件读取出来,保存为字符串
// public void setText(String text, boolean html)
helper.setText(str, true);
javaMailSender.send(message);
return 200;
}
@PostMapping("/sendFile")
public Integer sendWithAttachment(String to) throws MessagingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to);
helper.setSubject("个人简历");
helper.setText("HR您好,这是我的个人简历,请你查阅", false);
File file = new File("C:\\Users\\admin\\Downloads\\简历.pdf");
helper.addAttachment(file.getName(), file);
javaMailSender.send(message);
return 200;
}
}
hello-world.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: linear-gradient(135deg, #eef2ff 0%, #f8fafc 100%);
}
.card {
background: #fff;
padding: 40px 48px;
border-radius: 20px;
box-shadow: 0 12px 32px rgba(15, 23, 42, .10);
text-align: center;
width: min(90vw, 420px);
}
/* Logo 位置:使用指定链接作为背景图片 */
.logo {
width: 126px;
height: 126px;
margin: 0 auto 24px;
border-radius: 50%;
background-image: url("https://cdn-bot.hunyuan.tencent.com/m/yb-chat/logo_animated_126.png");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
box-shadow: 0 6px 16px rgba(99, 102, 241, .25);
}
.btn {
padding: 12px 28px;
font-size: 16px;
color: #fff;
background: #4f46e5;
border: none;
border-radius: 999px;
cursor: pointer;
transition: transform .15s ease, background .2s ease;
}
.btn:hover { background: #4338ca; transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.message {
margin-top: 24px;
font-size: 28px;
font-weight: 600;
color: #111827;
opacity: 0;
transform: translateY(8px);
transition: opacity .35s ease, transform .35s ease;
}
.message.show { opacity: 1; transform: translateY(0); }
</style>
</head>
<body>
<div class="card">
<div class="logo" role="img" aria-label="Logo"></div>
<button class="btn" id="sayBtn">点我一下</button>
<div class="message" id="msg">Hello World</div>
</div>
<script>
const btn = document.getElementById('sayBtn');
const msg = document.getElementById('msg');
btn.addEventListener('click', () => {
msg.classList.add('show');
});
</script>
</body>
</html>