SpringBoot整合Vue3实现发送邮箱验证码功能
ztj100 2025-05-30 19:04 7 浏览 0 评论
1.效果演示
2.思维导图
3.前言
本篇文章主要讲解 Springboot 整合 Vue3 实现校验 qq 邮箱验证码之后重置用户密码的功能。
开发功能之前我们先梳理一下主要逻辑:
1.我们要想让系统给其他用户发送邮件,那么系统肯定要先绑定一个已注册的用户 A 的邮箱。 用户 A 必须开通 qq 邮箱的相关服务。开通服务的 A 的邮箱号码作为发件人。
2.在前端页面用户输入邮箱号码,点击获取验证码按钮调用后台接口,后台随机生成几个数字的验证码,并将该用户的邮箱号码和验证码存入 Redis。
3.用户输入收到的验证码。点击下一步调用后台校验验证码功能。后台将用户输入的验证码和 Redis 里面的数据进行比对,校验无误返回 true。验证失败返回错误的提示信息。
4.用户验证码校验无误之后,就进入到修改密码的页面,接着完成修改密码的功能。
4.开通 qq 邮箱服务
我们要让 A 用户给其他用户发送邮件,那么用户 A 必须开通相关的服务。
1.进入 qq 邮箱,点击右上角的账号与安全
2.开启邮箱服务,保存生成的邮箱授权码
5.前端
5.1 创建并配置 vue 项目
这里我们使用的前端脚手架是 vite,vue 版本是 vue3,前端组件库是 Element plus。
5.1.1 创建 vue3 项目
npm create vite@latest vue3-zhifou -- --template vue
5.1.2 安装配置 Element Plus
npm install element-plus --save
在 main.js 里面配置 Element plus
5.1.3 安装配置 axios
npm i axios -- save
在 src/util 下面新建 axios.js 文件
import axios from "axios";
import router from "../router/index"
import { ElMessage } from 'element-plus'
// 1. 创建axios实例
const instance = axios.create({
// 接口
baseURL: "/api",
// 超时时间
timeout: 60000,
});
// 2.请求拦截
instance.interceptors.request.use(
config => {
// let token = sessionStorage.getItem('token');
// if (token) {
// config.headers['token'] = token
// }
return config;
},
error => {
// 请求发生错误,抛出异常
Promise.reject(error);
}
);
// 3.响应拦截
instance.interceptors.response.use(
res => {
// 关闭进度条
return res;
},
error => {
// 关闭进度条
if (error && error.response) {
const status = error.response.status
switch (status) {
case 400:
ElMessage.error("请求错误");
break;
case 401:
ElMessage.error("未授权,请重新登录");
break;
case 404:
ElMessage.error("请求错误,未找到相应的资源");
break;
case 500:
ElMessage.error("服务器错误");
break;
default:
ElMessage.error("请求失败");
}
} else {
if (JSON.stringify(error).includes("timeout")) {
error.code = "TIMEOUT";
error.message = "服务器响应超时,请刷新页面";
}
}
return Promise.reject(error);
}
);
// 4.导出 axios 实例
export default instance;
5.1.4 封装常用的 http 请求
在 /src/util 下面新建 http.js 文件
import instance from "./axios";
const post = (url, data) => {
return new Promise((resolve, reject) => {
instance
.post(url, data)
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
};
const get = (url, data) => {
return new Promise((resolve, reject) => {
instance
.get(url, { params: data })
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
};
const put = (url, data) => {
return new Promise((resolve, reject) => {
instance
.put(url, data)
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
};
const del = (url, data) => {
return new Promise((resolve, reject) => {
instance
.delete(url, { params: data })
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
};
export default {
post,
get,
put,
del,
};
5.1.5 配置后端服务 IP 与端口
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path';
export default defineConfig({
plugins: [vue()],
// 设置别名
resolve: {
alias: [
{
// 设置别名, '@' 指向 'src' 目录
find: "@",
replacement: path.resolve(__dirname, './src')
},
]
},
server: {
open: true,
port: 3000,
proxy: {
"/api": {
target: "http://127.0.0.1:8081/springboot-vue3-email", //
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
})
5.1.6 配置访问后台的接口文件
在 /src/api 文件夹下新建接口文件:index.js
import http from "../utils/http";
// 发送邮箱验证码
const sendEmailVerifyCode = (data) => {
return http.get("/index/sendEmailVerifyCode", data);
};
// 校验邮箱验证码
const checkEmailVerifyCode = (data) => {
return http.post("/index/checkEmailVerifyCode", data);
};
// 通过邮箱重置用户密码
const resetPasswordByEmail = (data) => {
return http.post("/sysUser/resetPasswordByEmail", data);
};
export default { sendEmailVerifyCode, resetPasswordByEmail, checkEmailVerifyCode }
5.2 创建发送邮箱验证码的组件
在 /src/components 文件夹下新建 resetPassword.vue 文件。这里不再贴出完整代码,只讲核心代码,后面会有完整代码。
其实整个页面稍微有点复杂的就是发送验证码的功能
当用户点击获取验证码按钮,获取验证码这几个文字变成“x秒后重新发送”,并且禁止点击。
所以要定义一个数字变量用来显示倒计时,还要定义一个变量和 disabled 进行绑定。
const isSend = ref(false);
const countDown = ref(0);
用户点击获取验证码按钮之后调用方法:
当后台发送验证码成功之后,disabled 绑定的变量值就为 true,countDown 初始值为 60。
然后通过 setInterval 定时器每隔一秒将 countDown 的值减去 1。当 countDown 的值小于等于 0 时,disabled 绑定的变量值就为 false,countDown 的值变为 0,并清除定时器。
接着点击下一步按钮,校验验证码是否正确:
// 校验邮箱验证码
const checkEmailCode = () => {
emailFormRef.value.validate(async (valid) => {
if (valid) {
const res = await userApi.checkEmailVerifyCode(form);
if (res.data.code === 200) {
// 校验通过
if (res.data.data) {
isCheckEmail.value = true;
}
} else {
ElMessage.error(res.data.message);
}
} else {
return false;
}
});
};
当校验通过之后就显示重置用户密码的表单信息,否则提示校验失败的提示信息。
这里用户发送邮箱验证码和重置密码的表单项都在同一个 el-form 里面,只不过是通过一个变量的值进行控制显示。
默认 isCheckEmail 的值是 false,当验证码校验通过之后就修改为 true。
6.后端 6.1 创建 Springboot 项目
6.1 pom 文件引入相关依赖
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.15</version>
</dependency>
<!-- 邮箱验证码 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
6.2 yml 配置 email 相关属性
server:
port: 8081
servlet:
context-path: /springboot-vue3-email
spring:
# 数据库相关
datasource:
url: jdbc:mysql://127.0.0.1:3306/zc_online_order?allowPublicKeyRetrieval=true&useSSL=false
username: root
password: zhiFou2024@!
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# redis相关
redis:
host: 127.0.0.1
port: 6379
password: zhiFou2024@!
database: 0
# 邮箱相关
mail:
host: smtp.qq.com #邮箱服务器地址
username: 2xxxxxx@qq.com #邮箱账号
password: xxxxxxxx #邮箱授权码
default-encoding: utf-8 #默认编码
6.3 创建 Email 工具类
/**
* @author 知否技术
* @description 邮箱工具类
* @date 2024-09-25 10:31
*/
@Component
@Slf4j
public class EmailUtil {
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private RedisTemplate redisTemplate;
/**
* 发件人邮箱
*/
@Value("${spring.mail.username}")
private String username ;
/**
* 发送邮箱验证码
* @param email
* @return
*/
public boolean sendEmailCode(String email){
// 创建邮件消息
SimpleMailMessage message = new SimpleMailMessage();
String emailCode = RandomUtil.randomNumbers(6);
// 邮箱验证码存入redis
String redisKey = String.format("redis:resetPassword:sendMessage:%s", email);
redisTemplate.opsForValue().set(redisKey, emailCode, 20, TimeUnit.MINUTES);
// 设置邮件主题
message.setSubject("【知否技术】你此次重置密码的验证码是:" + emailCode);
// 设置邮件发送者,昵称+<邮箱地址>
message.setFrom("发件人" + '<' + username + '>');
// 设置邮件接收者,可以有多个接收者,多个接受者参数需要数组形式
message.setTo(email);
// 设置邮件发送日期
message.setSentDate(new Date());
// 设置邮件的正文
message.setText("你此次重置密码的邮箱验证码是" + emailCode + ",请在20分钟内输入验证码进行下一步操作。如非本人操作,请忽略本次邮件!");
try {
// 发送邮件
javaMailSender.send(message);
return Boolean.TRUE;
} catch (Exception e) {
log.error("邮箱验证码异常结果: " + e.getMessage());
return Boolean.FALSE;
}
}
/**
* 校验邮箱验证码
* @param checkEmailParam
* @return
*/
public boolean checkEmailVerifyCode(CheckEmailParam checkEmailParam) {
String redisKey = String.format("redis:resetPassword:sendMessage:%s", checkEmailParam.getEmail());
// 从Redis获取邮箱验证码
String redisEmailCode = (String)redisTemplate.opsForValue().get(redisKey);
if(StrUtil.isBlank(redisEmailCode)){
throw new RuntimeException("验证码已过期,请重新发送!");
}
if(!checkEmailParam.getVerifyCode().equals(redisEmailCode)){
throw new RuntimeException("验证码不正确,请重新填写!");
}
return true;
}
}
6.4 封装 Redis 配置类
新建 RedisConfig 类
@Slf4j
@Configuration
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
6.5 配置全局异常处理器
新建 GlobalExceptionHandler 类
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕获全局异常
*
* @param e
* @return
*/
@ExceptionHandler(RuntimeException.class)
public Result handler(RuntimeException e) {
log.error("全局异常:{}", e.getMessage());
return Result.fail(e.getMessage());
}
}
6.6 创建 controller
@RestController
@RequestMapping("/index")
public class IndexController {
@Autowired
private EmailUtil emailUtil;
/**
* 发送邮箱验证码
* @param email
* @return
*/
@GetMapping("/sendEmailVerifyCode")
public Result sendEmailVerifyCode(@RequestParam String email) {
boolean flag = emailUtil.sendEmailCode(email);
if (flag) {
return Result.success(Boolean.TRUE);
} else {
return Result.fail();
}
}
/**
* 校验邮箱验证码
* @param checkEmailParam
* @return
*/
@PostMapping("/checkEmailVerifyCode")
public Result checkEmailVerifyCode(@RequestBody CheckEmailParam checkEmailParam){
boolean flag = emailUtil.checkEmailVerifyCode(checkEmailParam);
if (flag) {
return Result.success(Boolean.TRUE);
} else {
return Result.fail();
}
}
}
7.完整代码
链接:https://pan.baidu.com/s/1bYaQwdnsw8O8hmXTGI8iVQ?pwd=1234
提取码:1234
相关推荐
- Spring IoC Container 原理解析
-
IoC、DI基础概念关于IoC和DI大家都不陌生,我们直接上martinfowler的原文,里面已经有DI的例子和spring的使用示例...
- SQL注入:程序员亲手打开的潘多拉魔盒,如何彻底封印它?
-
一、现象:当你的数据库开始"说话",灾难就来了场景还原:...
- Java核心知识3:异常机制详解
-
1什么是异常异常是指程序在运行过程中发生的,由于外部问题导致的运行异常事件,如:文件找不到、网络连接失败、空指针、非法参数等。异常是一个事件,它发生在程序运行期间,且中断程序的运行。...
- MyBatis常用工具类三-使用SqlRunner操作数据库
-
MyBatis中提供了一个非常实用的、用于操作数据库的SqlRunner工具类,该类对JDBC做了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。SqlR...
- 爆肝2W字梳理50道计算机网络必问面试题
-
1.说说HTTP常用的状态码及其含义?思路:这道面试题主要考察候选人,是否掌握HTTP状态码这个基础知识点。...
- SpringBoot整合Vue3实现发送邮箱验证码功能
-
1.效果演示2.思维导图...
- 最全JAVA面试题及答案(200+)
-
Java基础1.JDK和JRE有什么区别?JDK:JavaDevelopmentKit的简称,Java开发工具包,提供了Java的开发环境和运行环境。JRE:JavaRunti...
- Java程序员找工作翻车现场!你的项目描述踩了这几个坑?
-
Java程序员找工作翻车现场!你的项目描述踩了这几个坑?噼里啪啦敲了三年代码,简历一投石沉大海?兄弟,问题可能出在项目描述上!知道为什么面试官看你的项目像看天书吗?因为你写了三个致命雷区:第一,把项目...
- 2020最新整理JAVA面试题附答案,包含19个模块共208道面试题
-
包含的模块:本文分为十九个模块,分别是:Java基础、容器、多线程、反射、对象拷贝、JavaWeb、异常、网络、设计模式、Spring/SpringMVC、SpringBoot/Spring...
- 底层原理深度解析:equals() 与 == 的 JVM 级运作机制
-
作为Java开发者,你是否曾在集合操作时遇到过对象比较的诡异问题?是否在使用HashMap时发现对象丢失?这些问题往往源于对equals()和==的误解,以及实体类中这两个方法的不当实...
- 雪花算法,什么情况下发生 ID 冲突?
-
分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的...
- 50个Java编程技巧,免费送给大家
-
一、语法类技巧1.1.使用三元表达式普通:...
- 如何规划一个合理的JAVA项目工程结构
-
由于阿里Java开发手册对于工程结构的描述仅限于1、2节简单的概述,不能满足多样的实际需求,本文根据多个项目中工程的实践,分享一种较为合理实用的工程结构。工程结构的原则有依据、实用。有依据的含义是指做...
- Java 编程技巧之单元测试用例编写流程
-
温馨提示:本文较长,同学们可收藏后再看:)前言...
- MyBatis核心源码解读:SQL执行流程的奇妙之旅
-
MyBatis核心源码解读:SQL执行流程的奇妙之旅大家好呀!今天咱们要来一场既烧脑又有趣的旅程——探索MyBatis这个强大框架的核心秘密。你知道吗?当你在项目里轻轻松松写一句“select*f...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)