目录
设计思路
分析
需求:当用户输入的用户名存在,并且密码输错 3 次以上,就触发账号冻结功能(冻结时间自定义0)。
主要分为以下步骤:
- 对客户端传入的账号和密码进行非空校验。(防止恶意用户通过 Postman 等工具请求非法数据)
- 通过客户端传入的账号获取该用户的所有数据,进行非空校验。
- 判断该用户输错密码是否等于 3 次(通过用户表的 state 字段的数值进行判断),若等于 3 次,首先记录下该用户开始冻结的时间(通过用户表中的 createtime 字段记录,这个字段后面会用到),创建一个线程使用 wait 进行冷冻时间等待,当冷冻时间结束,唤醒线程,将用户输错密码次数修改为 0 (state = 0)。
- 若输错密码小于 3 次,则校验密码是否正确,若密码错误则 state + 1,若密码正确,则 state = 0。
- 若输错密码大于 3 次,则计算剩余冷却时间(剩余冷却时间 = 开始冻结的时间 + 冷冻的时间 - 当前时间),这个计算不难,不好处理的时间格式(代码注释中有详细解释),最后将剩余冷却时间反馈给客户端。
前后端交互接口
请求
POST /user/login
Content-Type: application/json
{
"username": username.val(),
"password": password.val()
}
响应
Ps:采用同一返回数据格式处理(“code:状态码,msg:信息,data:数据”)
HTTP/1.1 200 OK
Content-Type: application/json
{
code: 200,
msg: "",
data: 1
}
代码实现和详细注释
数据库设计
用户表如下:
create table userinfo(
id int primary key auto_increment,
username varchar(100) unique,
password varchar(65) not null,
photo varchar(500) default "img/default.jpg",
createtime timestamp default current_timestamp,
updatetime timestamp default current_timestamp,
`state` int default 0
) default charset 'utf8mb4';
实体类设计
用户实体类如下:
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private String photo;
//格式化时间处理(处理到秒是为了精确冻结时间)
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
private LocalDateTime createtime;
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss", timezone = "GMT+8")
private LocalDateTime updatetime;
private Integer state;
}
前后端交互
客户端开发
js代码如下:
function login() {
//非空校验
var username = jQuery("#username");
var password = jQuery("#password");
var inputCheckPassword = jQuery("#checkpassword");
if (username.val() == "") {
alert("请先输入用户名!");
username.focus();
return;
}
if (password.val() == "") {
alert("请先输入密码!");
password.focus();
return;
}
if(inputCheckPassword.val() == "") {
alert("请先输入验证码!");
inputCheckPassword.focus();
return;
}
//比较验证码
if(inputCheckPassword.val() != checkPassword) {
alert("验证码错误,请重试");
inputCheckPassword.focus();
return;
}
//ajax 登录接口
jQuery.ajax({
type: "POST",
url: "/user/login",
data: {
"username": username.val(),
"password": password.val()
},
success: function (result) {
if (result != null && result.code == 200 && result.data != null) {
//登录成功
location.href = '/myblog_list.html';
} else {
alert(result.msg);
}
}
});
服务器开发
private Object lock = new Object();
@RequestMapping("/login")
public AjaxResult login(HttpServletRequest request, HttpServletResponse response, String username, String password) throws IOException, ParseException {
//非空校验
if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
return AjaxResult.fail(403, "账号或密码错误,请稍后重试!");
}
UserInfo userInfo = userService.getUserByName(username);
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return AjaxResult.fail(403, "账号不存在!");
}
//安全校验:当用户输入密码错误 3 次执行冻结(禁止用户登录)
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
if(userService.getStateByName(username) == 3) {
//记录该用户开始冻结时间(格式yyyy-MM-ddThh:mm:ss)
userInfo.setCreatetime(LocalDateTime.now());
//修改用户开始冻结时间
userService.updateUserInfoById(userInfo);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
//state + 1
userService.updateStateByName(username, userInfo.getState() + 1);
lock.wait(AppVariable.FREEZE_TIME); //这里为了演示效果,时间设置为 10s
//解冻:修改 state 为 0
userService.updateStateByName(username, 0);
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
return AjaxResult.fail(403, "账号已被冻结,请 10s 后重试!");
} else if(userService.getStateByName(username) > 3) {
//账号错误三次以上,计算剩余时间
//将格式话时间转化为时间戳计算
String[] time = userInfo.getCreatetime().toString().split("T");
Date date = simpleDateFormat.parse(time[0] + " " + time[1]);
//计算相差秒数(以下计算都是 毫秒级别,因此最后需要除 1000 换算到秒)
return AjaxResult.fail(403, "账号已被冻结,请 "+
((date.getTime() + AppVariable.FREEZE_TIME - System.currentTimeMillis()) / 1000) +"s 后重试");
}
//ps:这里注意要对加盐密码解密
if(userInfo == null || !PasswordUtils.check(password, userInfo.getPassword())) {
//用户名不存在或密码错误
if(!PasswordUtils.check(password, userInfo.getPassword())) {
//密码错误,该用户的 state 需要 +1
userService.updateStateByName(username, userInfo.getState() + 1);
}
return AjaxResult.fail(403, "账号或密码错误,请稍后重试!");
}
//登录成功
HttpSession session = request.getSession(true);
session.setAttribute(AppVariable.USER_SESSION_KEY, userInfo);
//安全校验,登录成功后将 state 状态改回 0
userService.updateStateByName(username, 0);
return AjaxResult.success(1);
}