springboot整合websocket实现登录挤退现象
在项目期间遇到了同一个账号不能在不同的地方同时登录的情况,解决方法用到了websocket。
关于websocket的原理网上有很多,我这里就不写了,推荐博客:
https://www.cnblogs.com/myzhibie/p/4470065.html
websocket理清原理:https://zhuanlan.zhihu.com/p/95622141
这里我主要记录一下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的连接开始。
maven依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
二:实现
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