目录
设计思路
分析
前后端交互接口
请求
响应
代码实现和详细注释
数据库设计
实体类设计
前后端交互
客户端开发
服务器开发
设计思路
分析
需求:当用户输入的用户名存在,并且密码输错 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() {@Overridepublic void run() {synchronized (lock) {try {//state + 1userService.updateStateByName(username, userInfo.getState() + 1);lock.wait(AppVariable.FREEZE_TIME); //这里为了演示效果,时间设置为 10s//解冻:修改 state 为 0userService.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 需要 +1userService.updateStateByName(username, userInfo.getState() + 1);}return AjaxResult.fail(403, "账号或密码错误,请稍后重试!");}//登录成功HttpSession session = request.getSession(true);session.setAttribute(AppVariable.USER_SESSION_KEY, userInfo);//安全校验,登录成功后将 state 状态改回 0userService.updateStateByName(username, 0);return AjaxResult.success(1);}