目录
设计思路
分析
前后端交互接口
请求
响应
代码实现和详细注释
数据库设计
实体类设计
前后端交互
客户端开发
服务器开发
需求:当用户输入的用户名存在,并且密码输错 3 次以上,就触发账号冻结功能(冻结时间自定义0)。
主要分为以下步骤:
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);
}