在項目期間遇到了同一個賬號不能在不同的地方同時登錄的情況,解決方法用到了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