Spring Boot+Vue 前后端分离实战:微光聊天室项目(重置密码页面)

之前我们已经写过了登录页面和注册页面,本期我们来写忘记密码的重置页面,本文将详细实现基于 邮箱验证码的密码重置功能。文章从用户触发“忘记密码”流程开始,完整讲解邮箱验证码发送、校验与失效机制的设计思路;在前端 Vue 中实现表单校验、验证码倒计时与交互反馈;在后端 Spring Boot 中完成验证码生成、缓存存储、接口校验与密码安全重置等关键逻辑。通过该实战案例,帮助你构建一个安全、规范、体验良好的邮箱验证码重置密码功能,为完整用户系统提供可靠保障。
1
验证码发送

下面内容是我们重置密码时候用到的验证码发送 在验证码模块的service实现的内容 如图 内容如下
/**
* 发送更改邮箱时候的邮箱验证码
*
* @param email 邮箱号
*/
@Override
public void sendChangeEmailCaptcha(String email) {
// 查询邮箱是否存在
LambdaQueryWrapper<UserInfo> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(UserInfo::getEmail, email);
boolean exists = userInfoService.exists(lambdaQueryWrapper);
// 判断是否存在
if (!exists) {
throw new BusinessException("该邮箱未被注册,请先前往注册");
}
// 获取剩余的过期时间
Long expire = redisUtils.getExpire(CaptchaConstant.CHANGE_EMAIL_CAPTCHA_KEY + email);
// 判断是否频繁发送
if (captchaProperties.getExpireTime() - expire < captchaProperties.getResendIntervalSeconds()) {
throw new BusinessException("请勿频繁发送验证码");
}
// 获取验证码
String code = CaptchaGenerator.generateCode();
// 声明参数实体
CaptchaMailDTO mailDTO = new CaptchaMailDTO(code, EmailCaptchaEnum.CHANGE_EMAIL.getDesc());
// 发送邮件
emailUtils.sendCaptchaThymeleafEmail(email, mailDTO, EmailCaptchaEnum.CHANGE_EMAIL.getSubject());
// 存储到redis中
redisUtils.set(CaptchaConstant.CHANGE_EMAIL_CAPTCHA_KEY + email, code, captchaProperties.getExpireTime());
}
2
实体DTO

如图所示在login模块的dto包中 新建一个实体类 内容如下 因为我们的密码需要加密所以就只做不为空的判断不做其他判断了
package com.glow.login.dto;
import com.glow.login.constant.AuthRegex;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author gjq
* @version 1.0
* @description: 重置密码的DTO实体类
* @date 2026/1/7 10:20
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResetPasswordDTO {
/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
/**
* 验证码
*/
@NotBlank(message = "验证码不能为空")
@Pattern(regexp = AuthRegex.CAPTCHA_REGEX, message = "请输入六位数的验证码")
private String code;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}
3
重置密码service




12
service的内容如下 大概流程就是 先校验 验证码是否正确 然后解密密码 再跟进邮箱修改密码并加密
/**
* 根据邮箱重置密码
*
* @param passwordDTO 重置密码的实体类
* @return 是否重置成功
*/
@Override
public boolean resetPasswordByEmail(ResetPasswordDTO passwordDTO) {
// 校验邮箱验证码
emailCaptchaService.verifyEmailCaptcha(passwordDTO.getEmail(), passwordDTO.getCode(),
CaptchaConstant.CHANGE_EMAIL_CAPTCHA_KEY);
// 解密密码
String password = RsaUtils.decrypt(passwordDTO.getPassword(), authProperties.getRsaPrivateKey());
// 修改密码 根据邮箱
LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(UserInfo::getPassword, SecurityUtil.encryptPassword(password));
updateWrapper.eq(UserInfo::getEmail,passwordDTO.getEmail());
// 修改并返回
return userInfoService.update(updateWrapper);
}
4
controller层编写

