springboot整合websocket实现登录挤退现象

在项目期间遇到了同一个账号不能在不同的地方同时登录的情况,解决方法用到了websocket。

关于websocket的原理网上有很多,我这里就不写了,推荐博客:

https://www.cnblogs.com/myzhibie/p/4470065.html

这里我主要记录一下websocket来实现的登录挤退的功能。

一:实现的思想

1.我的思路是这样的,在登录的时候要去后台验证账号密码的正确性,如果这个都不正确那就别说了。

2.当账号和密码正确时,在session里面存储一下该用户信息,后台返回给前端一个标准,表示账号和密码正确,然后前端通过js来建立websocket的连接

后台会接收这个连接,然后在这个连接中取出该连接服务器的session,通过session里面存储的用户id来判断静态变量websocket

list里面是否含有该用户id的websocket(毕竟用户id为唯一标识)。

3.如果含有,则说明该用户已经在登录的状态。所以通过后台的websocket对象来发送消息,告知前端js的websocket说用户已经登录了。

4.如果不含有,则说明该账号目前不处于登录状态,就存放到静态变量List<Websocket>里面。并发送消息到前台说明登录成功。

以上为最基本的思想。但是问题来了。如何实现?同时,如何在websocket获得该次连接服务器的HttpSession对象?

慢慢来解决。这里默认该用户账号密码正确,从js发送websocket的连接开始。

二:实现

  1.js发送websocket的连接

  

function onenSocket(){   /*newsinfo2为项目的根目录,websocket为请求地址,后台通过注解接收*/
    var socket = new WebSocket("ws://localhost:8080/newsinfo2/webSocket/");

        if(typeof(socket) == undefined){
            alert("您的浏览器不支持webSocket,请换一个浏览器...");
            return ;
        }    /*websocket接收消息的函数*/
        socket.onmessage = function(msg){
            if(msg == "已登录"){
                alert("您的账号已在另一个地方尝试登录,如果不是您知晓的情况,请及时修改密码...");
            }else if(msg == "登录成功"){
                location.href="../index/index.html";
            }else if(msg == "修改密码"){
                alert("您账号的密码已经被修改!如果不是你自己知晓的情况,请及时修改密码...");
                location.href="../login/login.html";
            }
        }
        //socket打开时的方法
        socket.onopen = function(){
        }
        //socket关闭时的方法
        socket.onclose = function(){
        }
         //socket出错时的方法
        socket.onerror = function(){
        }
        /*//在页面加载时自动断开链接,这样就不会异常断开链接,后台不会报错误
        $(document).ready(function(){
            socket.close();
        });*/
}

该js发送请求后,后台接收如下:

  2.后台websocket的接收

@ServerEndpoint(value = "/webSocket/")
public class WebSocketServer {

WebSocketServer为自创的类。通过这个注解,这个类会有一些自带的方法:

  onopen():连接时需要调用的方法

  onError():出现错误时执行的方法

  onClose():关闭连接时调用的方法

该类中必须要自定义一个静态变量:

  

//用于存储webSocketServer

public static CopyOnWriteArraySet<WebSocketServer> webSocketServerSet = new CopyOnWriteArraySet<WebSocketServer>();

这个框架就算是建立了,接下来是一些缝缝补补的工作。

要完整的将我的webSocketServer呈现,那还需要获得httpsession对象。获得对象的方法和思想请看下面的博客:

https://www.cnblogs.com/zhuxiaojie/p/6238826.html

我的WebSocket整体呈现:

package news.webSocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

import news.bean.UserInfo;
import news.config.Configuretor;
import news.utils.LogerUtils;
import news.utils.StaticValue;

/**
 * webSocketServer类,用于处理登录挤退现象,
 * 思路:登录时,要判断该账号是否已创建一个webSocket对象存储起来了,根据这个判断的结果来进行下一步动作
 * @author 徐金仁
 */
@ServerEndpoint(value = "/webSocket/" , configurator = Configuretor.class)
public class WebSocketServer {
    //用于存储webSocketServer
    public static CopyOnWriteArraySet<WebSocketServer> webSocketServerSet = new CopyOnWriteArraySet<WebSocketServer>();
    private Session session;  //与某个客户端连接的会话,该session是属于WebSocket的session,不属于HttpSession
    private String sid; //用户的编号
    private Logger log = LogerUtils.getLogger(this.getClass());
    @Autowired
    private HttpSession httpSession_;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((session == null) ? 0 : session.hashCode());
        result = prime * result + ((httpSession_ == null) ? 0 : httpSession_.hashCode());
        result = prime * result + ((sid == null) ? 0 : sid.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        WebSocketServer other = (WebSocketServer) obj;
        if (session == null) {
            if (other.session != null)
                return false;
        } else if (!session.equals(other.session))
            return false;
        if (httpSession_ == null) {
            if (other.httpSession_ != null)
                return false;
        } else if (!httpSession_.equals(other.httpSession_))
            return false;
        if (sid == null) {
            if (other.sid != null)
                return false;
        } else if (!sid.equals(other.sid))
            return false;
        return true;
    }

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketServerSet() {
        return webSocketServerSet;
    }

