Vue + element +Springboot 通过邮箱找回密码

  • 需求分析
    • 一、导入
    • 二、流程分析
  • 详细设计
    • 一、前端界面设计
      • 1. 登录界面
      • 2. 重置密码界面
    • 二、后端代码设计
      • 1. JavaMail配置
      • 2. QQ邮箱开启STMP授权
      • 3. 配置applicaiton.yml文件
      • 4. 新建文件夹
      • 5. 邮件配置:
      • 6. User相关类:User.java、UserMapper、UserService.java、UserServiceImpl.java
      • 7. ResetPassword相关类
      • 8. 数据库创建表格
      • 9. 编写控制器LoginController
      • 10. Result类
  • 效果展示
    • 一、登录界面
    • 二、找回密码
    • 三、输入邮箱
    • 四、输入验证码
    • 五、修改密码
  • 结尾
  • 查漏补缺
  • 查漏补缺2

需求分析

一、导入

接上一篇登录界面 拼图验证, 项目开发过程中还有一个需求,实现 通过邮箱重置密码

重置密码,作为一个正经网站,那都是必备的需求,那咱可不得整一个!

但是鉴于设计的范围有点广,先列出我觉得你需要会一点的东西,否则,直接dang代码可能报很多错误。

前端知识:

  • Vue.js
  • axios
  • element

后端知识:

  • springboot

二、流程分析


文字解释:

1. 根据用户注册时输入的邮箱账号,当进入重置密码界面时,先输入用户名和邮箱,前端表单将输入送到后端时,判断数据库中是否有对应的账户及邮箱
2. 如果存在,则后端发送一份携带验证码html邮件到用户邮箱,用户复制验证码到前端页面
3. 提交验证码到后端,后端根据用户名和验证码验证是否匹配,若匹配成功,则进入到输入密码的板块;若过期,则从 1开始重置密码。
4. 最后将当前的用户名密码重置为新密码。
5. 一个账号一天之内只能重置3次,超出后当天不在让其重置密码。

详细设计

一、前端界面设计

前端项目配置,首先需要安装axios, 用来跨域传输数据,以及element,因为绘制组件的时候用到了,这里我就默认你们都有了。

1. 登录界面

为什么要登录界面呢?

  1. 一般用户都是输入密码多次出错后,才会开始重置密码,所以在登录界面提供重置密码链接。
  2. 登录界面需要传递用户输入的用户名/邮箱,咱不能让张三重置李四的账号密码(好像也不会,就算知道了李四的邮箱,但是你也得登录李四的邮箱之后才能获得邮件邮件码)。

Login.vue

