利用session实现一个账户只能在一个客户端登录,后登录客户端可以挤掉前登录客户端账号,类似QQ

在项目中需要利用userId和时间戳生成订单,这就要保证一个用户在同一时间只能在一个客户端上进行操作,而且为了账号的安全性。所以就想实现QQ那样的后者挤掉前者的功能。

由于自己是一边学一边写的项目,所以对session这些功能的特性没有充分的了解。刚开始以为很简单,直接后者登录时直接查询当前用户信息是否已经存在在session中,如果存在移除掉前者的session信息就可以,因为每个session都有自己的sessionId。这简直不要太简单。结果无知就是天真。首先思路没啥问题,等到去写方法得时候太发现session是每个会话独有的,其他的连接是不能访问其他连接的session的,而且也没有直接通过sessionId移除session这种方法。

于是百度看看别人都是怎么做的。也是利用的session。思路是把session和用户状态存储到一个临时表中,然后给了两种方案,一种是hashMap,一种是servletcontext。两种都不是很了解。于是百度,一下是关于两种的解释:

hashMao百度百科

servletcontext百度百科

二者比较之后我觉得servletcontext简单易操作,而且整个服务器端可以共享数据,服务器启动自动生成,关闭即销毁。刚好符合需求。开搞。先上最终效果:

 可以看到左边浏览器已经登录了迪丽热巴账号,右边再去登录,这时左边不管是页面刷新还是跳转其他页面,都会重定向到登陆页,并提示账号在其他地方登录!

上代码;

登录成功时,将用户信息存入session中,取出sessionId和用户Id,以用户id为key(因为每个用户id都是唯一的,sessionId会改变的)。

 UserInfo userInfo = userRepositories.findByAccountAndPassword(account,password);
            session.setAttribute("loginUser",userInfo);/*登录成功把用户信息存入session中,便于调用*/
            String userId = String.valueOf(userInfo.getId());/*获取当前用户id并初始化为String类型*/
            String sessionId = session.getId();/*获取当前会话的sessionId*/
            ServletContext servletContext = GetServletContext.getServletContext();
            servletContext.setAttribute(userId,sessionId);/*以用户id为key,sessionId为value存储到容器中*/
            session.removeAttribute("loginState");
            return "登录成功";

 然后写一个检查session的方法,判断当前session中是否有用户登录信息,没有的话说明没登录,返回true,如果存在登录信息,取出当前sessionId以及servletcontext中此用户id对应的sessionId,判断两个sessionId是否相等,相等的话,说明是同一个连接会话,也返回true。不相等的话,就说明后面登录的sessionId已经覆盖掉了之前servletcontext中的sessionId,存在异地登录,返回false。

public class CheckSession{
    
    public static boolean checkSession(){
        HttpSession session = GetSession.getSession();
        UserInfo userInfo = (UserInfo) session.getAttribute("loginUser");
        if (userInfo == null){
            return true;
        }else{
            ServletContext servletContext = GetServletContext.getServletContext();
            String sessionId = session.getId();
            String userId = String.valueOf(userInfo.getId());
            String oldSessionId = (String) servletContext.getAttribute(userId);
            if (oldSessionId == null || oldSessionId.equals(sessionId)){/*如果不存在此用户的sessionId(一般不可能)
                                                                         或者新旧id相等,说明是同一个登录*/
                return true;
            }else {
                return false;/*否则就是不同客户端登录,返回false*/
            }
        }

    }
}

然后写一个移除session的方法,因为我这里每个页面拦截判断后都需要执行。

public class DelSecondUser {
    public static void delSecondUser(){
        HttpSession session = GetRequest.getRequest().getSession();
        session.removeAttribute("loginUser");
        session.setAttribute("loginState","1");/*存储一个状态,用于前端判断是多客户端登录,账号被挤掉了*/
    }
}

关于在普通类方法中怎么获得session等,可以查看我上一篇博客:springboot普通类获取session等

然后去拦截器那里进行判断:

@GetMapping({"/index", ""})
    public String index(ModelMap modelMap) {
        if (!CheckSession.checkSession()) {
            DelSecondUser.delSecondUser();
            return "redirect:user";
        } else {
           
            return "index";
        }
    }

这里我是用@GetMapping来拦截路径请求的,首先就执行checksession判断,如果返回的是false,说明账号已经在其他地方登录了,那么这个就得会话的session就得被移除,所以执行delSecondUser方法,并重定向到登陆页。不是的话就进行正常逻辑操作。每个页面都进行这个逻辑判断。就可以实现只要刷新或跳转页面就会退出。

至于怎么在登录页判断是不是被挤掉账号而过来的进行提示

记得我们在移除session的那个方法里写了这样一个语句。

 session.setAttribute("loginState","1");/*存储一个状态,用于前端判断是多客户端登录,账号被挤掉了*/

如果是被挤到的,那么给前端一个1的状态。

然后在登录页放一个隐藏的标签来接收它:

<span style="display:none;"th:if="${session.loginState!=null}"th:text="${session.loginState}" id="ifLogin"></span><!--如果是1代表是被挤掉而重定向过来的-->
<span style="display: none;" th:if="${session.loginState==null}" th:text="0" id="ifLogin"></span><!--默认是0-->

在登录页的js里写一个判断方法:

//判断是不是被挤出登录
function ifLogin(){
    var state = $('#ifLogin').text().trim();
    if (state == "1"){
        layer.confirm('您的账号在其他地方登录,请确认是否本人操作?', {
            btn: ['是我本人操作','不是我,立即修改密码'] //按钮
            ,closeBtn:0
            ,title:"警告"
            ,btnAlign: 'c'
        }, function(){
            layer.msg('好的!请注意保护个人账号安全!',{icon:6});
        }, function(){
            location.href="forgetPd";
        });
    }
}

每次进入页面都进行判断,是不是1,是1就给出提示。

如果在登录界面再次登录,那么就移除掉这loginstate状态,前面登录那里有写到。

但是有一种情况,比如涉及到用户登录的情况下才能执行的业务逻辑,假如a客户端用户1正在充值,订单号是需要从session中取出用户id加时间戳生成的,这时候b客户端突然有人用用户1的账号登录了,a客户端的session被移除了,但是a客户端页面没刷新,用户1可以继续点击充值按钮,这时候后台取不到session信息,报错。

所以我们在后台需要执行一个判断,如果取不到session,直接返回一个结果前端判断即可,如果取得到则继续业务操作就行。

至此此功能完整实现。

由于本人比较小白,所以表述上有啥错误或者有更好的方法,欢迎大佬们指正和指教*^_^*

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值