如图所示在web模块的auth包下就两个controller 一个是发送验证码的 一个是根据验证码重置密码的
/**
* 发送重置密码时候的邮箱验证码
*
* @param email 邮箱
* @return 发送成功
*/
@PostMapping("password/forgot/sendEmailCaptcha")
public ApiResponse sendForgotPasswordEmailCaptcha(@RequestParam("email") @Email String email) {
emailCaptchaService.sendChangeEmailCaptcha(email);
return ApiResponse.success("验证码发送成功");
}
/**
* 重置登录密码
*
* @param passwordDTO 重置密码参数¬
*/
@PostMapping("password/forgot/resetPasswordByEmail")
public ApiResponse resetPasswordByEmail(@RequestBody @Valid ResetPasswordDTO passwordDTO) {
boolean result = loginService.resetPasswordByEmail(passwordDTO);
return result ? ApiResponse.success("密码重置成功!") : ApiResponse.error("密码重置失败!");
}
5
前端api编写

如图所示 在api的auth login.ts 里面 输入以下内容 这个是请求的接口方法
/**
* 根据邮箱重置密码
*/
export function resetPasswordByEmail(resetPwdDto: ResetPasswordDto) {
return request({
url: '/login/password/forgot/resetPasswordByEmail',
method: 'post',
data: resetPwdDto
})
}
/**
* 发送找回密码时候的邮箱验证码
*/
export function sendForgotPasswordEmailCaptcha(email: string) {
return request({
url: '/login/password/forgot/sendEmailCaptcha',
method: 'post',
params: {
email
}
})
}
6
忘记密码页面编写