    public static void setWebSocketServerSet(CopyOnWriteArraySet<WebSocketServer> webSocketServerSet) {
        WebSocketServer.webSocketServerSet = webSocketServerSet;
    }

    public String getSid() {
        return sid;
    }

    public void setSid(String sid) {
        this.sid = sid;
    }

    public HttpSession gethttpSession_() {
        return httpSession_;
    }

    public void sethttpSession_(HttpSession httpSession_) {
        this.httpSession_ = httpSession_;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    /**
     * 获取HttpSession
     * @return
     */
    public HttpSession getHttpSession(){
        return this.httpSession_;
    }

    /**
     * 获取session
     * @return
     */
    public Session getSession(){
        return this.session;
    }

    /**
     * 连接的时候需要调用的方法
     * @throws IOException
     */
    @OnOpen
    public void onOpen(Session session,  EndpointConfig config) throws IOException{
        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        this.session = session;
        this.httpSession_ = httpSession;
        System.out.println("链接中..." + httpSession_.getId());
        //StaticValue为自定义的存放key值的类,里面都是一些常量
        Object obj =  (this.httpSession_.getAttribute(StaticValue.CURRENT_USER));
        if(obj != null){ //说明还链接中
            this.sid = String.valueOf(((UserInfo)obj).getUid());
            log.info(this.sid + "正在链接中...");
            if(!webSocketServerSet.contains(this)){
                webSocketServerSet.add(this); //将连接到的添加进入set里面
            }
        }else{ //说明不链接了
            //等会在写
        }
        /*this.sendMessage("连接成功!");*/
    }
    /**
     * 发送消息的方法
     * @param string
     * @throws IOException
     */
    public void sendMessage(String msg) throws IOException  {
        this.session.getBasicRemote().sendText(msg);
    }

    /**
     * 出现错误的方法
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session , Throwable error){
        log.error( this.sid + "websocket出错断开链接");
    }
    /**
     * 当连接断开时,调用的方法
     */
    @OnClose
    public void onClose(){
        webSocketServerSet.remove(this);
    }

    /**
     * 根据sid查询webSocket
     * @param sid
     * @return
     */
    public static WebSocketServer getWebSocket(String sid){
        for(WebSocketServer w : webSocketServerSet){
            if(sid.equals(w.sid)){
                return w;
            }
        }
        return null;
    }
}

controller层:

@RequestMapping("login")
    public UserInfo login(String uname, String upwd, HttpSession session) throws IOException{
        int result = 0;
        UserInfo userInfo = new UserInfo();
        userInfo.setUname(uname);
        userInfo.setUpwd(upwd);
        UserInfo us = null;
        us = loginService.login(userInfo);
        if(us == null){
            us = new UserInfo();
            us.setUid(-1); //表示账号或密码不对
        }else{//如果查寻到账号和密码都没有错误,则要判断是否已经被登录了,
            WebSocketServer wws = WebSocketServer.getWebSocket(String.valueOf(us.getUid()));
            if(wws != null){ //如果有
                wws.sendMessage("已登录");
                us.setUid(-2); //表示已登录
            }else{//表示暂时没有人登录,您是第一个,要将信息存储一下
                session.setAttribute("userInfo", us);
                session.setAttribute(StaticValue.CURRENT_USER, us);
                System.out.println("session的id:" + session.getId());
            }
        }
        System.out.println(us);
        return us;

这里还有几个坑,一个是如果就是登陆成功后,页面一刷新,websocket就会出异常断开,这里没有什么好的办法,只有每次刷新或者跳转页面的之后,都要重新链接。

还有一个是localhost访问的情况和127.0.0.1访问的情况下是不同的。如果你在js中链接使用127.0.0.1,而项目运行后在浏览器地址上显示的是localhost的话,那么获得的HttpSession并不是同一个对象。这样的话会导致程序员的判断出现错误。解决的办法是同一使用127.0.0.1或者是localhost。至于为什么会出现这种不同,请查看下面:

localhost 127.0.0.1和本机ip三者的区别

localhost 
不联网 
不使用网卡,不受防火墙和网卡限制 
本机访问

127.0.0.1 
不联网 
网卡传输,受防火墙和网卡限制 
本机访问

本机IP 
联网 
网卡传输 ,受防火墙和网卡限制 
本机或外部访问

以上三者区别知识的来源:

https://blog.csdn.net/qq_35101027/article/details/80745664

原文地址:https://www.cnblogs.com/1998xujinren/p/12340671.html

时间: 2024-09-29 09:07:41

springboot整合websocket实现登录挤退现象的相关文章

springboot整合websocket(1)

一.背景 ??我们都知道http协议只能浏览器单方面向服务器发起请求获得响应,服务器不能主动向浏览器推送消息.想要实现浏览器的主动推送有两种主流实现方式: 轮询:缺点很多,但是实现简单 websocket:在浏览器和服务器之间建立tcp连接,实现全双工通信 ??springboot使用websocket有两种方式,一种是实现简单的websocket,另外一种是实现STOMP协议.这一篇实现简单的websocket,STOMP下一篇在讲. 注意:如下都是针对使用springboot内置容器 二.实

springboot整合websocket实现客户端与服务端通信

定义 ?WebSocket是通过单个TCP连接提供全双工(双向通信)通信信道的计算机通信协议.此WebSocket API可在用户的浏览器和服务器之间进行双向通信.用户可以向服务器发送消息并接收事件驱动的响应,而无需轮询服务器. 它可以让多个用户连接到同一个实时服务器,并通过API进行通信并立即获得响应. 案例介绍 ? 后端在接收到用户新下的订单后,通知到后台系统 服务端代码 pom.xml <dependency> <groupId>org.springframework.boo

springboot 整合 MongoDB 实现登录注册,html 页面获取后台参数的方法

springboot简介: Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者. MongoDB的简介: MongoDB 是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据

springboot整合websocket实现一对一消息推送和广播消息推送

maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 常量类 //webSocket相关配置 //链接地址 public static String WEBSOCKETPATHPERFIX = "/ws-push&

SpringBoot 整合websocket

1.MyWebSocket package org.hxm.webSocket; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.webso

springboot整合shiro实现登录认证以及授权

1.添加shiro的依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web- starter</artifactId> <version>1.4.0</version> </dependency> 2.先创建一个Realm public class MyShiroRealm extends Aut

Spring Boot2 系列教程 (十七) | 整合 WebSocket 实现聊天室

微信公众号:一个优秀的废人.如有问题,请后台留言,反正我也不会听. 前言 昨天那篇介绍了 WebSocket 实现广播,也即服务器端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器.但这无法解决消息由谁发送,又由谁接收的问题.所以,今天写一篇实现一对一的聊天室. 今天这一篇建立在昨天那一篇的基础之上,为便于更好理解今天这一篇,推荐先阅读:「SpringBoot 整合WebSocket 实现广播消息 」 准备工作 Spring Boot 2.1.3 RELEASE Spring S

SpringBoot整合LayUI和Thymeleaf制作简单登录页面

前面已经学习过SpringBoot整合Thymeleaf,这次主要把上次提到的简单登录界面用博文形式写出来 记录一个小Demo的学习,如果没看过SpringBoot整合Thymeleaf可以看一下SpringBoot整合Thymeleaf(三) 先上页面效果图: Demo所涉及的知识点 1.SpringBoot请求映射 2.static和templates静态资源映射 只要简单了解这两个知识点,就可以做出简单的登录的页面 Demo所涉及的目录结构图 Demo所涉及的Pom文件的主要依赖 <dep

SpringSecurity解决跨域问题,在SpringBoot整合SprinSecurity中如何用前后端分离Ajax登录,Ajax登录返回状态200还是近error

先说说SpringSecurity如何实现前后端分离Ajax登录? 今天使用SpringBoot整合SpringSecurity中想使用Ajax替代SpringSecurit的Form表单提交,在这里我们的提交方式还是使用表单提交 http.formLogin().loginProcessingUrl("/authentication/form") loginProcessingUrl方法表示你登录请求的地址,在这里SpringSecurity默认登录页面地址是/login ,填写了u