SpringBoot+Vue实现邮件发送与验证码验证
# Spring Boot + Vue 实现邮件发送与验证码验证
前言
在企业级应用中,用户身份验证是一个重要的环节,尤其是在登录、注册、找回密码等场景中,验证码验证是一种常用且有效的手段。通过邮件发送验证码并进行验证,不仅可以提高安全性,还能增强用户体验。本节将详细介绍如何使用 Spring Boot 和 Vue 来实现一个完整的邮件发送与验证码验证功能,结合 Redis 实现验证码的存储与管理,确保系统在高并发、分布式环境下的可靠性与一致性。
# 一、依赖引入
在 Spring Boot
项目中,需要先引入邮件发送和 Redis 的相关依赖。
pom.xml
依赖配置
<dependencies>
<!-- Spring Boot 邮件发送依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Spring Boot Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
关键依赖说明:
- spring-boot-starter-mail:简化邮件发送的配置和操作。
- spring-boot-starter-data-redis:提供对 Redis 的支持,用于存储验证码等临时数据。
# 二、配置文件
在 application.yml
中配置邮件服务器和 Redis。
application.yml
配置
spring:
mail:
host: smtp.qq.com # SMTP服务器地址,例如QQ邮箱
port: 587 # SMTP服务器端口
username: your-email@example.com # 发件人邮箱地址
password: your-email-password # 授权码(而非邮箱密码)
properties:
mail:
smtp:
auth: true # 启用SMTP认证
starttls:
enable: true # 启用STARTTLS加密
redis:
host: localhost
port: 6379
timeout: 6000ms
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
配置说明:
spring.mail.password
这里是邮箱的授权码(例如QQ邮箱),而非登录密码。spring.redis
相关配置用于连接 Redis 数据库,用于存储验证码数据。
# 三、Redis 配置
为了更好地管理 Redis 连接,推荐通过配置类进行自定义配置。
RedisConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/**
* 自定义 RedisTemplate,用于序列化 Redis 中的键和值
* @param lettuceConnectionFactory 连接工厂,必须配置
* @return RedisTemplate<String, Object>
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(lettuceConnectionFactory);
// 设置键和值的序列化器,避免出现乱码问题
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
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
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
关键点:
- LettuceConnectionFactory:用于创建 Redis 连接。
- StringRedisSerializer:指定 Redis 中存储的键和值的序列化方式,确保数据能够正确存取。
# 四、工具类:统一响应结果封装
在企业项目中,统一的响应格式有助于前后端协同工作。
AjaxResult.java
package com.example.util;
/**
* 统一响应结果封装类
*/
public class AjaxResult {
private int code; // 响应状态码,200表示成功,500表示失败
private String message; // 响应消息
private Object data; // 响应数据,可能为空
/**
* 操作成功时调用
* @param data 返回的数据
* @return AjaxResult
*/
public static AjaxResult success(Object data) {
return new AjaxResult(200, "操作成功", data);
}
/**
* 操作失败时调用
* @param message 错误信息
* @return AjaxResult
*/
public static AjaxResult fail(String message) {
return new AjaxResult(500, message, null);
}
/**
* 自定义状态码和消息的失败响应
* @param code 自定义状态码
* @param message 错误信息
* @return AjaxResult
*/
public static AjaxResult fail(int code, String message) {
return new AjaxResult(code, message, null);
}
private AjaxResult(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
// Getters and Setters
}
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
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
关键点:
- 通过静态方法
success
和fail
,快速生成响应结果,便于控制器返回统一的格式。
# 五、服务层接口和实现
# 1. 邮件发送服务接口与实现
邮件发送逻辑通过 EmailService
统一管理,封装了发送邮件的具体过程,包括支持 HTML 格式的邮件内容。
# EmailService.java
package com.example.service;
import javax.mail.MessagingException;
public interface EmailService {
/**
* 发送邮件
*
* @param to 收件人邮箱地址,必须提供,不能为空
* @param subject 邮件主题,必须提供,不能为空
* @param text 邮件正文,支持HTML格式,必须提供,不能为空
* @throws MessagingException 当邮件发送失败时抛出该异常
*/
void sendEmail(String to, String subject, String text) throws MessagingException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
接口说明:
to
:收件人邮箱地址,必须提供且不能为空。subject
:邮件主题,必须提供且不能为空。text
:邮件正文,支持 HTML 格式,必须提供且不能为空。
# EmailServiceImpl.java
package com.example.service.impl;
import com.example.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
@Service
public class EmailServiceImpl implements EmailService {
@Autowired
private JavaMailSender javaMailSender;
@Value("${spring.mail.username}")
private String fromEmail; // 从配置文件中读取发件人邮箱
@Override
public void sendEmail(String to, String subject, String text) throws MessagingException {
// 创建MimeMessage对象,用于构建邮件信息
MimeMessage message = javaMailSender.createMimeMessage();
// 使用MimeMessageHelper来帮助设置邮件内容
MimeMessageHelper helper = new MimeMessageHelper(message, true); // true表示支持HTML格式
helper.setFrom(fromEmail); // 设置发件人
helper.setTo(to); // 设置收件人
helper.setSubject(subject); // 设置邮件主题
helper.setText(text, true); // 设置邮件正文,支持HTML格式
// 发送邮件
javaMailSender.send(message);
}
}
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
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
关键点:
- JavaMailSender:Spring 提供的邮件发送接口,简化了邮件发送操作。
- MimeMessageHelper:辅助构建复杂的邮件内容,包括支持 HTML 格式的正文。
# 2. 验证码发送服务接口与实现
验证码的生成、发送和存储逻辑集中在 VerifyCodeService
中,通过 Redis 存储验证码,确保在分布式系统中的一致性。
# VerifyCodeService.java
package com.example.service;
import com.example.util.AjaxResult;
public interface VerifyCodeService {
/**
* 发送验证码到指定邮箱
*
* @param email 收件人邮箱地址,必须提供且格式正确
* @return AjaxResult 返回包含UUID的结果,供后续验证使用
*/
AjaxResult sendVerificationCode(String email);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
接口说明:
email
:收件人邮箱地址,必须提供且格式正确。
# VerifyCodeServiceImpl.java
package com.example.service.impl;
import com.example.service.EmailService;
import com.example.service.VerifyCodeService;
import com.example.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class VerifyCodeServiceImpl implements VerifyCodeService {
@Autowired
private EmailService emailService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private ResourceLoader resourceLoader; // 用于加载HTML模板
@Override
public AjaxResult sendVerificationCode(String email) {
// 验证邮箱格式
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
if (!Pattern.matches(emailRegex, email)) {
return AjaxResult.fail("无效的邮箱地址");
}
// 生成6位随机验证码
String verificationCode = generateVerificationCode();
// 加载并填充HTML模板
String emailContent = loadEmailTemplate(verificationCode);
// 发送邮件
try {
emailService.sendEmail(email, "邮箱验证码", emailContent);
} catch (Exception e) {
return AjaxResult.fail("邮件发送失败");
}
// 将验证码存入Redis,设置5分钟有效期
String uuid = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("emailCode:" + uuid, verificationCode, 5, TimeUnit.MINUTES);
// 返回UUID给前端,供后续验证使用
return AjaxResult.success(uuid);
}
// 生成随机验证码
private String generateVerificationCode() {
return String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 生成6位随机数
}
// 加载并填充HTML模板
private String loadEmailTemplate(String code) {
try {
// 加载模板文件
Resource resource = resourceLoader.getResource("classpath:templates/email-template.html");
// 读取模板内容
String template = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining(System.lineSeparator()));
// 替换占位符
return template.replace("{{code}}", code);
} catch (Exception e) {
throw new RuntimeException("加载邮件模板失败", e);
}
}
}
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
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
关键点:
- 验证码生成、邮件发送和 Redis 存储的逻辑集中在服务层。
- 使用
ResourceLoader
动态加载 HTML 模板,并使用占位符替换的方式填充验证码。
# 3. HTML 邮件模板
为了提升代码的维护性和复用性,HTML 模板被抽离出来放置在 resources/templates
目录下。
# email-template.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>验证码邮件</title>
<style>
.container {
width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
background-color: #f9f9f9;
border: 1px solid #e8e8e8;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
text-align: center;
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}
.content {
font-size: 16px;
color: #333333;
line-height: 1.6;
}
.code {
font-size: 24px;
font-weight: bold;
color: #f44336;
margin: 20px 0;
text-align: center;
}
.footer {
font-size: 12px;
color: #999999;
text-align: center;
margin-top: 30px;
}
.footer p {
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">验证码邮件通知</div>
<div class="content">
<p>尊敬的用户,您好:</p>
<p>您正在进行重要操作,请在验证码输入框中输入下方的验证码:</p>
<div class="code">{{code}}</div>
<p>请注意:该验证码有效期为5分钟,请勿泄露给他人。</p>
<p>如果您没有进行此操作,请忽略此邮件。</p>
</div>
<div class="footer">
<p>此邮件为系统自动发送,请勿回复。</p>
<p>感谢您的使用!</p>
<p>企业团队</p>
</div>
</div>
</body>
</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
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
# 六、控制器层
控制器负责接收前端请求并调用服务层接口。
# VerifyCodeController.java
package com.example.controller;
import com.example.service.VerifyCodeService;
import com.example.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/verify")
public class VerifyCodeController {
@Autowired
private VerifyCodeService verifyCodeService;
/**
* 发送验证码到用户邮箱
*
* @param admin 请求体,包含用户邮箱
* @return 发送结果和UUID
*/
@PostMapping("/send-code")
public AjaxResult sendVerificationCode(@RequestBody Admin admin) {
return verifyCodeService.sendVerificationCode(admin.getEmail());
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
关键点:
- 控制器的职责仅限于接收请求和返回响应,具体业务逻辑由服务层实现。
# 七、前端 Vue 代码集成
前端通过 Axios 调用后端接口,实现验证码的发送。
# request.js
import axios from 'axios';
// 创建axios实例
const request = axios.create({
baseURL: 'http://localhost:8080/api', // 后端服务基础路径
timeout: 10000 // 请求超时
});
// 请求拦截器
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
return config;
}, error => {
return Promise.reject(error);
});
// 响应拦截器
request.interceptors.response.use(response => {
return response.data;
}, error => {
return Promise.reject(error);
});
export default request;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Vue 组件代码
<template>
<div>
<el-form>
<el-form-item>
<el-input v-model="admin.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-button type="primary" @click="sendCode">发送验证码</el-button>
</el-form>
</div>
</template>
<script>
import request from '@/utils/request';
export default {
data() {
return {
admin: {
email: ''
}
};
},
methods: {
async sendCode() {
try {
const response = await request.post('/verify/send-code', this.admin);
if (response.code === 200) {
this.$message.success('验证码发送成功,请检查您的邮箱');
} else {
this.$message.error(response.message);
}
} catch (error) {
this.$message.error('验证码发送失败');
}
}
}
};
</script>
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
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
关键点:
- 通过
Axios
发送 POST 请求,提交邮箱信息以请求发送验证码。 - 验证码发送成功后,前端提示用户检查邮箱。
总结
- 依赖配置与代码结构清晰:项目遵循了分层架构,控制器、服务层和工具类职责明确。
- 服务层封装核心业务逻辑:验证码生成、邮件发送和存储均在服务层实现,控制器只负责调用。
- Redis 实现分布式数据一致性:验证码存储在 Redis 中,确保数据在分布式环境中的一致性和可用性。
- 前后端分离:前端 Vue 代码通过 Axios 调用后端 API,实现了验证码发送功能。
编辑此页 (opens new window)
上次更新: 2025/03/16, 22:19:39