如图所示在views 下面新建一个forgot的包 然后里面新建一个index.vue的文件 当前你也可以直接教 forgot.vue 然后在vue文件里面写入以下内容 大概意思就是 发送验证码 然后根据验证码进行重置密码密码重置成功后直接跳转到登录页面
<script setup lang="ts">
import GlassInput from "@/components/glass/GlassInput.vue";
import GlassDivider from "@/components/glass/GlassDivider.vue";
import GlassBtn from "@/components/glass/GlassBtn.vue";
import type {ResetPasswordDto} from "@/types/auth/resetPasswordDto.ts";
import {reactive, ref} from "vue";
import {
captchaErrorIfInvalid,
emailErrorIfInvalid,
passwordErrorIfInvalid
} from "@/utils/validators/validate.ts";
import notification from "@/components/notification/notification.ts";
import {resetPasswordByEmail, sendForgotPasswordEmailCaptcha} from "@/api/auth/login.ts";
import {rsaEncryption} from "@/utils/rsaJsencrypt.ts";
import router from "@/router";
// 重置密码实体dto
let passwordDto: ResetPasswordDto = reactive({email: "", code: "", password: ""})
// 输入的密码
const password = ref("");
// 输入的确认密码
const confirmPass = ref("")
// 错误提示
let errorMsg = reactive({username: "", email: "", code: "", password: "", confirmPass: ""})
/**
* 重置密码
*/
const resetPassword = () => {
// 校验格式是否正确
if (verifyPasswordForm()) {
// 设置加密密码
passwordDto.password = rsaEncryption(password.value)
// 根据邮箱重置密码
resetPasswordByEmail(passwordDto).then(response => {
if (response.code == 200) {
notification.success({title: "密码重置成功!", message: '请前往登录'});
router.push({path:'/login'})
}
})
}
}
/**
* 校验表单的错误信息
*/
const verifyPasswordForm = (): boolean => {
// 设置邮箱异常提示
errorMsg.email = emailErrorIfInvalid(passwordDto.email)
// 设置验证码异常提示
errorMsg.code = captchaErrorIfInvalid(passwordDto.code)
// 设置密码异常提示
errorMsg.password = passwordErrorIfInvalid(password.value)
// 判断两次密码是否一致
if (password.value != confirmPass.value) {
errorMsg.confirmPass = "两次输入的密码不一致"
} else {
errorMsg.confirmPass = ""
}
// 判断是否有异常消息
return !(errorMsg.email || errorMsg.username || errorMsg.password || errorMsg.confirmPass || errorMsg.code);
}
// 验证码时间
const codeTime = ref(0);
// 计时器对象
let timer: undefined | number = undefined
// 开启验证码倒计时
const startCodeTimer = () => {
codeTime.value = 60
if (codeTime) clearInterval(timer)
timer = setInterval(() => {
codeTime.value--
if (codeTime.value <= 0 && codeTime) {
clearInterval(timer)
timer = undefined
}
}, 1000)
}
// 验证码加载状态
const captchaLoading = ref(false);
/**
* 发送验证码
*/
const sendCaptcha = () => {
// 获取校验邮箱的异常提示
let message = emailErrorIfInvalid(passwordDto.email);
// 赋值提示
errorMsg.email = message
// 判断是否有错误提示
if (message) {
return false;
}
// 开启加载状态
captchaLoading.value = true
// 发送邮箱验证码
sendForgotPasswordEmailCaptcha(passwordDto.email).then(response => {
if (response.code == 200) {
startCodeTimer()
}
}).finally(()=>{
// 关闭加载状态
captchaLoading.value = false;
})
}
</script>
<template>
<div class="auth-box">
<div class="auth-form-container glass-card">
<div class="auth-header">
<img src="@/assets/images/logo.png" width="50" alt="logo">
<span class="auth-title">重置密码</span>
<span class="auth-slogan">找回您的微光账户</span>
</div>
<div class="auth-form">
<div class="auth-form-item">
<label class="auth-form-label">邮箱地址</label>
<div class="auth-input-box">
<GlassInput :error-message="errorMsg.email" v-model="passwordDto.email" placeholder="请输入您的邮箱"/>
<div class="auth-form-error" v-show="errorMsg.email" v-text="errorMsg.email"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">验证码</label>
<div class="auth-input-box">
<div class="auth-captcha-box">
<GlassInput :maxLength="6" v-model="passwordDto.code" class="auth-captcha-input"
placeholder="六位数验证码"/>
<GlassBtn class="auth-captcha-btn" :disabled="captchaLoading ||codeTime > 0" @click="sendCaptcha">
<template v-if="captchaLoading">发送中</template>
<template v-else-if="codeTime === 0">发送验证码</template>
<template v-else>{{ `(${codeTime}秒)` }}</template>
</GlassBtn>
</div>
<div class="auth-form-error" v-show="errorMsg.code" v-text="errorMsg.code"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">新密码</label>
<div class="auth-input-box">
<GlassInput :maxLength="20" v-model="password" placeholder="请输入6-20位密码" type="password"/>
<div class="auth-form-error" v-show="errorMsg.password" v-text="errorMsg.password"/>
</div>
</div>
<div class="auth-form-item">
<label class="auth-form-label">确认新密码</label>
<div class="auth-input-box">
<GlassInput :maxLength="20" v-model="confirmPass" placeholder="再次输入密码" type="password"/>
<div class="auth-form-error" v-show="errorMsg.confirmPass" v-text="errorMsg.confirmPass"/>
</div>
</div>
<GlassBtn class="auth-submit-btn" @click="resetPassword">重置密码</GlassBtn>
</div>
<GlassDivider class="auth-form-divider"/>
<div class="auth-form-footer">
想起密码?
<router-link class="auth-footer-toLogin" to="/login">返回登录</router-link>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
@use "@/assets/style/auth-form";
</style>
7
添加路由




12
- 如图1所示我们把 路由对应过去
- 然后我们把忘记密码的路由加入到白名单中 如图2所示
8
完成展示



12
演示效果看媒体
0
0
0
qq空间
微博
复制链接
分享 更多相关项目
猜你喜欢
评论/提问(已发布 0 条)
0