增加了找回密码功能
This commit is contained in:
parent
197c32aff6
commit
78ee5ed462
@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
165
src/main/resources/static/js/reset-pwd.js
Normal file
165
src/main/resources/static/js/reset-pwd.js
Normal 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)
|
||||
}
|
||||
};
|
||||
}
|
@ -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">
|
||||
|
106
src/main/resources/templates/site/reset-pwd.html
Normal file
106
src/main/resources/templates/site/reset-pwd.html
Normal 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">重置 密码</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>
|
Loading…
x
Reference in New Issue
Block a user