最近想起之前項目里面的一個實現,是關於訂閱推送的,當粉絲訂閱了大V或者說作者發布的內容被評論和點贊之后,對應的用戶會受到通知,當然,本身系統用戶並不多,所以直接采用的是輪訓的方式,由前端這邊定時向后端發起接口請求,獲取消息推送,無疑呢,此種方式也可以解決問題,但是大部分請求基本無用,白白浪費帶寬和網絡資源。
今天難得媳婦兒帶孩子回娘家了,下班到家也無事,便想着整理下前后端通過websocket實現消息推送的方式。當然,前端這塊,主要采用原始的js通過websocket的方式獲取消息,在微信公眾號和支付寶小程序開發中都有相應的onWebSocekt方式,有興趣的同學可以自行學習。
廢話不多說,開始啃代碼。
1、pom.xml
<!--websocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!--thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
2、application.yml
server:
port: 8080
spring:
thymeleaf:
cache: false # 開發時關閉緩存,不然沒法看到實時頁面
mode: HTML # 用非嚴格的 HTML
encoding: UTF-8
servlet:
content-type: text/html
3、WebSocketServer,實現前后端的長連接
package com.cookie.server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; /** * @Author : cxq * @Date : 2020/8/31 15:50 */ // 前端通過該連接與后端保持交互 @ServerEndpoint(value = "/server") @Component public class WebSocketServer { @PostConstruct public void init() { System.out.println("websocket 加載"); } private static Logger log = LoggerFactory.getLogger(WebSocketServer.class); private static final AtomicInteger OnlineCount = new AtomicInteger(0); // concurrent包的線程安全Set,用來存放每個客戶端對應的Session對象。 private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>(); /** * 連接建立成功調用的方法 */ @OnOpen public void onOpen(Session session) { SessionSet.add(session); int cnt = OnlineCount.incrementAndGet(); // 在線數加1 log.info("有連接加入,當前連接數為:{}", cnt); SendMessage(session, "連接成功"); } /** * 連接關閉調用的方法 */ @OnClose public void onClose(Session session) { SessionSet.remove(session); int cnt = OnlineCount.decrementAndGet(); log.info("有連接關閉,當前連接數為:{}", cnt); } /** * 收到客戶端消息后調用的方法 * * @param message * 客戶端發送過來的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("來自客戶端的消息:{}",message); SendMessage(session, "收到消息,消息內容:"+message); } /** * 出現錯誤 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("發生錯誤:{},Session ID: {}",error.getMessage(),session.getId()); error.printStackTrace(); } /** * 發送消息,實踐表明,每次瀏覽器刷新,session會發生變化。 * @param session * @param message */ public static void SendMessage(Session session, String message) { try { // session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); session.getBasicRemote().sendText(message); } catch (IOException e) { log.error("發送消息出錯:{}", e.getMessage()); e.printStackTrace(); } } /** * 群發消息 * @param message * @throws IOException */ public static void BroadCastInfo(String message) throws IOException { for (Session session : SessionSet) { if(session.isOpen()){ SendMessage(session, message); } } } /** * 指定Session發送消息 * @param sessionId * @param message * @throws IOException */ public static void SendMessage(String message,String sessionId) throws IOException { Session session = null; for (Session s : SessionSet) { if(s.getId().equals(sessionId)){ session = s; break; } } if(session!=null){ SendMessage(session, message); } else{ log.warn("沒有找到你指定ID的會話:{}",sessionId); } } }
4、WebSocketController,主要實現消息群發和一對一發送
package com.cookie.controller; import com.cookie.server.WebSocketServer; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; /** * @Author : cxq * @Date : 2020/8/31 16:19 */ @RestController @RequestMapping("/webSocket") public class WebSocketController { /** * 群發消息內容 * * @param message * @return */ @RequestMapping(value = "/sendAll", method = RequestMethod.GET) public String sendAllMessage(@RequestParam(required = true) String message) { try { WebSocketServer.BroadCastInfo(message); } catch (IOException e) { e.printStackTrace(); } return "ok"; } /** * 指定會話ID發消息 * * @param message 消息內容 * @param id 連接會話ID * @return */ @RequestMapping(value = "/sendOne", method = RequestMethod.GET) public String sendOneMessage(@RequestParam(required = true) String message, @RequestParam(required = true) String id) { try { WebSocketServer.SendMessage(message, id); } catch (IOException e) { e.printStackTrace(); } return "ok"; } }
5、index.html接收后端發送的消息及展示
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>websocket測試</title> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <style type="text/css"> h3,h4{ text-align:center; } </style> </head> <body> <h3>WebSocket測試,客戶端接收到的消息如下:</h3> <textarea id = "messageId" readonly="readonly" cols="150" rows="30" > </textarea> <script type="text/javascript"> var socket; if (typeof (WebSocket) == "undefined") { console.log("遺憾:您的瀏覽器不支持WebSocket"); } else { console.log("恭喜:您的瀏覽器支持WebSocket"); //實現化WebSocket對象 //指定要連接的服務器地址與端口建立連接 //注意ws、wss使用不同的端口。我使用自簽名的證書測試, //無法使用wss,瀏覽器打開WebSocket時報錯 //ws對應http、wss對應https。 socket = new WebSocket("ws://localhost:8080/server"); //連接打開事件 socket.onopen = function() { console.log("Socket 已打開"); socket.send("消息發送測試(From Client)"); }; //收到消息事件 socket.onmessage = function(msg) { $("#messageId").append(msg.data+ "\n"); console.log(msg.data ); }; //連接關閉事件 socket.onclose = function() { console.log("Socket已關閉"); }; //發生了錯誤事件 socket.onerror = function() { alert("Socket發生了錯誤"); } //窗口關閉時,關閉連接 window.unload=function() { socket.close(); }; } </script> </body> </html>
6、啟動項目,訪問頁面看效果
訪問 localhost:8080,網頁展示如下
多看幾個頁面,頁面展示內容都一樣,同時后端控制台打印消息如下
接下來,使用postman,依次調用一對一消息推送和群發消息
一對一:http://localhost:8080/webSocket/sendOne?message="這是一條單個消息"&id=1
頁面展示如下:
群發消息:http://localhost:8080/webSocket/sendAll?message="這是一條群發消息"
頁面展示如下: