增加了找回密码功能

This commit is contained in:
sinjar.chen 2021-07-07 21:58:56 +08:00
parent 197c32aff6
commit 78ee5ed462
6 changed files with 469 additions and 1 deletions

View File

@ -23,6 +23,8 @@ import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -64,6 +66,14 @@ public class LoginController implements CommunityConstant {
return "site/login";
}
/**
* 进入重置密码界面
*/
@GetMapping("/resetPwd")
public String getResetPwdPage() {
return "site/reset-pwd";
}
/**
* 注册用户
* @param model
@ -144,6 +154,27 @@ public class LoginController implements CommunityConstant {
}
}
/**
* 验证用户输入的图片验证码是否和redis中存入的是否相等
*
* @param kaptchaOwner cookie 中取出的 kaptchaOwner
* @param checkCode 用户输入的图片验证码
* @return 失败则返回原因, 验证成功返回 "",
*/
private String checkKaptchaCode(String kaptchaOwner, String checkCode) {
if (StringUtils.isBlank(checkCode)) {
return "未发现输入的图片验证码";
}
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
String kaptchaValue = (String) redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isBlank(kaptchaValue)) {
return "图片验证码过期";
} else if (!kaptchaValue.equalsIgnoreCase(checkCode)) {
return "图片验证码错误";
}
return "";
}
/**
* 用户登录
* @param username 用户名
@ -206,4 +237,89 @@ public class LoginController implements CommunityConstant {
return "redirect:/login";
}
/**
* 重置密码
*/
@PostMapping("/resetPwd")
@ResponseBody
public Map<String, Object> resetPwd(@RequestParam("username") String username,
@RequestParam("password") String password,
@RequestParam("emailVerifyCode") String emailVerifyCode,
@RequestParam("kaptchaCode") String kaptcha,
Model model,
@CookieValue("kaptchaOwner") String kaptchaOwner) {
Map<String, Object> map = new HashMap<>(4);
// 检查图片验证码
String kaptchaCheckRst = checkKaptchaCode(kaptchaOwner, kaptcha);
if (StringUtils.isNotBlank(kaptchaCheckRst)) {
map.put("status", "1");
map.put("errMsg", kaptchaCheckRst);
}
// 检查邮件验证码
String emailVerifyCodeCheckRst = checkRedisResetPwdEmailCode(username, emailVerifyCode);
if (StringUtils.isNotBlank(emailVerifyCodeCheckRst)) {
map.put("status", "1");
map.put("errMsg", emailVerifyCodeCheckRst);
}
// 执行重置密码操作
Map<String, Object> stringObjectMap = userService.doResetPwd(username, password);
String usernameMsg = (String) stringObjectMap.get("errMsg");
if (StringUtils.isBlank(usernameMsg)) {
map.put("status", "0");
map.put("msg", "重置密码成功!");
map.put("target", "/login");
}
return map;
}
/**
* 发送邮件验证码(用于重置密码)
*
* @param kaptchaOwner cookie 中取出的 kaptchaOwner
* @param kaptcha 用户输入的图片验证码
* @param username 用户输入的需要找回的账号
*/
@PostMapping("/sendEmailCodeForResetPwd")
@ResponseBody
public Map<String, Object> sendEmailCodeForResetPwd(Model model, @CookieValue("kaptchaOwner") String kaptchaOwner,
@RequestParam("kaptcha") String kaptcha,
@RequestParam("username") String username) {
Map<String, Object> map = new HashMap<>(3);
// 检查图片验证码
String kaptchaCheckRst = checkKaptchaCode(kaptchaOwner, kaptcha);
if (StringUtils.isNotBlank(kaptchaCheckRst)) {
map.put("status", "1");
map.put("errMsg", kaptchaCheckRst);
}
Map<String, Object> stringObjectMap = userService.doSendEmailCode4ResetPwd(username);
String usernameMsg = (String) stringObjectMap.get("errMsg");
if (StringUtils.isBlank(usernameMsg)) {
map.put("status", "0");
map.put("msg", "已经往您的邮箱发送了一封验证码邮件, 请查收!");
}
return map;
}
/**
* 检查 邮件 验证码
*
* @param username 用户名
* @param checkCode 用户输入的图片验证码
* @return 验证成功 返回"", 失败则返回原因
*/
private String checkRedisResetPwdEmailCode(String username, String checkCode) {
if (StringUtils.isBlank(checkCode)) {
return "未发现输入的邮件验证码";
}
final String redisKey = "EmailCode4ResetPwd:" + username;
String emailVerifyCodeInRedis = (String) redisTemplate.opsForValue().get(redisKey);
if (StringUtils.isBlank(emailVerifyCodeInRedis)) {
return "邮件验证码已过期";
} else if (!emailVerifyCodeInRedis.equalsIgnoreCase(checkCode)) {
return "邮件验证码错误";
}
return "";
}
}

View File

@ -318,4 +318,69 @@ public class UserService implements CommunityConstant {
return list;
}
/**
* 发送邮箱验证码
* @param account 账户名, 目前是用户名
*
* @return Map<String, Object> 返回错误提示消息如果返回的 map 为空则说明发送验证码成功
*/
public Map<String, Object> doSendEmailCode4ResetPwd(String account) {
Map<String, Object> map = new HashMap<>(2);
User user = userMapper.selectByName(account);
if (user == null) {
map.put("errMsg", "未发现账号");
return map;
}
final String email = user.getEmail();
if (StringUtils.isBlank(email)) {
map.put("errMsg", "该账号未绑定邮箱");
return map;
}
// 生成6位验证码
String randomCode = CommunityUtil.getRandomCode(6);
// 给注册用户发送激活邮件
Context context = new Context();
context.setVariable("email", "您的验证码是 " + randomCode);
// http://localhost:8080/echo/activation/用户id/激活码
String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
context.setVariable("url", url);
String content = templateEngine.process("/mail/activation", context);
mailClient.sendMail(email,"重置 Echo 账号密码", content);
final String redisKey = "EmailCode4ResetPwd:" + account;
redisTemplate.opsForValue().set(redisKey, randomCode, 600, TimeUnit.SECONDS);
return map;
}
/**
* 发送邮箱验证码
* @param account 账户名, 目前是用户名
*
* @return Map<String, Object> 返回错误提示消息如果返回的 map 为空则说明发送验证码成功
*/
public Map<String, Object> doResetPwd(String account, String password) {
Map<String, Object> map = new HashMap<>(2);
if (StringUtils.isBlank(password)) {
map.put("errMsg", "密码不能为空");
return map;
}
User user = userMapper.selectByName(account);
if (user == null) {
map.put("errMsg", "未发现账号");
return map;
}
final String passwordEncode = CommunityUtil.md5(password + user.getSalt());
int i = userMapper.updatePassword(user.getId(), passwordEncode);
if (i <= 0) {
map.put("errMsg", "修改数据库密码错误");
} else {
clearCache(user.getId());
}
return map;
}
}

View File

@ -1,7 +1,9 @@
package com.greate.community.util;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.util.DigestUtils;
import java.util.HashMap;
@ -69,6 +71,20 @@ public class CommunityUtil {
return json.toJSONString();
}
/**
* 生成指定位数的数字随机数, 最高不超过 9
*
* @param length
* @return
*/
public static String getRandomCode(int length) {
Validate.isTrue(length <= 9 && length > 0, "生成数字随机数长度范围应该在 1~9 内, 参数 length : %s", length);
int floor = (int) Math.pow(10, length - 1);
int codeNum = RandomUtils.nextInt(floor, floor * 10);
return Integer.toString(codeNum);
}
/**
* 测试
* @param args

View File

@ -0,0 +1,165 @@
var CONTEXT_PATH = "";
//V2 static
String.format = function () {
if (arguments.length == 0) {
return null;
}
let str = arguments[0];
for (let i = 1; i < arguments.length; i++) {
let re = new RegExp('\\{' + i + '\\}', 'gm');
str = str.replace(re, arguments[i]);
}
return str;
};
$.fn.parseForm=function(){
let serializeObj={};
let array=this.serializeArray();
let str=this.serialize();
$(array).each(function(){
if(serializeObj[this.name]){
if($.isArray(serializeObj[this.name])){
serializeObj[this.name].push(this.value);
}else{
serializeObj[this.name]=[serializeObj[this.name],this.value];
}
}else{
serializeObj[this.name]=this.value;
}
});
return serializeObj;
};
$(function(){
$("#resetPwd").click(function(){
if (check_data()) {
$.ajax({
type : "post",
url : '/resetPwd',
data : $('#resetPwdForm').parseForm(),
success : function (result) {
if (result) {
if (result.status === '1') {
console.log('重置密码失败');
console.log(result.errMsg);
} else if (result.status === '0') {
console.log('重置密码成功');
if (result.msg) {
alert(result.msg);
location.href='/login';
}
} else {
console.log(result);
}
}
}
})
} else {
$("input").focus(clear_error);
}
});
});
function check_data() {
if (!$("#password").val()) {
$("#password").addClass("is-invalid");
return false;
}
if (!$("#confirm-password").val().trim()) {
$("#confirm-password").addClass("is-invalid");
return false;
}
if (!$("#username").val()) {
$("#username").addClass("is-invalid");
return false;
}
if (!$("#kaptchaCode").val().trim()) {
$("#kaptchaCode").addClass("is-invalid");
return false;
}
if (!$("#emailVerifyCode").val().trim()) {
$("#emailVerifyCode").addClass("is-invalid");
return false;
}
var pwd1 = $("#password").val();
var pwd2 = $("#confirm-password").val();
if(pwd1 !== pwd2) {
$("#confirm-password").addClass("is-invalid");
return false;
}
return true;
}
function clear_error() {
$(this).removeClass("is-invalid");
}
function refresh_kaptcha() {
var path = CONTEXT_PATH + "/kaptcha?p=" + Math.random();
$("#kaptcha").attr("src", path);
}
// 发送邮箱验证码功能
function sendEmailCodeForResetPwd() {
var kaptchaCode = ($('#kaptchaCode').val() || '').trim();
var username = ($('#username').val() || '').trim();
if (kaptchaCode && username) {
$.ajax({
type: 'POST',
url:"/sendEmailCodeForResetPwd",
data: {
username: username,
kaptcha: kaptchaCode
},
success:function(result){
if (result) {
if (result.status === '1') {
console.log('发送邮箱验证码失败');
alert(result.errMsg);
} else if (result.status === '0') {
console.log('发送邮箱验证码成功');
var div = $($('#hideDiv')[0]);
var clear = disableForAWhile(div, div);
alert(result.msg);
} else {
console.log(result);
}
}
}
});
} else {
console.log('请输入完整的请求数据')
}
}
// 倒计时
// hideDiv 和 secondTextDiv 选择器, 仅仅支持jquery选择器的第一个元素
function disableForAWhile(hideDiv, secondTextDiv, time, str, style) {
// 备份
let tmpHtml = hideDiv[0].outerHTML;
let secondHtml = secondTextDiv[0].outerHTML;
time = time || 60;
str = str || '重新发送({1})';
style = style || {'pointer-events': 'none', 'cursor': 'not-allowed'};
hideDiv.css(style);
// clear function
secondTextDiv.text(String.format(str, time--));
let clear = function (id) {
clearInterval(id);
hideDiv[0].outerHTML = tmpHtml;
secondTextDiv[0].outerHTML = secondHtml;
};
let innerId = setInterval(() => {
secondTextDiv.text(String.format(str, time--));
if (time <= 0) {
clear(innerId);
}
}, 1000);
return {
clear: () => {
clear(innerId)
}
};
}

View File

@ -57,7 +57,7 @@
<input type="checkbox" id="rememberMe" name="rememberMe"
th:checked="${rememberMe}">
<label class="form-check-label" for="rememberMe">记住我</label>
<a href="forget.html" class="text-danger float-right">忘记密码?</a>
<a href="resetPwd" class="text-danger float-right">忘记密码?</a>
</div>
</div>
<div class="form-group row mt-4">

View File

@ -0,0 +1,106 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="shortcut icon" th:href="@{/img/favicon.ico}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/global.css}"/>
<link rel="stylesheet" type="text/css" th:href="@{/css/login.css}"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
<title>Echo - 注册</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header"></header>
<!-- 内容 -->
<div class="main">
<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
<h3 class="text-center text-info border-bottom pb-3">重置&nbsp;&nbsp;密码</h3>
<form class="mt-5" id="resetPwdForm">
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label text-right">账号或邮箱:</label>
<div class="col-sm-10">
<input type="text" id="username"
th:class="|form-control ${usernameMsg!=null ? 'is-invalid' : ''}|"
th:value="${user!=null ? user.username : ''}"
name="username" placeholder="请输入您的账号或绑定的邮箱!" required>
<!--错误提示消息, 当 上面的 input class = is-invalid 时显示-->
<div class="invalid-feedback">
请输入请输入您的账号或绑定的邮箱
</div>
</div>
</div>
<div class="form-group row mt-4">
<label for="password" class="col-sm-2 col-form-label text-right">新密码:</label>
<div class="col-sm-10">
<input type="password" id="password"
th:class="|form-control ${passwordMsg!=null ? 'is-invalid' : ''}|"
th:value="${user!=null ? user.password : ''}"
name="password" placeholder="请输入新密码!" required>
<!--错误提示消息-->
<div class="invalid-feedback">
请输入新密码
</div>
</div>
</div>
<div class="form-group row mt-4">
<label for="confirm-password" class="col-sm-2 col-form-label text-right">确认新密码:</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="confirm-password"
th:value="${user!=null ? user.password : ''}"
placeholder="请再次输入新密码!" required>
<div class="invalid-feedback">
请确任两次密码是否匹配
</div>
</div>
</div>
<div class="form-group row mt-4">
<label for="kaptchaCode" class="col-sm-2 col-form-label text-right">验证码:</label>
<div class="col-sm-6">
<input type="text" th:class="|form-control ${codeMsg != null ? 'is-invalid' : ''}|"
id="kaptchaCode" name="kaptchaCode" placeholder="请输入验证码!">
<div class="invalid-feedback">
请输入图片验证码
</div>
</div>
<div class="col-sm-4">
<a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">
<img th:src="@{/kaptcha}" id = "kaptcha" style="width:100px;height:40px;" class="mr-2"/>
</a>
</div>
</div>
<div class="form-group row mt-4">
<label for="emailVerifyCode" class="col-sm-2 col-form-label text-right">邮箱验证码:</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="emailVerifyCode" name="emailVerifyCode" placeholder="请输入邮箱验证码!">
<div class="invalid-feedback">
请输入邮箱验证码
</div>
</div>
<div class="col-sm-4">
<a id="hideDiv" href="javascript:sendEmailCodeForResetPwd();" class="btn btn-info form-control">获取邮箱验证码</a>
</div>
</div>
<div class="form-group row mt-4">
<div class="col-sm-2"></div>
<div class="col-sm-10 text-center">
<button type="button" id="resetPwd" class="btn btn-info text-white form-control">重置密码</button>
</div>
</div>
</form>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark" th:replace="index::footer"></footer>
</div>
<script th:src="@{/js/jquery-3.1.0.min.js}"></script>
<script th:src="@{/js/popper.min.js}"></script>
<script th:src="@{/js/bootstrap.min.js}"></script>
<script th:src="@{/js/reset-pwd.js}"></script>
</body>
</html>