<template><div id="login"><el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24"><el-form :model="loginForm" :rules="rules" class="login-container" size="medium" @keyup.enter.native="handleClick"><h3 class="login_title">用户登录</h3><el-form-item prop="username" ><el-input type="text" v-model="loginForm.username" autofocus ref="username"auto-complete="off" placeholder="用户名/邮箱" prefix-icon="el-icon-user-solid" spellcheck="false"></el-input></el-form-item><el-form-item prop="password"><el-input type="password" v-model="loginForm.password" autofocus ref="password"auto-complete="off" placeholder="密码" prefix-icon="el-icon-key" v-on:input="change"></el-input></el-form-item><el-form-item style="width: 100%;"><el-checkbox class="login_remember" v-model="checked" ><span style="color: #409EFF">记住我</span><label style="color: #949493">不是自己电脑请勿勾选</label></el-checkbox><el-button type="text" style="float: right; text-decoration: none; color: #50b6ff" @click="beforeTo">找回密码?</el-button></el-form-item><el-form-item style="width: 100%"><el-button type="primary" class="button_login" >登录</el-button><router-link to="register"><el-button type="primary" class="button_register">注册</el-button></router-link></el-form-item></el-form></el-col><p class="login-copyright">© 2020 lkq 版权所有</p></div>
</template><script>
export default {name: 'Login',data() {return {isVerificationShow: false,rules: {username: [{required: true, message: '用户名或邮箱不能为空', trigger: 'change'}],password: [{required: true, message: '密码不能为空', trigger: 'change'}]},checked: true,loginForm: {username: '',password: ''},puzzleImgList: [require("../../assets/images/verify/1.jpg"),require("../../assets/images/verify/2.jpg"),require("../../assets/images/verify/3.jpg"),require("../../assets/images/verify/4.jpg"),require("../../assets/images/verify/5.jpg"),require("../../assets/images/verify/6.jpg"),require("../../assets/images/verify/7.jpg"),require("../../assets/images/verify/8.jpg"),require("../../assets/images/verify/9.jpg"),require("../../assets/images/verify/10.jpg"),],isInput: false,}},methods: {handleSuccess() {// 验证通过后关闭图片验证this.isVerificationShow = false;// 将数据传送到后端验证this.login()},handleError() {// 滑动验证失败console.log("验证失败")},handleClick() {if (this.loginForm.username === '' || this.loginForm.password === '') {// 点击登录时,如果用户名或者密码未输入,那么提醒用户输入if (this.loginForm.username === '') {this.$message({message: '警告, 用户名或邮箱未输入哦',type: 'warning'});this.$refs.username.focus();}else {this.$message({message: '警告, 密码未输入呀',type: 'warning'});this.$refs.password.focus();}}else {this.isVerificationShow = true;}},change() {// 如果监听到输入框发生变化,那么采用用户输入的密码this.isInput = true;},beforeTo() {if (this.loginForm.username === '') {this.$notify({title: '跳转失败',message: "输入用户名或邮箱再去重置密码吧",type: 'error'});}else {this.$router.push({name: 'ResetPassword',query: {username: this.loginForm.username,}})}}}
}
</script><style scoped >#login {background-image: url('../login_img.jpg');background-repeat: no-repeat;background-size: cover;height: 100%;width: 100%;position: fixed;
}
.login-container {border-radius: 15px;background-clip: padding-box;margin: 10% 40% 0 40%;width: 20%;padding: 25px 30px;background: #fff;border: 1px solid #eaeaea;box-shadow: 0 0 25px #cac6c6;opacity: 0.7;
}
.login_title {margin: 0px auto 40px auto;text-align: center;color: #505458;
}
.login_remember {margin: 0px;text-align: left;float: left;}
.button_login {width: 40%;background: #409EFF;border: none;float: left
}.button_register {width: 40%;background: #505458;border: none;float: right;
}
.login-copyright {color: #eee;padding-bottom: 20px;text-align: center;position: relative;z-index: 1;
}
@media screen and (min-height: 550px) {.login-copyright {position: absolute;bottom: 0;right: 0;left: 0;}
}
</style>

稍稍解释一下下:

  • :model="loginForm" : 表单的数据对象。
  • :rules="rules : 表单验证规则。
  • @keyup.enter.natice="handleClick": enter键函数,用户输入数据后可以直接enter键,不一定要点击按钮。
  • 记住我:利用cookie来存储登录信息,不是重点。
  • 找回密码:利用邮箱找回密码,重点。

界面效果展示:

2. 重置密码界面

ResetPassword.vue

<template><div class="resetPassword"><div class="container"><el-steps :active="active" :space="200" finish-status="success"  align-center><el-step title="验证用户名和邮箱" icon="el-icon-edit"></el-step><el-step title="输入验证码" icon="el-icon-s-promotion"></el-step><el-step title="设置新密码" icon="el-icon-key"></el-step></el-steps><div v-if="active === 0" class="common_div"><el-form :model="Form"  class="user-container" label-position="left" label-width="60px" size="medium"><el-form-item  style="float: right; width: 80%" label="用户名"><el-input type="text" v-model="Form.username" autofocus ref="username" auto-complete="off"placeholder="请输入要找回密码的用户名" prefix-icon="el-icon-user-solid" spellcheck="false" :disabled="isUsername"></el-input></el-form-item><el-form-item style="float: right; width: 80%" label="邮箱号"><el-input type="text" v-model="Form.email" autofocus ref="email" auto-complete="off"placeholder="请输入用来找回密码的邮箱" prefix-icon="el-icon-message" spellcheck="false" :disabled="!isUsername"></el-input></el-form-item></el-form></div><div v-if="active === 1" class="common_div"><el-form :model="codeForm"  class="user-container" label-position="left" label-width="60px" size="medium"><el-form-item  style="float: right; width: 80%" label="验证码"><el-input type="text" v-model="codeForm.code" autofocus ref="code" auto-complete="off"placeholder="请输入邮箱验证码" prefix-icon="el-icon-s-promotion" spellcheck="false"></el-input></el-form-item></el-form></div><div v-if="active === 2" class="common_div"><el-form :model="passwordForm"  class="user-container" label-position="left" label-width="60px" size="medium"><el-form-item  style="float: right; width: 80%" label="新密码"><el-input type="password" v-model="passwordForm.password" autofocus ref="password"auto-complete="off" placeholder="请输入新密码" prefix-icon="el-icon-key" ></el-input></el-form-item></el-form></div><div class="common_div"><el-button  @click="next" :disabled="disabled" class="action_button">下一步</el-button></div></div></div>
</template><script>
export default {name: "ResetPassword",data() {return {active: 0,Form: {username: '',email: '',},codeForm: {code: '',},passwordForm: {password: '',},disabled: false,isUsername: false,}},created() {let regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;if (regEmail.test(this.$route.query.username)) {console.log("传来了邮箱")this.Form.email = this.$route.query.username;this.isUsername = false;} else {// 传来的不是邮箱,那就是用户名console.log("传来了用户名")this.Form.username = this.$route.query.username;this.isUsername = true;}},methods: {isEmail() {let regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;if (!regEmail.test(this.Form.email)) {this.$message({message: '邮箱格式不正确',type: 'error'});return false;}return true;},beforePost () {if (this.Form.username === '' || this.Form.email === '') {// 重置密码时,如果用户名或者邮箱未输入,那么提醒用户输入if (this.Form.username === '') {this.$message({message: '警告, 用户名未输入哦',type: 'warning'});this.$refs.username.focus();}else {this.$message({message: '警告, 邮箱未输入呀',type: 'warning'});this.$refs.email.focus();}return false;}else {//进行邮箱格式的检测return this.isEmail();}},next() {// 当面板为0时,先判断用户名和邮箱是否输入,进行相关的验证if (this.active === 0) {let isFinished = this.beforePost();if (isFinished) {//数据输入正确后,将按钮禁掉,并提示相关信息,然后数据发送到后台this.disabled = true;this.$notify.info({title: '提示',message: '数据正确发送,请耐心等待,勿重复操作!',duration: 0,offset: 100,});this.post();}}// 当面板为1时,则到了用户输入验证码的时候, 将验证码传入后台if (this.active === 1) {// 如果验证码未输入,提示用户if (this.codeForm.code === '') {this.$notify({title: '警告',message: '警告, 验证码未输入,请去您邮箱中查看!',type: 'warning',offset: 100,});this.$refs.code.focus();}else {this.$axios.post('/resetPassword', {code: this.codeForm.code,username: this.username,}).then(successResponse => {if (successResponse.data.code === 200) {//验证码输入正确,this.active++;this.$notify({title: '成功',message: '验证码匹配正确!',type: 'success',duration: 0,offset: 100,});}else if (successResponse.data.code === 400) {//验证码匹配错误返回对应信息this.$message.error(successResponse.data.message);}}).catch(failResponse => {})}}// 当面板为2时,则到了用户输入密码的时候, 将密码传入后台if (this.active === 2) {//在发送密码之前,先校验一下是否输入了,不能让用户不小心输入了空密码if (this.passwordForm.password === '') {this.$notify({title: '警告',message: '警告, 新密码未输入',type: 'warning',offset: 100,});this.$refs.password.focus();}else {let password_md5 = this.$md5(this.passwordForm.password);this.$axios.post('/resetPassword', {password: password_md5,username: this.username,}).then(successResponse => {if (successResponse.data.code === 200) {//密码修改成功this.$notify({title: '成功',message: '该账号密码修改正确!',type: 'success',duration: 0,offset: 100,});let path = this.$route.query.redirect;this.$router.replace({path: path === '/' || path === undefined ? '/login' : path})}else if (successResponse.data.code === 400) {//修改密码失败,返回对应信息this.$message.error(successResponse.data.message);}}).catch(failResponse => {})}}},post(){console.log(this.Form.username)console.log(this.Form.email)this.$axios.post('/resetPassword', {username: this.Form.username,email: this.Form.email,}).then(successResponse => {if (successResponse.data.code === 200) {// 如果返回的结果正确,那么需要发送邮件到对应的用户邮箱中,用户自己登录邮箱后找到对应的链接后才可以输入新密码this.$notify({title: '成功',message: '已向'+ this.Form.email + '发送验证码,请在5分钟之内修改密码,否则验证码失效',type: 'success',duration: 0,offset: 100});//跳转到下一个面板,并且将按钮恢复正常this.active++;this.disabled = false;//将用户username保存下来this.username = successResponse.data.result;}else if (successResponse.data.code === 400) {//如果用户名和密码匹配错误,那么显示错误信息,并让按钮重新可用this.$notify({title: '失败',message: successResponse.data.message + "未知错误!",type: 'error',duration: 0,offset: 100,});this.disabled = false;}}).catch(failResponse => {})},}
}
</script><style scoped>.resetPassword{background-image: url("../../assets/resetPassword_img.jpg");background-position: center;height: 100%;width: 100%;background-size: cover;position: fixed;}.container{border-radius: 15px;background-clip: padding-box;margin: 10% auto;width: 30%;padding: 25px 30px;background: #fff;border: 1px solid #eaeaea;box-shadow: 0 0 25px #cac6c6;opacity: 0.7;}.common_div{margin-top: 5%;}.user-container {width: 80%;background: #fff;}.action_button {width: 20%;margin-top: 3%;text-align: center;}</style>

代码解释:

  • el-steps: element组件中的步骤条,详细使用查看 element 步骤条。
  • Form: 表单数据对象。
  • v-if: 条件渲染,分步骤。
  • 其他函数详情注释。

界面效果展示

二、后端代码设计

1. JavaMail配置

要想在springboot中发送邮件,需要提供特定的依赖。首先是mail依赖,然后是lombok依赖,和mybatis-plus依赖,负责数据库字段和实体属性的映射。在pom.xml中添加依赖:

     <!-- 邮件依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--mybatis-plus自动的维护了mybatis以及mybatis-spring的依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.0</version></dependency>

2. QQ邮箱开启STMP授权


开启后会获得一个邮箱 授权码,这个授权码可以用记事本记录下来。

注:网易邮箱之前申请时候无法使用,推荐使用qq邮箱发送。

3. 配置applicaiton.yml文件

spring:mail:username: 填写你的qq邮箱password: 填写qq邮箱授权码host: smtp.qq.comprotocol: smtpdefault-encoding: UTF-8properties:mail.smtp.auth: truemail.smtp.starttls.enable: truemail.smtp.starttls.required: truemail.smtp.socketFactory.port: 465 #协议为SMTP是SSL端口号465mail.smtp.socketFactory.class: javax.net.ssl.SSLSocketFactorymail.smtp.socketFactory.fallback: false

4. 新建文件夹

config、controller、entity、mapper、result、service

其他可以忽略。

整个项目文件夹显示如下

解释:

  • config:存储相关的配置,如邮件配置,数据库配置,shiro配置等。
  • controller:控制器。
  • entity: 实体类
  • exception: 异常类,此处无需理会。
  • filter: 过滤器,此处无需理会.
  • mapper: 数据库基础接口,管理CRUD操作。
  • realm: shiro控制登录信息。
  • result:返回前端信息类。
  • service: 接口和实例层, 主要的业务逻辑层。
  • utils:一些封装的方法。
  • impl:实例。
  • service: 接口。

5. 邮件配置:

EmailConfig.java

package com.lkq.pet.config;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** @author GoldenRetriever* @time 2020/10/12 16:57* @description 邮箱配置*/
@Data
@Component
public class EmailConfig {/*** 发件人邮箱*/@Value("${spring.mail.username}")private String emailForm;
}

EmailService.java

package com.lkq.pet.service;/*** @author GoldenRetriever* @time 2020/10/12 16:59* @description 邮件服务接口,该实体用于用户重置密码*/
public interface EmailService {/*** 发送简单邮件* @param sendTo 收件人地址* @param title  邮件标题* @param content 邮件内容*/void sendSimpleMail(String sendTo, String title, String content);/*** 发送HTML邮件* @param sendTo 收件人地址* @param title 邮件标题* @param content 邮件内容*/void sendHtmlMail(String sendTo, String title, String content);
}

EmailServiceImpl.java

package com.lkq.pet.service.impl;import com.lkq.pet.config.EmailConfig;
import com.lkq.pet.service.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;import javax.mail.internet.MimeMessage;/*** @author GoldenRetriever* @time 2020/10/12 17:03* @description 邮件业务层实例,实现对应接口方法*/
@Service
public class EmailServiceImpl implements EmailService {@Autowiredprivate EmailConfig emailConfig;@Autowiredprivate JavaMailSender mailSender;/*** 发送简单邮件后端耗时明显更短,* @param sendTo 收件人地址* @param title  邮件标题* @param content 邮件内容*/@Overridepublic void sendSimpleMail(String sendTo, String title, String content) {SimpleMailMessage message = new SimpleMailMessage();message.setFrom(emailConfig.getEmailForm());message.setTo(sendTo);message.setSubject(title);message.setText(content);try{mailSender.send(message);}catch (Exception e) {e.printStackTrace();}}/*** 发送Html邮件时间将会变长,但是需求更多(暂时带有附件的功能未添加)* @param sendTo 收件人地址* @param title 邮件标题* @param content 邮件内容*/@Overridepublic void sendHtmlMail(String sendTo, String title, String content) {MimeMessage message = mailSender.createMimeMessage();try{//true表示需要创建一个multipart messageMimeMessageHelper helper = new MimeMessageHelper(message, true);helper.setFrom(emailConfig.getEmailForm());helper.setTo(sendTo);helper.setSubject(title);helper.setText(content, true);mailSender.send(message);}catch (Exception e){e.printStackTrace();}}
}

6. User相关类:User.java、UserMapper、UserService.java、UserServiceImpl.java

User对应实体,UserMapper连接数据库,UserService.java对应封装的接口方法,UserServiceImpl继承UserService,实现接口方法。

User.java

package com.lkq.pet.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.stereotype.Repository;import java.util.Date;/*** @author GoldenRetriever* @time 2020/10/5 20:39* @description 用户实体类*/
@Data
@TableName(value = "user")
@Repository
public class User {/*** 主键user_id,用户名,密码(存错加密密文),盐(加密盐),性别,真实姓名,生日,头像,邮箱,角色id, 用户状态*/@TableId(value = "user_id", type = IdType.AUTO)private Integer userId;@TableField(value = "username")private String username;@TableField(value = "password")private String password;@TableField(value = "salt")private String salt;@TableField(value = "gender")private int gender;@TableField(value = "real_name")private String realName;// 返回时间格式@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")@TableField(value = "birthday")private Date birthday;@TableField(value = "avatar")private String avatar;@TableField(value = "email")private String email;@TableField(value = "state")private int state;@TableField(exist = false)private String code;}

UserMapper.java

package com.lkq.pet.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lkq.pet.entity.User;/*** @author LKQ* @date 2021/3/29 9:16* @description*/
public interface UserMapper extends BaseMapper<User> {}

UserService.java


package com.lkq.pet.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lkq.pet.entity.Role;
import com.lkq.pet.entity.User;
import com.lkq.pet.result.Result;import java.util.List;
import java.util.Map;/*** @author GoldenRetriever* @time 2020/10/6 10:34* @description 继承mybatis-plus提供的IService接口,进一步封装CRUD,具体方法看官网*/
public interface UserService extends IService<User> {/*** 数据库中是否存在用户名* @param username 用户名* @return boolean*/boolean isExistUser(String username);/*** 通过用户名查找对应的邮箱号* @param username 用户名* @return email 邮箱号*/String findEmailByUsername(String username);/*** 通过用户名查找对应的用户id* @param username 用户名* @return id*/int findIdByUsername(String username);
}

UserServiceImpl.java

package com.lkq.pet.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lkq.pet.entity.*;
import com.lkq.pet.mapper.UserMapper;
import com.lkq.pet.result.Result;
import com.lkq.pet.result.ResultFactory;
import com.lkq.pet.service.*;
import com.lkq.pet.utils.Md5Utils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;import javax.annotation.Resource;
import java.util.*;/*** @author GoldenRetriever* @time 2020/10/6 10:35* @description UserService层实现UseService接口,继承了mybatis-plus提供的ServiceImpl类*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{@Resourceprivate UserService userService;@Resourceprivate ResetPasswordService resetPasswordService;@Resourceprivate EmailService emailService;/*** 检查想重置密码的用户和邮箱是否匹配,若匹配成功,则返回当前用户的相关信息* @param user 用户实体* @return Result信息*/@Overridepublic Result checkUserAndEmail(User user) {//获取前端传来的数据String username = user.getUsername();String email = user.getEmail();//将用户名中可能存在HTML编码转义username = HtmlUtils.htmlEscape(username);email = HtmlUtils.htmlEscape(email);if (isExistUser(username)) {//如果存在该用户,则查找该用户的邮箱及用户idString storeEmail = findEmailByUsername(username);int userId = findIdByUsername(username);if (email.equals(storeEmail)) {//用户名和邮箱匹配正确后,需要在表resetPassword中生成一条信息,保存验证码,修改次数等信息//生成一个6位的数字int num = (int) ((Math.random() * 9 + 1) * 100000);String code = String.valueOf(num);//当前时间作为修改密码的开始时间Date currentTime = new Date();//截至时间为开始时间延后5分钟Date deadline = new Date(currentTime.getTime() + 5*60*1000);int limitNum = 3;int isEffective = 1;if (resetPasswordService.isExistUserId(userId)) {//如果表中存在这一个用户的修改信息,那么只需要修改相关信息ResetPassword rp = resetPasswordService.getOneByUserId(userId);if (currentTime.after(new Date(rp.getCreateTime().getTime() + 24*60*60*1000))){//如果当前的时间在修改密码存储时间后一天,那么就判断过了冷却时间,该用户可以重新设置密码, 24小时60分钟60srp.setIsEffective(isEffective);rp.setResetNum(0);}if (rp.getResetNum() > rp.getLimitNum()) {//如果当前修改次数超过上限rp.setIsEffective(0);}//先判断用户是否能够修改密码, 值为1代表允许,否则不能修改if (rp.getIsEffective() == 1) {// 设置对应的数据rp.setCode(code);rp.setCreateTime(currentTime);rp.setDeadline(deadline);//重置次数+1int resetTimes = rp.getResetNum();resetTimes++;rp.setResetNum(resetTimes);rp.setLimitNum(limitNum);if (rp.getResetNum() > rp.getLimitNum()) {// 修改次数 > 限制次数rp.setIsEffective(0);}else {rp.setIsEffective(isEffective);}}else {return ResultFactory.buildFailResult("当日账号密码修改次数超过上限,请明天重试!");}try{resetPasswordService.updateById(rp);}catch (Exception e) {System.out.println(e);}}else {//表中不存在这个用户,那么需要重新添加一条新数据ResetPassword rp = new ResetPassword();rp.setUserId(userId);rp.setCode(code);rp.setCreateTime(currentTime);rp.setDeadline(deadline);rp.setIsEffective(isEffective);rp.setResetNum(0);rp.setLimitNum(limitNum);try{resetPasswordService.save(rp);}catch (Exception e) {System.out.println(e);}}//发送html邮件到对应的邮箱号String title = "重置密码-来自lkq宠物医院管理后台";String content = "<html>\n" +"<body>\n" +"<h3>hello! 忘记密码啦?!</h3>\n" +"<p>" + "用户" + username + ": 你好"+"<br/>" + "你正在lkq宠物医院平台进行重置密码操作<br/>" +"您本次重置密码的验证码为<br/>" +"<p style=\"font-size:24px; color: #409EFF\">" + code + "</p>" +"<br/>请在5分钟之内填写验证码"+"<br/>如果非本人操作,请忽略本邮件, 如有疑问,欢迎致信1242061129@qq.com" +"</P>" +"</body>\n" +"</html>\n";//发送Html邮件时间相对较长emailService.sendHtmlMail(email, title, content);return ResultFactory.buildSuccessResult(username);}return ResultFactory.buildFailResult("用户邮箱号输入错误,请重新输入");}return ResultFactory.buildFailResult("该用户未注册,请先注册账号");}/*** 检查验证码是否正确* @param code 验证码* @param username 用户username* @return Result信息*/@Overridepublic Result checkCode(String code, String username) {int userId = findIdByUsername(username);ResetPassword rp = resetPasswordService.getOneByUserId(userId);//获取当前时间Date currentTime = new Date();if (currentTime.after(rp.getDeadline())) {return ResultFactory.buildFailResult("验证时间已过,请刷新界面,从头开始重置密码!");}else {if (rp.getCode().equals(code)) {return ResultFactory.buildSuccessResult("验证码匹配正确", username);}return ResultFactory.buildFailResult("验证码匹配错误!");}}/*** 重置密码* @param password 输入密码的md5密文* @param username 用户名* @return Result信息*/@Overridepublic Result resetPassword(String username, String password) {int userId = findIdByUsername(username);User user = userService.getById(userId);//获取随机的16位长度盐String salt = Md5Utils.getSalt();user.setSalt(salt);//md5加密后的密码和随机生成的salt拼接后再加密形成第二密文user.setPassword( Md5Utils.getSaltMd5(password, salt));//更新到数据库userService.updateById(user);return ResultFactory.buildSuccessResult("修改密码成功");}/*** 判断数据库中是否存在用户* @param username 用户名* @return true、false*/@Overridepublic boolean isExistUser(String username) {//mybatis-plus的条件构造器queryWrapperQueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);return userService.count(queryWrapper) > 0;}/*** 通过用户名查找对应的邮箱号* @param username 用户名* @return email 邮箱号*/@Overridepublic String findEmailByUsername(String username) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);//找到数据库中用户名和输入用户名相同的一条数据User user = userService.getOne(queryWrapper);//返回对应的邮箱return user.getEmail();}/*** 通过用户名查找对应的用户id* @param username 用户名* @return id*/@Overridepublic int findIdByUsername(String username) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("username", username);//找到数据库中用户名和输入用户名相同的一条数据User user = userService.getOne(queryWrapper);//返回对应的用户idreturn user.getUserId();}
}

7. ResetPassword相关类

ResetPassword实体类,ResetPasswordMapper 基础映射, ResetPasswordService接口方法,ResetPassImpl 实现接口方法。

ResetPassword.java

package com.lkq.pet.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.stereotype.Repository;import java.util.Date;/*** @author GoldenRetriever* @time 2020/10/12 19:53* @description 重置密码实体,对应resetPassword表*/
@Data
@TableName(value = "resetPassword")
@Repository
public class ResetPassword {/*** 主键id*/@TableId(value = "id", type = IdType.AUTO)private int id;/*** 重置用户密码的用户id*/@TableField(value = "user_id")private int userId;/*** 随机生成的16位验证码*/@TableField(value = "code")private String code;/*** 开始时间*/@TableField(value = "create_time")private Date createTime;/*** 截至时间*/@TableField(value = "deadline")private Date deadline;/*** 是否有效,若当前时间超出截至时间,则判定当前验证码无效,0代表无效,1代表有效*/@TableField(value = "is_effective")private int isEffective;/*** 重置次数,记录当前重置次数*/@TableField(value = "reset_num")private int resetNum;/*** 当日限定重置次数, 默认为3次*/@TableField(value = "limit_num")private int limitNum;}

ResetPasswordMapper.java

package com.lkq.pet.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lkq.pet.entity.ResetPassword;/*** @author LKQ* @date 2021/3/29 9:11* @description*/
public interface ResetPasswordMapper extends BaseMapper<ResetPassword> {}

ResetPasswordService.java

package com.lkq.pet.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.lkq.pet.entity.ResetPassword;/*** @author GoldenRetriever* @time 2020/10/12 22:18* @description 定义重置密码的接口类,*/
public interface ResetPasswordService extends IService<ResetPassword> {/*** 通过userId判断resetPassword表中该用户是否修改过密码,有没有数据* @param userId 用户id* @return boolean*/boolean isExistUserId(int userId);/*** 通过userId取出这一条数据* @param userId 用户id* @return resetPassword对象*/ResetPassword getOneByUserId(int userId);
}

ResetPassImpl.java

package com.lkq.pet.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lkq.pet.entity.ResetPassword;
import com.lkq.pet.mapper.ResetPasswordMapper;
import com.lkq.pet.service.ResetPasswordService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @author GoldenRetriever* @time 2020/10/12 22:25* @description 重置密码的实现类*/
@Service
public class ResetPassImpl extends ServiceImpl<ResetPasswordMapper, ResetPassword> implements ResetPasswordService {@Resourceprivate ResetPasswordService resetPasswordService;/*** 判断是否存在该用户修改的数据,* @param userId 用户id* @return boolean*/@Overridepublic boolean isExistUserId(int userId) {QueryWrapper<ResetPassword> queryWrapper = new QueryWrapper<>();queryWrapper.eq("user_Id", userId);return resetPasswordService.count(queryWrapper) > 0;}/*** 通过用户id获取这条数据* @param userId 用户id* @return ResetPassword实例*/@Overridepublic ResetPassword getOneByUserId(int userId) {QueryWrapper<ResetPassword> queryWrapper = new QueryWrapper<>();queryWrapper.eq("user_Id", userId);return resetPasswordService.getOne(queryWrapper);}
}

8. 数据库创建表格

  • user:存储用户信息,这里主要用到user_id。
  • resetpassword:根据上面实体创建表。

9. 编写控制器LoginController

package com.lkq.pet.controller;import com.lkq.pet.result.Result;
import com.lkq.pet.entity.User;
import com.lkq.pet.result.ResultFactory;
import com.lkq.pet.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @author GoldenRetriever* @time 2020/10/5 21:47* @description 后端登录控制器,处理前端请求*/
@RestController
@CrossOrigin(value = "http://localhost:8080", maxAge = 1800, allowedHeaders ="Content-Type")
public class LoginController {@Autowiredprivate UserServiceImpl userServiceImpl;@PostMapping("/api/resetPassword")public Result resetPassword(@RequestBody User user) {if (user.getUsername()!=null&& user.getEmail()!=null){//第一步,传来的是用户名和邮件,其他为空,则生成验证码并发送邮件return userServiceImpl.checkUserAndEmail(user);}if (user.getCode()!=null && user.getUsername()!=null) {//第二步,传来code和username,需要验证数据库中的code是否正确return userServiceImpl.checkCode(user.getCode(), user.getUsername());}if (user.getUsername()!=null && user.getPassword()!=null) {//最后,用户名和密码同时传过来,开始重置密码。return userServiceImpl.resetPassword(user.getUsername(), user.getPassword());}return ResultFactory.buildFailResult("未知错误");}}

@CrossOrigin注解:用来跨域。

10. Result类

这个类的作用是为了处理后端返回数据,项目文件夹:


Result.java

package com.lkq.pet.result;import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;/*** @author GoldenRetriever* @time 2020/9/18 21:46* @description 存储响应结果*/
@Data
@Component
@NoArgsConstructor
public class Result {/*** 响应码,结果信息,数据*/private int code;private String message;private Object result;Result(int code, String message, Object data) {this.code = code;this.message = message;this.result = data;}}

ResultCode.java

package com.lkq.pet.result;/*** @author GoldenRetriever* @time 2020/10/7 15:01* @description ResultCode类*/
public class ResultCode {/*** Http状态码, 200请求成功,400客户端请求语法错误,* 401请求要求用户的身份认证* 404服务器无法根据客户端的请求找到资源(网页),* 500服务器内部错误,无法完成请求*/static int SUCCESS = 200;static int FAIL = 400;public static int UNAUTHORIZED = 401;public static int NOTFOUND =404;public static int INTERNAL_SERVER_ERROR = 500;public int code;ResultCode(int code) {this.code = code;}}

ResultFactory.java

package com.lkq.pet.result;/*** @author GoldenRetriever* @time 2020/10/7 15:11* @description*/
public class ResultFactory {public static Result buildResult(int resultCode, String message, Object data) {return new Result(resultCode, message, data);}/*** 连接错误* @param message 错误信息* @return Result*/public static Result buildFailResult(String message) {return buildResult(ResultCode.FAIL, message, null);}/*** 连接成功* @param data 返回数据* @return Result*/public static Result buildSuccessResult(Object data) {return buildResult(ResultCode.SUCCESS, "成功", data);}/*** 操作成功* @param message 提示信息* @param data 传回数据* @return Result对象*/public static Result buildSuccessResult(String message, Object data) {return buildResult(ResultCode.SUCCESS, message, data);}
}

效果展示

一、登录界面

二、找回密码


这里从登录界面传来了用户名:最好的一天,且无法修改。

三、输入邮箱


下一步之后

四、输入验证码


网易邮箱收到验证码。

验证码在限定时间内匹配成功

五、修改密码

输入新密码后

数据库变化

其中214968就是发送的验证码

结尾

天空好想下雨,我好想住你隔壁!

查漏补缺

2021/7/29

针对留言中提出的UserServiceImpl类中resetPassword方法找不到引用类,原因是博主之前后端生成随机16位加密盐是手动写的方法,在之前的开发中能够成功。后来因为使用到shiro框架,于是就将这部分给优化了,调用官方提供的方法来生成随机盐。具体是先导入在该类头部导入两个包,然后修改resetPassword方法,应该就能够解决问题。

import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
     /*** 重置密码* @param password 输入密码的md5密文* @param username 用户名* @return Result信息*/@Overridepublic Result resetPassword(String username, String password) {int userId = findUserIdByUsername(username);try {User user = userService.getById(userId);//获取随机的16位长度盐String salt = new SecureRandomNumberGenerator().nextBytes().toString();user.setSalt(salt);int times = 2;//md5加密后的密码和随机生成的salt拼接后再加密形成第二密文user.setPassword(new SimpleHash("md5", password, salt, times).toString());//更新到数据库userService.updateById(user);}catch (Exception e) {e.printStackTrace();return ResultFactory.buildFailResult("未知错误");}return ResultFactory.buildSuccessResult("修改密码成功");}

查漏补缺2

2022/7/2

有小伙伴提出 qq 邮箱 SMTP服务器 的端口现在是587,在官网qq帮助中心中显示465/587,如果一个不能用,可以替换试试。

Vue + element + Springboot 通过邮箱找回密码相关推荐

  1. 基于SpringBoot实现邮箱找回密码

    基于邮箱发送验证码的方式 >>文章末尾有demo的git地址供下载参考 实现思路 用户点击忘记密码 用户输入用户名以及邮箱,点击获取验证码 后端校验用户名以及邮箱,正确后生成验证码 生成的 ...

  2. java 基于springboot邮箱找回密码功能

    一.主要内容 基于springboot实现密码找回功能. 二.邮箱找回密码的思想. 1.输入注册邮箱,点击获取验证码.会将验证码发送到邮箱. 2.用户进入邮箱,查看验证码. 3.用户输入验证码,输入新 ...

  3. SpringBoot实现通过邮箱找回密码功能

    养成习惯,先赞后看!!! 目录 1.前言 2.步骤 2.1导入依赖 2.2开启邮箱的SMTP服务 2.3配置application.yaml文件 2.4 编写逻辑的步骤 2.4.1创建pm_valid ...

  4. java邮箱找回密码_Spring实现简单的邮箱找回密码功能

    通过spring可以实现简单的邮箱找回密码的功能,在此做一下简单的笔记. 1.首先就是导入一些相关的jar包 2.加入配置文件,具体配置信息如下: encoding="UTF-8" ...

  5. java 邮箱找回密码_【JavaWeb】通过邮件找回密码

    前言 本文将介绍忘记密码时通过发送重置密码邮件找回密码的实现思路.整个实现过程中最重要的就是以下三点: 如何发送邮件到用户指定邮箱 邮件中的重置密码链接构成是怎么样的 验证重置密码链接的合法性(是否过 ...

  6. JavaWeb QQ邮箱找回密码

    我的上一篇博客,已经写了登录注册,接下来写QQ邮箱找回密码 首先:我们需要在 QQ邮箱设置中获取授权码,步骤如下 进入QQ邮箱---->设置---->账户---->开启服务:POP3 ...

  7. AspNetCore-MVC实战系列(二)之通过绑定邮箱找回密码

    AspNetCore - MVC实战系列目录 . 爱留图网站诞生 . git源码:https://github.com/shenniubuxing3/LovePicture.Web . AspNetC ...

  8. js加mysql写邮箱找回密码_邮箱找回密码实现

    邮箱找回密码实现 思路: 点击邮箱找回:前端给后端服务器发送请求 get:http://127.0.0.1:8000/api/v1/email?email=7777777777@qq.com 后端发送 ...

  9. laravel邮箱找回密码

    本文目录 一.邮箱找回密码 1.1 创建中间件 1.2 路由 1.3 控制器 1.4 测试效果 一.邮箱找回密码 1.1 创建中间件 创建一个验证邮箱验证码是否正确的中间件: 运行命令:php art ...

  10. Java实现邮箱找回密码

    通过邮件找回密码功能的实现 1.最近开发一个系统,有个需求就是,忘记密码后通过邮箱找回.现在的系统在注册的时候都会强制输入邮箱,其一目的就是 通过邮件绑定找回,可以进行密码找回.通过java发送邮件的 ...

最新文章

  1. 如何不用重启在CentOS 7/ RHEL 7虚拟机中添加一块新硬盘
  2. maven的依赖范围_Maven依赖范围
  3. selenium linux 谷歌浏览器,在Linux平台上无法通过Jenkins中的Selenium启动Chrome浏览器...
  4. 给C盘减减肥,让你电脑飞一般速度!
  5. matlab 锐化降噪,matlab 图形锐化 滤波
  6. 电脑时代计算机应用,【2017年整理】计算机应用与发展的神话时代.docx
  7. Spring4.x()--Jdbc事务-XML
  8. 一加10 Pro的性能配置还是非常不错的
  9. 大数据可视化技术的作用有哪些
  10. 电力拖动自动控制系统 华南理工大学期末重点 阮毅 长篇思维导图
  11. 《nik collection》怎么安装
  12. 蓝桥web模拟赛:时间管理大师
  13. 同步异步+阻塞非阻塞-二述
  14. 到底什么是 路由器(router)、交换机(switch)
  15. Django新建项目(Linux操作系统)
  16. ERA5-Land 逐小时数据_累积值(如辐射数据)处理的注意事项
  17. 进程、线程、协程之间的关系
  18. 快速实现自定义控件开关按钮
  19. 身份证号要是能修改就好了,不怕被骗子骗了
  20. CAD专用卸载工具,完美彻底卸载清除干净cad各种残留注册表和文件。

热门文章

  1. C++ stack的使用及模拟实现
  2. 树莓派和电脑之间串口通信
  3. java 自由落体for语句_JAVA 自由落体和平抛无能运动
  4. 未来人工智能发展趋势
  5. 排队论及排队系统优化
  6. ArcGIS实验教程——实验二十八:统计图表(饼状图、柱状图)制作
  7. Android Studio属性动画,Android Studio 三种方式建立动画效果
  8. 17个支持图片外链的免费相册
  9. IDEA在当前工作空间导入项目
  10. Android Studio插件整理