实战!SpringBoot整合Vue3完美实现发送邮件的功能!
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();
}
}
}