目录
2.3.2 getForgetPage方法和重置密码的resetPassword方法
1. 一些废话
这一部分足足写了我大半天,还是太菜了,主要是对前端不熟悉,导致传参出现了很大问题,严重拖慢了进度,但是好在最后做到了比较完美的效果,前端的界面是这样的:
不同于现在主流的先获取邮箱或者手机号,然后再修改密码,这个页面把所有的功能全部杂糅在一起了,然而我又不懂前端,懒得改页面,于是就只能在后端下下功夫了。
2. 三层架构
2.1 dao层
dao层的处理,主要就是增加一个方法依据邮箱查找用户,这一部分比较简单,就不多赘述了。
2.2 service层
service层,这一层主要包含了获取验证码(包含发邮件),以及重置密码两个功能。
2.2.1 获取验证码
下面这个是获取验证码,要注意的是,这里map里我加入了有效时间expirationTime和冷却时间cd这两个key,具体作用在前端处理的时候我会详细叙述,值得一提的是,这里集成了我上篇文章讲到的把发邮件交给kafka消息队列,有兴趣可以看一看:https://blog.csdn.net/BigCanda/article/details/129890574。
public Map<String, Object> getCode(String email) {
Map<String,Object> map = new HashMap<>();
if (StringUtils.isBlank(email)) {
map.put("emailMsg","邮箱不能为空!");
return map;
}
if (email.length() <= 11) {
map.put("emailMsg","邮箱格式错误!");
return map;
}
User user = userMapper.selectByEmail(email);
if (user == null) {
map.put("emailMsg","该邮箱未注册!");
return map;
}
if (user.getStatus() == 0){
map.put("emailMsg","该邮箱还未激活!");
return map;
}
String code = CommunityUtil.generateUUID().substring(0, 6);
map.put("code", code);
// 激活邮件
Context context = new Context();
context.setVariable("email",user.getEmail());
context.setVariable("code",code);
String content = templateEngine.process("mail/forget",context);
Mail mail = new Mail();
mail.setTo(user.getEmail());
mail.setContent(content);
mail.setSubject("修改牛客网密码");
try {
mailProducer.fireMail(mail);
} catch (Exception e) {
map.put("emailMsg", "发送失败,请稍后再试!");
return map;
}
map.put("expirationTime", LocalDateTime.now().plusMinutes(5));
map.put("cd", LocalDateTime.now().plusMinutes(1));
return map;
}
2.2.2 重置密码
重置密码比较简单,主要是用map返回一些错误信息:
public Map<String, Object> resetPassword(String password, String code) {
Map<String, Object> map = new HashMap<>();
if (code.isEmpty()) {
map.put("codeMsg", "验证码不能为空!");
return map;
}
if (password.isEmpty()) {
map.put("passwordMsg", "密码不能为空!");
return map;
}
if (password.length() < 10) {
map.put("passwordMsg", "密码长度不能小于10位!");
return map;
}
return map;
}
2.3 controller层
这一层是主要包含两个方法:getCode和resetPassword方法,我在妥协使用get方法获取验证码和使用post获取验证码之间纠结了很久,好在最后还是使用post比较完美的解决了问题。
2.3.1 getCode方法
@RequestMapping(path = "/user/getCode", method = RequestMethod.POST)
@ResponseBody
public String getCode(String email, HttpSession httpSession, HttpServletResponse httpServletResponse) {
if (httpSession.getAttribute("cd") != null && LocalDateTime.now().isBefore((LocalDateTime) httpSession.getAttribute("cd"))) {
return CommunityUtil.getJSONString(1, "您点击的太快了,休息一分钟吧!");
}
Map<String, Object> map = userService.getCode(email);
String code = null;
if (map.containsKey("emailMsg")) {
return CommunityUtil.getJSONString(1, map.get("emailMsg").toString());
}
if (map.containsKey("code")) {
code = (String) map.get("code");
}
if (code != null) {
httpSession.setAttribute("code", code);
httpSession.setAttribute("expirationTime",map.get("expirationTime"));
httpSession.setAttribute("cd",map.get("cd"));
Cookie cookie = new Cookie("email", email);
cookie.setMaxAge(300);
cookie.setPath(contextPath);
httpServletResponse.addCookie(cookie);
}
return CommunityUtil.getJSONString(0, "验证码发送成功,请注意查收!");
}
2.3.2 getForgetPage方法和重置密码的resetPassword方法
这里就基本可以看出来expirationTime和cd的作用了,就是限制获取验证码以及设定验证码有效时间。
@RequestMapping(path = "/user/forget",method = RequestMethod.GET)
public String getForgetPage() {
return "site/forget";
}
@RequestMapping(path = "/user/forget",method = RequestMethod.POST)
public String resetPassword(String password, String email, String code, HttpSession httpSession, Model model, HttpServletRequest httpServletRequest) {
if (email == null) {
model.addAttribute("emailMsg", "请输入邮箱!");
return "site/forget";
}
Cookie cookies[] = httpServletRequest.getCookies();
Cookie cookie = new Cookie("null", "null");
if (cookies.length != 0) {
for (Cookie cookie1 : cookies) {
if (cookie1.getName().equals("email")) {
cookie = cookie1;
break;
}
}
}
Map<String, Object> map = userService.resetPassword(password, code);
if (map.isEmpty())
{
if (LocalDateTime.now().isAfter((LocalDateTime) httpSession.getAttribute("expirationTime"))) {
model.addAttribute("codeMsg", "输入的验证码已过期,请重新获取验证码!");
model.addAttribute("email", email);
return "site/forget";
}
try {
if (httpSession.getAttribute("code").toString().equals(code)) {
if (!cookie.getValue().equals(email)) {
model.addAttribute("emailMsg", "验证码和邮箱不匹配,请重新获取验证码!");
return "site/forget";
}
userService.updatePasswordByEmail(email, password);
httpSession.removeAttribute("code");
httpSession.removeAttribute("expirationTime");
httpSession.removeAttribute("cd");
cookie.setMaxAge(0);
model.addAttribute("msg", "密码修改成功,请重新登录!");
model.addAttribute("target", "/login");
model.addAttribute("email", email);
model.addAttribute("username", userService.selectUserByEmail(email));
return "site/operate-result";
} else {
model.addAttribute("codeMsg", "验证码错误!");
model.addAttribute("email", email);
return "site/forget";
}
} catch (Exception e) {
model.addAttribute("codeMsg", "请先获取验证码!");
return "site/forget";
}
}
model.addAttribute("codeMsg", map.get("codeMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
model.addAttribute("email", email);
return "site/forget";
}
3. 前端页面的处理
3.1 html页面的处理
<!doctype html>
<html lang="en" xmlns:th="https://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" href="https://static.nowcoder.com/images/logo_87_87.png"/>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" th:href="@{/css/global.css}" />
<link rel="stylesheet" th:href="@{/css/login.css}" />
<title>牛客网-忘记密码</title>
</head>
<body>
<div class="nk-container">
<!-- 头部 -->
<header class="bg-dark sticky-top" th:replace="index::header">
<div class="container">
<!-- 导航 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<!-- logo -->
<a class="navbar-brand" href="#"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- 功能 -->
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="../index.html">首页</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link position-relative" href="letter.html">消息<span class="badge badge-danger">12</span></a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="register.html">注册</a>
</li>
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" href="login.html">登录</a>
</li>
<li class="nav-item ml-3 btn-group-vertical dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item text-center" href="profile.html">个人主页</a>
<a class="dropdown-item text-center" href="setting.html">账号设置</a>
<a class="dropdown-item text-center" href="login.html">退出登录</a>
<div class="dropdown-divider"></div>
<span class="dropdown-item text-center text-secondary">nowcoder</span>
</div>
</li>
</ul>
<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" action="search.html">
<input class="form-control mr-sm-2" type="search" aria-label="Search" />
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>
</div>
</nav>
</div>
</header>
<!-- 提示框 -->
<div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="hintModalLabel">提示</h5>
</div>
<div class="modal-body" id="hintBody">
</div>
</div>
</div>
</div>
<!-- 内容 -->
<div class="main">
<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
<form class="mt-5" method="post" th:action="@{/user/forget}">
<div class="form-group row">
<label for="your-email" class="col-sm-2 col-form-label text-right">邮箱:</label>
<div class="col-sm-10">
<input type="email"
th:class="|form-control ${emailMsg != null?'is-invalid' : ''}|"
th:value="${email != null ? email : ''}"
id="your-email" name="email" placeholder="请输入您的邮箱!" >
<div class="invalid-feedback " th:text="${emailMsg}">
</div>
</div>
</div>
<div class="form-group row mt-4">
<label for="verifycode" 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="verifycode" name = "code" placeholder="请输入验证码!">
<div class="invalid-feedback" th:text="${codeMsg}">
验证码不正确!
</div>
</div>
<div class="col-sm-4">
<button type="button" id="emailBtn" class="btn btn-info form-control" >获取验证码</button>
</div>
</div>
<div class="form-group row mt-4">
<label for="your-password" class="col-sm-2 col-form-label text-right">新密码:</label>
<div class="col-sm-10">
<input type="password" name="password"
th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
class="form-control" id="your-password" placeholder="请输入新的密码!">
<div class="invalid-feedback" th:text="${passwordMsg}">
密码长度不能小于8位!
</div>
</div>
</div>
<div class="form-group row mt-4">
<div class="col-sm-2"></div>
<div class="col-sm-10 text-center">
<button type="submit" class="btn btn-info text-white form-control">重置密码</button>
</div>
</div>
</form>
</div>
</div>
<!-- 尾部 -->
<footer class="bg-dark">
<div class="container">
<div class="row">
<!-- 二维码 -->
<div class="col-4 qrcode">
<img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
</div>
<!-- 公司信息 -->
<div class="col-8 detail-info">
<div class="row">
<div class="col">
<ul class="nav">
<li class="nav-item">
<a class="nav-link text-light" href="#">关于我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">加入我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">意见反馈</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">企业服务</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">联系我们</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">免责声明</a>
</li>
<li class="nav-item">
<a class="nav-link text-light" href="#">友情链接</a>
</li>
</ul>
</div>
</div>
<div class="row">
<div class="col">
<ul class="nav btn-group-vertical company-info">
<li class="nav-item text-white-50">
公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
</li>
<li class="nav-item text-white-50">
联系方式:010-60728802(电话) admin@nowcoder.com
</li>
<li class="nav-item text-white-50">
牛客科技©2018 All rights reserved
</li>
<li class="nav-item text-white-50">
京ICP备14055008号-4
<img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
京公网安备 11010502036488号
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</footer>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{/js/forget.js}"></script>
</body>
</html>
3.2 js方法的选择
起先我用的是get方法, 参考了下面这篇文章:
(24条消息) (仿牛客论坛项目)06 - 忘记密码_html忘记密码页面代码_每天都要学习的阿荣的博客-CSDN博客
因为根本没学过前端,于是就去文章里面偷了个这样的js函数:
<script>
function get_code() {
var email = document.getElementById("your-email");
// 点击该超链接会发送请求给浏览器
window.location.href = "getCode?email=" + email;
}
</script>
但是发现根本不行,上面的跳转根本跳转不了,地址栏里面的email值一直都是html对象,后来改了一下,变成了这样才能正常访问:
<script>
function get_code() {
var email = document.getElementById("your-email");
// 点击该超链接会发送请求给浏览器
window.location.href = "getCode?email=" + email.value;
}
</script>
但是这样做每次都要刷新页面,而且不安全,可以通过在地址栏一直输入链接获取验证码,而且还有更严重的缺陷:由于我把email存在cookie里面用作后续比对以及自动回填到新页面的邮箱栏上,防止一个邮箱收的验证码可以改另一个邮箱对应的账号的密码这种情况发生,然后在重新设置密码成功以后清除cookie,然而这样做加上使用get的后果就是,cookie失效之前都不能改另一个账号的密码。
上面说的可能有些抽象,举个很简单的例子,我改一个账号密码改到一半不想改了,想改另一个账号的密码,可是在cookie过期之前我怎么也不能修改邮箱,每次点重置密码都会把填好的新邮箱自动改掉并且提示你修改失败,这就挺反人类的。
然后我恶补前端知识,到处c,终于写出了异步的post方法,很好的解决了上面的问题:
$(function(){
$("#emailBtn").click(get_code);
});
function get_code() {
var email = $("#your-email").val();
if (email != "") {
$("#emailBtn").click(time(this));
}
$.post(
CONTEXT_PATH + "/user/getCode",
{"email":email},
function data(data) {
data = $.parseJSON(data);
$("#hintBody").text(data.msg);
$("#hintModal").modal("show");
setTimeout(function(){
$("#hintModal").modal("hide");
}, 2000);
}
);
$("#hintModal").modal("show");
setTimeout(function(){
$("#hintModal").modal("hide");
}, 2000);
}
var wait=60;
function time(obj) {
if (wait == 0) {
obj.removeAttribute("disabled");
obj.innerHTML="获取验证码";
wait = 60;
} else {
obj.setAttribute("disabled", true);
obj.innerHTML=wait+"秒后重新发送";
wait--;
setTimeout(function() {
time(obj)
},
1000)
}
}
值得注意的是我把前端的验证码发送冷却也加上了,当然也是偷的,文章链接:
(23条消息) 点击发送短信验证码按钮后,倒计时60秒_点击发送验证码按钮优化 倒计时会在点击按钮后延迟几秒_程序员老牛了laoliu的博客-CSDN博客
4. 测试
4.1 一般测试
首先不输入邮箱:
然后输入邮箱获取验证码:
成功收到邮件:
刷新页面重复获取:
输入验证码,但是不输入密码:
输入不合规的密码:
操作超时:
正确操作,跳转到成功页面:
4.2 非常规测试:
同时改两个账号的密码,首先获取一个账号:
再获取另一个账号:
然后用一个邮箱收到的验证码填另一个的:
同理,用一个邮箱收到验证码但是不改密码,把邮箱改了,再点击重置密码密码也是一样的效果。
5. 后记
这篇文章主要是做学习记录用的,主要记录我当时的想法和心态,写得很长和啰嗦,大佬轻喷。