利用spring?boot+WebSocket實(shí)現(xiàn)后臺(tái)主動(dòng)消息推送功能
前言:
使用此webscoket務(wù)必確保生產(chǎn)環(huán)境能兼容/支持!使用此webscoket務(wù)必確保生產(chǎn)環(huán)境能兼容/支持!使用此webscoket務(wù)必確保生產(chǎn)環(huán)境能兼容/支持!主要是tomcat的兼容與支持。
有個(gè)需求:
APP用戶產(chǎn)生某個(gè)操作,需要讓后臺(tái)管理系統(tǒng)部分人員感知(表現(xiàn)為一個(gè)頁(yè)面消息)。
最早版本是后臺(tái)管理系統(tǒng)輪訓(xùn),每隔一段時(shí)間輪訓(xùn)一次,由于消息重要,每隔幾秒就查一次。這樣做明顯很不雅!會(huì)消耗大量資源,并且大部分請(qǐng)求是沒(méi)有用的(查不到數(shù)據(jù)進(jìn)來(lái)),很藍(lán)瘦。
后來(lái),想著用消息推送的方式來(lái)處理這個(gè)邏輯。用戶在app產(chǎn)生了目標(biāo)操作,即產(chǎn)生一個(gè)消息,推送給后臺(tái)管理系統(tǒng)的對(duì)應(yīng)用戶。
然后我就找各種資料,一開(kāi)始同事推薦dwz,后來(lái)發(fā)現(xiàn)不太適用于目前的項(xiàng)目(也許能實(shí)現(xiàn)只是我不知道如何實(shí)現(xiàn))。
后來(lái)了解到WebSocket,網(wǎng)上看了很多文檔都是類似聊天室的場(chǎng)景,有些不同。在此,我主要側(cè)重介紹下 服務(wù)器主動(dòng)推送,由服務(wù)端來(lái)觸發(fā)。
WebSocket 主要能實(shí)現(xiàn)的場(chǎng)景:
1、網(wǎng)頁(yè)聊天室
2、服務(wù)器消息實(shí)時(shí)通知
WebSocket 使用方法應(yīng)該有很多,在次介紹下使用 tomcat8+h5 環(huán)境下的實(shí)現(xiàn)。
ps:我自己的測(cè)試環(huán)境是tomcat7這樣寫(xiě)是不行的。wang115032337《https://blog.csdn.net/wang115032337》這位朋友在他的環(huán)境下,tomcat7/8都可以用本文章的寫(xiě)法,只不過(guò)需要去除WebSocketConfig類(有文章表示tomcat7和8對(duì)websocket的支持是不同的,本人未深入了解)
話不多說(shuō),直接上代碼,想深入了解WebSocket 的請(qǐng)查閱相關(guān)介紹。
1.pom
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2. 使用@ServerEndpoint創(chuàng)立websocket endpoint( wang115032337這位朋友在他的環(huán)境下加入@ServerEndpoint類會(huì)報(bào)錯(cuò),直接刪除了仍可用)
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
3.具體實(shí)現(xiàn)類 可自己選擇url要不要帶參數(shù)
package com.star.manager.service; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @Slf4j //@ServerEndpoint("/websocket/{user}") @ServerEndpoint(value = "/websocket") @Component public class WebSocketServer { //靜態(tài)變量,用來(lái)記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計(jì)成線程安全的。 private static int onlineCount = 0; //concurrent包的線程安全Set,用來(lái)存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送數(shù)據(jù) private Session session; /** * 連接建立成功調(diào)用的方法*/ @OnOpen public void onOpen(Session session) { this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在線數(shù)加1 log.info("有新連接加入!當(dāng)前在線人數(shù)為" + getOnlineCount()); try { sendMessage("連接成功"); } catch (IOException e) { log.error("websocket IO異常"); } } // //連接打開(kāi)時(shí)執(zhí)行 // @OnOpen // public void onOpen(@PathParam("user") String user, Session session) { // currentUser = user; // System.out.println("Connected ... " + session.getId()); // } /** * 連接關(guān)閉調(diào)用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //從set中刪除 subOnlineCount(); //在線數(shù)減1 log.info("有一連接關(guān)閉!當(dāng)前在線人數(shù)為" + getOnlineCount()); } /** * 收到客戶端消息后調(diào)用的方法 * * @param message 客戶端發(fā)送過(guò)來(lái)的消息*/ @OnMessage public void onMessage(String message, Session session) { log.info("來(lái)自客戶端的消息:" + message); //群發(fā)消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發(fā)生錯(cuò)誤"); error.printStackTrace(); } public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群發(fā)自定義消息 * */ public static void sendInfo(String message) throws IOException { log.info(message); for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue; } } } public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketServer.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketServer.onlineCount--; } }
產(chǎn)生一個(gè)消息:產(chǎn)生消息場(chǎng)景有多種,http(s),定時(shí)任務(wù),mq等,這貼一個(gè)httpq請(qǐng)求的controller代碼
@RequestMapping(value="/pushVideoListToWeb",method=RequestMethod.POST,consumes = "application/json") public @ResponseBody Map<String,Object> pushVideoListToWeb(@RequestBody Map<String,Object> param) { Map<String,Object> result =new HashMap<String,Object>(); try { WebSocketServer.sendInfo("有新客戶呼入,sltAccountId:"+CommonUtils.getValue(param, "sltAccountId")); result.put("operationResult", true); }catch (IOException e) { result.put("operationResult", true); } return result; }
重要的地方我都加粗了,主要是這段,使用這個(gè)方法,可以實(shí)現(xiàn)服務(wù)器主動(dòng)推送。
public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); }
4.js(html就不寫(xiě)了,隨便找個(gè)能觸發(fā)這個(gè)js的就可以)
//socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三"); var socket; if(typeof(WebSocket) == "undefined") { console.log("您的瀏覽器不支持WebSocket"); }else{ console.log("您的瀏覽器支持WebSocket"); //實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口 建立連接 //socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三")
socket = new WebSocket("ws://localhost:9094/starManager/websocket"); //打開(kāi)事件 socket.onopen = function() { console.log("Socket 已打開(kāi)"); //socket.send("這是來(lái)自客戶端的消息" + location.href + new Date()); }; //獲得消息事件 socket.onmessage = function(msg) { console.log(msg.data); //發(fā)現(xiàn)消息進(jìn)入 調(diào)后臺(tái)獲取 getCallingList(); }; //關(guān)閉事件 socket.onclose = function() { console.log("Socket已關(guān)閉"); }; //發(fā)生了錯(cuò)誤事件 socket.onerror = function() { alert("Socket發(fā)生了錯(cuò)誤"); } $(window).unload(function(){ socket.close(); }); // $("#btnSend").click(function() { // socket.send("這是來(lái)自客戶端的消息" + location.href + new Date()); // }); // // $("#btnClose").click(function() { // socket.close(); // }); }
簡(jiǎn)單說(shuō)說(shuō):
通過(guò)前端代碼
socket = new WebSocket("ws://localhost:9094/starManager/websocket");
其中,starManager是工程名,/webscoket是訪問(wèn)路徑名
建立連接,前端調(diào)用scoket.open() 會(huì)使后臺(tái)在靜態(tài)成員變量webSocketSet里面增加一個(gè)元素,相當(dāng)于一個(gè)緩存。后臺(tái)服務(wù)調(diào)用sendMessage
(指定某個(gè)用戶,定向)或sendInfo(遍歷webSocketSet逐個(gè)發(fā)送,類似群發(fā))方法,即可向已登錄的客戶端推送消息。
代碼就這么多。我的用這些代碼就跑的起來(lái)。做的時(shí)候出現(xiàn)過(guò)頁(yè)面報(bào)404等錯(cuò)誤,如果也是spring boot+h5,仔細(xì)核對(duì)下和我代碼有無(wú)區(qū)別,加配置 路徑是有ok,問(wèn)題應(yīng)該不大。
如果你恰好也有可以用WebSocket實(shí)現(xiàn)的類似場(chǎng)景,希望對(duì)你有幫助。
總結(jié)
到此這篇關(guān)于利用spring boot+WebSocket實(shí)現(xiàn)后臺(tái)主動(dòng)消息推送功能的文章就介紹到這了,更多相關(guān)springboot+WebSocket消息推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
老生常談spring boot 1.5.4 日志管理(必看篇)
下面小編就為大家?guī)?lái)一篇老生常談spring boot 1.5.4 日志管理(必看篇)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06Java實(shí)現(xiàn)Web應(yīng)用中的定時(shí)任務(wù)(實(shí)例講解)
下面小編就為大家分享一篇Java實(shí)現(xiàn)Web 應(yīng)用中的定時(shí)任務(wù)的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-11-11java后臺(tái)實(shí)現(xiàn)js關(guān)閉本頁(yè)面,父頁(yè)面指定跳轉(zhuǎn)或刷新操作
這篇文章主要介紹了java后臺(tái)實(shí)現(xiàn)js關(guān)閉本頁(yè)面,父頁(yè)面指定跳轉(zhuǎn)或刷新操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Spring事務(wù)管理中關(guān)于數(shù)據(jù)庫(kù)連接池詳解
事務(wù)的作用就是為了保證用戶的每一個(gè)操作都是可靠的,事務(wù)中的每一步操作都必須成功執(zhí)行,只要有發(fā)生異常就 回退到事務(wù)開(kāi)始未進(jìn)行操作的狀態(tài)。事務(wù)管理是Spring框架中最為常用的功能之一,我們?cè)谑褂肧pring Boot開(kāi)發(fā)應(yīng)用時(shí),大部分情況下也都需要使用事務(wù)2022-12-12Java類成員訪問(wèn)權(quán)限控制知識(shí)總結(jié)
這篇文章主要介紹了Java類成員訪問(wèn)權(quán)限控制知識(shí)總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04SpringBoot?2.5.5整合輕量級(jí)的分布式日志標(biāo)記追蹤神器TLog的詳細(xì)過(guò)程
分布式追蹤系統(tǒng)是一個(gè)最終的解決方案,如果您的公司已經(jīng)上了分布式追蹤系統(tǒng),這篇文章主要介紹了SpringBoot?2.5.5整合輕量級(jí)的分布式日志標(biāo)記追蹤神器TLog,需要的朋友可以參考下2022-10-10java 使用簡(jiǎn)單的demo實(shí)例告訴你優(yōu)化算法的強(qiáng)大
本篇文章介紹了,在java中使用簡(jiǎn)單的demo實(shí)例告訴你優(yōu)化算法的強(qiáng)大。需要的朋友參考下2013-05-05idea啟動(dòng)springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法
這篇文章主要介紹了idea啟動(dòng)springmvc項(xiàng)目時(shí)報(bào)找不到類的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Java單元測(cè)試Powermockito和Mockito使用總結(jié)
公司單元測(cè)試框架選用了Junit 4.12,Mock框架選用了Mockito和PowerMock,本文主要介紹了Java單元測(cè)試Powermockito和Mockito使用總結(jié),感興趣的可以了解一下2021-09-09