业务需求:用户输入账号和邮箱,向后台发送获取验证码的请求,通过验证码,用户可以重置自己的密码
步骤:
- 验证用户:用户输入账户和邮箱,后台验证用户是否存在
- 处理验证码:通过用户验证,生成验证码,将邮箱和验证码作为k-v存储到redis中,并将验证码发送都用户邮箱
- 判断验证码:用户输入验证码,后台检测验证码是否正确
效果展示:
代码展示:
后台部分
/**验证用户信息并发送验证码**
//service
/**
* 通过用户名和邮箱查询用户
* @param userName 用户名
* @param Email 邮箱
* @return 用户对象信息
*/
public SysUser selectUserByUserNameAndEmail(String userName, String Email);
/**
* 通过用户有名和邮箱查询用户
* @param userName 用户名
* @param email 邮箱
* @return 用户对象信息
*/
@Override
public SysUser selectUserByUserNameAndEmail(String userName, String email) {
return userMapper.selectUserByUserNameAndEmail(userName,email);
}
//mapper
/**
* 通过用户有名和邮箱查询用户
* @param userName 用户名
* @param email 邮箱
* @return 用户对象信息
*/
public SysUser selectUserByUserNameAndEmail(@Param("userName") String userName, @Param("email") String email);
<--mapper.xml-->
<select id="selectUserByUserNameAndEmail" resultMap="SysUserResult">
select * from sys_user where u.user_name = #{userName} and u.email = #{email}
</select>
//controller
/**
* 验证邮箱并发送验证码
*/
@GetMapping("/verifyUserAndSendCode/{userName}/{email}")
public AjaxResult verifyUserAndSendCode(@PathVariable("userName") String userName,
@PathVariable("email") String email) throws Exception {
SysUser user = userService.selectUserByUserNameAndEmail(userName, email);
//验证账户是否存在
if (user == null){
return AjaxResult.error("账户或邮箱错误!");
} else if(user.getStatus().equals("1")){
return AjaxResult.error("该用户已停用,无法重置密码!");
}else{
//key邮箱号,value验证码
String code = redisTemplate.opsForValue().get(email); //需要注入redisTemplate<String,String>
//从redis中获取验证码,取到返回success
if (!StringUtils.isEmpty(code)){
return AjaxResult.success();
}
//获取不到,生成随机验证码
code = GenPassWordUtil.getPassWordOne(6);
//调用service方法,通过邮箱服务发送验证码
String result = sendEmailService.sendAuthCode(email, code);
//生成的验证码放在redis中,设置有效时间:5分钟
if (result.equals("Success")){
redisTemplate.opsForValue().set(email,code,5, TimeUnit.MINUTES);
return AjaxResult.success("验证码发送成功");
} else {
return AjaxResult.error("邮件发送出错");
}
}
}
/**判断验证码并重置密码**
//service
/**
* 验证用户输入的验证码是否正确
* @param toEmail 发送的用户邮箱
* @param code 验证码
* @return
*/
public boolean checkAuthCode(String toEmail,String code){
String redisCode = redisTemplate.opsForValue().get(toEmail);
return redisCode != null && redisCode.equals(code);
}
/**
* 重置用户密码
*
* @param user 用户信息
* @return 结果
*/
public int resetPwd(SysUser user);
/**
* 重置用户密码
*
* @param user 用户信息
* @return 结果
*/
@Override
public int resetPwd(SysUser user)
{
return userMapper.updateUser(user);
}
//mapper
/**
* 修改用户信息
*
* @param user 用户信息
* @return 结果
*/
public int updateUser(SysUser user);
<!--mapper.xml-->
<update id="updateUser" parameterType="SysUser">
update sys_user
<set>
<if test="password != null and password != ''">password = #{password},</if>
</set>
where user_id = #{userId}
</update>
//controller
/**
* 判断验证码并重置密码
*/
@GetMapping("/checkAuthCode/{userName}/{email}/{authCode}")
public AjaxResult checkAuthCode(@PathVariable("userName") String userName,
@PathVariable("email") String email,
@PathVariable("authCode") String authCode) throws Exception {
boolean checkAuthCode = sendEmailService.checkAuthCode(email, authCode);
if (checkAuthCode){
SysUser user = userService.selectUserByUserNameAndEmail(userName,email);
String newPassword = GenPassWordUtil.getPassWordOne(8);
user.setPassword(newPassword);
int i = userService.resetPwd(user);
System.out.println(i);
sendEmailService.sendEmail(user.getEmail(), user.getNickName(), newPassword);
return AjaxResult.success("密码已重置");
} else {
return AjaxResult.error("验证码错误");
}
}
使用的工具类
//生成密码工具类
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class GenPassWordUtil {
/**
* 生成随机密码生成方式一
* 密码字典 -> 随机获取字符
*
* @param len 生成密码长度(最短为4位)
* @return 随机密码
*/
public static String getPassWordOne(int len) {
// 如果len 小于等于3
if (len <= 3) {
throw new RuntimeException("密码的位数不能小于3位!");
}
//生成的随机数
int i;
//生成的密码的长度
int count = 0;
// 密码字典
char[] str = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
// 大写字母密码字典
List<Character> bigStrList = Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z');
Set<Character> bigStrSet = new HashSet<>(bigStrList);
// 小写字母的密码字典
List<Character> upperStrList = Arrays.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
Set<Character> upperStrSet = new HashSet<>(upperStrList);
// 数字的密码字典
List<Character> numStrList = Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
Set<Character> numStrSet = new HashSet<>(numStrList);
StringBuilder builder = new StringBuilder();
Random r = new Random();
boolean isContainBigChar = false;
boolean isContainUpperChar = false;
boolean isContainNumChar = false;
while (count < len - 3) {
//生成 0 ~ 密码字典-1之间的随机数
i = r.nextInt(str.length);
builder.append(str[i]);
count++;
if (!isContainBigChar && bigStrSet.contains(str[i])) {
isContainBigChar = true;
}
if (!isContainUpperChar && upperStrSet.contains(str[i])) {
isContainUpperChar = true;
}
if (!isContainNumChar && numStrSet.contains(str[i])) {
isContainNumChar = true;
}
}
// 如果不存在的,则加,确保一定会存在数字,大写字母,小写字母
if (!isContainBigChar) {
builder.append(bigStrList.get(r.nextInt(bigStrList.size())));
}
if (!isContainUpperChar) {
builder.append(upperStrList.get(r.nextInt(upperStrList.size())));
}
if (!isContainNumChar) {
builder.append(numStrList.get(r.nextInt(numStrList.size())));
}
while (builder.length() < len) {
builder.append(str[r.nextInt(str.length)]);
}
return builder.toString();
}
}
//邮箱发送工具
public String sendAuthCode(String toEmail,String code) throws Exception{
String subject = "邮箱验证码"; //主题
String body = "<!DOCTYPE html>" //内容
+"<html>"
+"<head>"
+"<meta charset=\"utf-8\">"
+"<title>验证码</title>"
+"</head>"
+"<body>"
+" <p>您好:</p>"
+" <p> 您的验证码:<strong style=\"color:red;\"><i>"+code+"</i></strong></p>"
+" <p> 有效时间为5分钟,请尽快使用并不告诉他人</p>"
+" <p>支持</p>"
+"</body>"
+"</html>";
EmailUtils emailUtils = EmailUtils.entity(smtpHost, emailName, password, toEmail, null, subject, body, null);
emailUtils.send();
return "Success";
}
import com.sinosoft.common.config.EmailAuthenticator;
import javax.activation.CommandMap;
import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.activation.MailcapCommandMap;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.List;
import java.util.Properties;
/**
* 使用javax.mail发送邮件
*
* @author: gblfy
* @date 2021-06-22
*
* <p>
* 参数列表:
* 1.邮件服务器
* 2.发件人邮箱
* 3.发件人的授权密码
* 4.邮件主题
* 5.收件人,多个收件人以半角逗号分隔
* 6.抄送,多个抄送以半角逗号分隔
* 7.正文,可以用html格式的哟
* </p>
*/
public class EmailUtils {
private String smtpHost; // 邮件服务器地址
private String sendUserName; // 发件人的用户名
private String sendUserPass; // 发件人密码
private MimeMessage mimeMsg; // 邮件对象
private Multipart mp;// 附件添加的组件
private void init() {
// 创建一个密码验证器
EmailAuthenticator authenticator = null;
authenticator = new EmailAuthenticator(sendUserName, sendUserPass);
// 实例化Properties对象
Properties props = System.getProperties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.auth", "true"); // 需要身份验证
props.put("mail.smtp.starttls.enable", "false");
// 建立会话
Session session = Session.getDefaultInstance(props, authenticator);
// 置true可以在控制台(console)上看到发送邮件的过程
session.setDebug(true);
// 用session对象来创建并初始化邮件对象
mimeMsg = new MimeMessage(session);
// 生成附件组件的实例
mp = new MimeMultipart();
}
private EmailUtils(String smtpHost, String sendUserName, String sendUserPass, String to, String cc, String mailSubject,
String mailBody, List<String> attachments) {
this.smtpHost = smtpHost;
this.sendUserName = sendUserName;
this.sendUserPass = sendUserPass;
init();
setFrom(sendUserName);
setTo(to);
setCC(cc);
setBody(mailBody);
setSubject(mailSubject);
if (attachments != null) {
for (String attachment : attachments) {
addFileAffix(attachment);
}
}
}
/**
* 邮件实体
*
* @param smtpHost 邮件服务器地址
* @param sendUserName 发件邮件地址
* @param sendUserPass 发件邮箱密码
* @param to 收件人,多个邮箱地址以半角逗号分隔
* @param cc 抄送,多个邮箱地址以半角逗号分隔
* @param mailSubject 邮件主题
* @param mailBody 邮件正文
* @param attachments 附件路径
* @return
*/
public static EmailUtils entity(String smtpHost, String sendUserName, String sendUserPass, String to, String cc,
String mailSubject, String mailBody, List<String> attachments) {
return new EmailUtils(smtpHost, sendUserName, sendUserPass, to, cc, mailSubject, mailBody, attachments);
}
/**
* 设置邮件主题
*
* @param mailSubject
* @return
*/
private boolean setSubject(String mailSubject) {
try {
mimeMsg.setSubject(mailSubject);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置邮件内容,并设置其为文本格式或HTML文件格式,编码方式为UTF-8
*
* @param mailBody
* @return
*/
private boolean setBody(String mailBody) {
try {
BodyPart bp = new MimeBodyPart();
bp.setContent("<meta http-equiv=Content-Type content=text/html; charset=UTF-8>" + mailBody,
"text/html;charset=UTF-8");
// 在组件上添加邮件文本
mp.addBodyPart(bp);
} catch (Exception e) {
System.err.println("设置邮件正文时发生错误!" + e);
return false;
}
return true;
}
/**
* 添加一个附件
*
* @param filename 邮件附件的地址,只能是本机地址而不能是网络地址,否则抛出异常
* @return
*/
public boolean addFileAffix(String filename) {
try {
if (filename != null && filename.length() > 0) {
BodyPart bp = new MimeBodyPart();
FileDataSource fileds = new FileDataSource(filename);
bp.setDataHandler(new DataHandler(fileds));
bp.setFileName(MimeUtility.encodeText(fileds.getName(), "utf-8", null)); // 解决附件名称乱码
mp.addBodyPart(bp);// 添加附件
}
} catch (Exception e) {
System.err.println("增加邮件附件:" + filename + "发生错误!" + e);
return false;
}
return true;
}
/**
* 设置发件人地址
*
* @param from 发件人地址
* @return
*/
private boolean setFrom(String from) {
try {
mimeMsg.setFrom(new InternetAddress(from));
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置收件人地址
*
* @param to 收件人的地址
* @return
*/
private boolean setTo(String to) {
if (to == null)
return false;
try {
mimeMsg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
} catch (Exception e) {
return false;
}
return true;
}
/**
* 设置抄送
*
* @param cc
* @return
*/
private boolean setCC(String cc) {
if (cc == null) {
return false;
}
try {
mimeMsg.setRecipients(Message.RecipientType.CC, InternetAddress.parse(cc));
} catch (Exception e) {
return false;
}
return true;
}
/**
* no object DCH for MIME type multipart/mixed报错解决
*/
private void solveError() {
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap(
"multipart/*;; x-java-content-handler=com.sun.mail.handlers.multipart_mixed; x-java-fallback-entry=true");
mc.addMailcap("message/rfc822;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);
Thread.currentThread().setContextClassLoader(EmailUtils.class.getClassLoader());
}
/**
* 发送邮件
*
* @return
*/
public boolean send() throws Exception {
mimeMsg.setContent(mp);
mimeMsg.saveChanges();
System.out.println("正在发送邮件....");
solveError();
Transport.send(mimeMsg);
System.out.println("发送邮件成功!");
return true;
}
}
前端部分
//js
// 验证账户信息和发送验证码
export function verifyUserAndSendCode(username,email) {
return request({
url: '/system/user/verifyUserAndSendCode/' + username +"/" + email,
method: 'get'
})
}
// 验证用户输入验证码
export function checkAuthCode(username,email,code) {
return request({
url: '/system/user/checkAuthCode/' + username + "/" + email + "/" + code,
method: 'get'
})
}
//vue
/** 获取验证码 */
sendCode: function(){
this.$refs["resetPassForm"].validate((valid) =>{
if (valid){
var userName = this.resetPassForm.username;
var email = this.resetPassForm.email;
console.log(userName);
console.log(email);
verifyUserAndSendCode(userName,email).then((response) =>{
this.msgSuccess("验证码发送成功")
})
if (this.canClick) return
this.canClick = true
this.btnText = this.totalTime + 's后重新发送'
let clock = window.setInterval(()=> {
this.totalTime--
this.btnText = this.totalTime + 's后重新发送'
if (this.totalTime < 0){
window.clearInterval(clock)
this.btnText = '重新发送验证码'
this.totalTime = 300
this.canClick = false
}
},1000)
}
})
},
/** 取消 */
cancel(){
this.resetPasswordDialog = false;
},
/** 重置密码表单重置 */
reset(){
this.resetPassForm ={
username:undefined,
email:undefined,
code:undefined,
};
this.resetForm("resetPassForm");
},
/** 重置密码确认按钮 */
submitForm: function(){
var username = this.resetPassForm.username;
var email = this.resetPassForm.email;
var code = this.resetPassForm.code;
console.log(username);
console.log(email);
console.log(code);
if (code == null) {
this.msgError("请输入用户信息!");
this.resetPasswordDialog = true;
} else {
checkAuthCode(username,email,code).then((response) => {
this.resetPasswordDialog = false;
this.msgSuccess("密码已重置,请邮箱查收!");
})
}
},