“用户多次登录,账号冻结业务”功能实现(代码+详细注释)
  TEZNKK3IfmPf 2024年03月29日 12 0

设计思路


分析

“用户多次登录,账号冻结业务”功能实现(代码+详细注释)

“用户多次登录,账号冻结业务”功能实现(代码+详细注释)

需求:当用户输入的用户名存在,并且密码输错 3 次以上,就触发账号冻结功能(冻结时间自定义0)。

主要分为以下步骤:

  1. 对客户端传入的账号和密码进行非空校验。(防止恶意用户通过 Postman 等工具请求非法数据)
  2. 通过客户端传入的账号获取该用户的所有数据,进行非空校验。
  3. 判断该用户输错密码是否等于 3 次(通过用户表的 state 字段的数值进行判断),若等于 3 次,首先记录下该用户开始冻结的时间(通过用户表中的 createtime 字段记录,这个字段后面会用到),创建一个线程使用 wait 进行冷冻时间等待,当冷冻时间结束,唤醒线程,将用户输错密码次数修改为 0 (state = 0)。
  4. 若输错密码小于 3 次,则校验密码是否正确,若密码错误则 state + 1,若密码正确,则 state = 0。
  5. 若输错密码大于 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);
    }
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2024年03月29日 0

暂无评论

推荐阅读
TEZNKK3IfmPf