WebSocket實現前後端通訊
長安如夢裡,何日是歸期。
簡介:我們上線了一個商城專案,移交運營團隊使用之後,他們要求商城有新訂單來的時候同時加上聲音提示,讓她們可以及時知道有單來了。我這邊想了想,這個需求是在後端完成還是前端完成,但是仔細一想,無論是在前端還是後端完成都一樣,需求註定甩不出去了,因為這個商城的後臺管理沒有前端工程師,前後端的工作都是一個後端工程師來完成的。這也導致前端介面很難看,包括前端程式碼風格~因為這是我第一次寫這麼多前端程式碼的專案~哈哈~敢讓我寫~我就敢寫。
一、思路:
1、平臺實現推送,之在前的專案有用過Ajax輪詢的技術,這種方式瀏覽器需要不斷的向伺服器發出請求,會浪費很多的頻寬等資源,技術可行但不太好。
2、完完全全在前端實現此需求,在前端監聽訂單列表中元素的變化,迴圈遍歷訂單列表監聽或使用Vue的Watch監聽,當訂單列表有新增元素即可呼叫播放音效API,感覺不怎麼靠譜,沒去試過。
3、最終使用了Websocket實現的 ,WebSocket是HTML5開始提供的一種在單個TCP連線上進行全雙工通訊的協議,能更好的節省伺服器資源和頻寬,並且能夠更實時地進行通訊。WebSocket 使得客戶端和伺服器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料,在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線-長連線,並進行雙向資料傳輸。我猜使用這個方案最重要的原因是後端開發是我更擅長的領域吧。
二、SpringBoot/SpringCloud整合WebSocket
1、引入WebSocket Jar包
在需要用到WebSocket 通訊的SpringBoot 工程的pom.xml 檔案中引入WebSocket Jar包。
1 <!-- test websocket -->
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-websocket</artifactId>
5 </dependency>
2、建立WebSocket 配置類
該配置類用於檢測帶註解@ServerEndpoint 的bean 並將它們註冊到容器中。
1 package com.tjt.mall.config;
2
3 import lombok.extern.slf4j.Slf4j;
4 import org.springframework.context.annotation.Bean;
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
7
8
9 @Configuration
10 @Slf4j
11 public class WebSocketConfig {
12 /**
13 * 給spring容器注入這個ServerEndpointExporter物件
14 * 相當於xml:
15 * <beans>
16 * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/>
17 * </beans>
18 * <p>
19 * 檢測所有帶有@serverEndpoint註解的bean並註冊他們。
20 *
21 * @return
22 */
23 @Bean
24 public ServerEndpointExporter serverEndpointExporter() {
25 log.info("serverEndpointExporter被注入了");
26 return new ServerEndpointExporter();
27 }
28 }
3、建立WebSocket 處理類
該WebSocket 處理類需要使用@ServerEndpoint 註解,在這個類裡監聽連線的建立、關閉和訊息的接收等。
1 package com..mall.config;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.stereotype.Component;
6
7 import javax.annotation.PostConstruct;
8 import javax.websocket.*;
9 import javax.websocket.server.ServerEndpoint;
10 import java.io.IOException;
11 import java.util.concurrent.CopyOnWriteArraySet;
12 import java.util.concurrent.atomic.AtomicInteger;
13
14 @ServerEndpoint(value = "/ws/path/asset") // WebSocket 路徑
15 @Component
16 public class WebSocketServer {
17
18 private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
19 private static final AtomicInteger OnlineCount = new AtomicInteger(0);
20 // concurrent包的執行緒安全Set,用來存放每個客戶端對應的Session物件。
21 private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
22
23 @PostConstruct
24 public void init() {
25 log.info("websocket 載入");
26 }
27
28
29 /**
30 * 連線建立成功呼叫的方法
31 */
32 @OnOpen
33 public void onOpen(Session session) throws IOException{
34 SessionSet.add(session);
35 int cnt = OnlineCount.incrementAndGet(); // 線上數加1
36 log.info("有連線加入,當前連線數為:{}", cnt);
37 // SendMessage(session, "連線成功");
38 }
39
40 /**
41 * 連線關閉呼叫的方法
42 */
43 @OnClose
44 public void onClose(Session session) {
45 SessionSet.remove(session);
46 int cnt = OnlineCount.decrementAndGet();
47 log.info("有連線關閉,當前連線數為:{}", cnt);
48 }
49
50 /**
51 * 收到客戶端訊息後呼叫的方法
52 *
53 * @param message
54 * 客戶端傳送過來的訊息
55 */
56 @OnMessage
57 public void onMessage(String message, Session session) throws IOException {
58 log.info("來自客戶端的訊息:{}",message);
59 SendMessage(session, "收到訊息,訊息內容:"+message);
60
61 }
62
63 /**
64 * 出現錯誤
65 * @param session
66 * @param error
67 */
68 @OnError
69 public void onError(Session session, Throwable error) {
70 log.error("發生錯誤:{},Session ID: {}",error.getMessage(),session.getId());
71 error.printStackTrace();
72 }
73
74 /**
75 * 傳送訊息,實踐表明,每次瀏覽器重新整理,session會發生變化。
76 * @param session
77 * @param message
78 */
79 public static void SendMessage(Session session, String message) throws IOException {
80 try {
81 // session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
82 session.getBasicRemote().sendText(message);
83 } catch (IOException e) {
84 log.error("傳送訊息出錯:{}", e.getMessage());
85 e.printStackTrace();
86 }
87 }
88
89 /**
90 * 群發訊息
91 * @param message
92 * @throws IOException
93 */
94 public static void BroadCastInfo(String message) throws IOException {
95 for (Session session : SessionSet) {
96 if(session.isOpen()){
97 SendMessage(session, message);
98 }
99 }
100 }
101
102 /**
103 * 指定Session傳送訊息
104 * @param sessionId
105 * @param message
106 * @throws IOException
107 */
108 public static void SendMessage(String message,String sessionId) throws IOException {
109 Session session = null;
110 for (Session s : SessionSet) {
111 if(s.getId().equals(sessionId)){
112 session = s;
113 break;
114 }
115 }
116 if(session!=null){
117 SendMessage(session, message);
118 }
119 else{
120 log.warn("沒有找到你指定ID的會話:{}",sessionId);
121 }
122 }
123 }
4、呼叫WebSocket 傳送訊息
在訂單生成的程式碼後,根據需求呼叫WebSocket 處理類中的群發或者單獨傳送訊息的方法。
1 log.info("購物車生成訂單OK: {}", result);
2 // send webSocket message
3 WebSocketServer.BroadCastInfo("after service order");
三、VUE整合WebSocket
在前端使用WebSocket 時還需要判斷下,因為目前雖然大部分瀏覽器都支援WebSocket,比如Chrome、Mozilla、Opera 和Safari,但還有少部分是不支援的。
1、HTML中操作WebSocket
在html頁面中建立websocket 連線,至於什麼時候連線WebSocket,看個人需求吧。我是在登入成功後,也就是在登入頁面的Html中連線WebSocket 並監聽訊息。
我把登入HTML頁面中的部分程式碼刪除了,只留下WebSocket 相關操作的和播放音效相關的程式碼。
1 <template>
2 <div class="dashboard-container">
3 <!-- 在div 中引入 音訊資源-->
4 <audio ref="audio" src="@/assets/voice/tjtVoice.mp3"></audio>
5 </div>
6 </template>
7
8 <script>
9
10 export default {
11
12 created() {
13 this.playVoice()
14 },
15
16 methods: {
17 playVoice() {
18 var socket;
19 // 當找不到控制程式碼物件即音訊資源的時候使用 that
20 let that = this
21 if (typeof (WebSocket) == "undefined") {
22 console.log("遺憾:您的瀏覽器不支援WebSocket");
23 } else {
24 console.log("恭喜:您的瀏覽器支援WebSocket");
25 //實現化WebSocket物件
26 //指定要連線的伺服器地址與埠建立連線
27 //注意ws、wss使用不同的埠。我使用自簽名的證照測試,
28 //無法使用wss,瀏覽器開啟WebSocket時報錯
29 //ws對應http、wss對應https。
30 socket = new WebSocket("ws://localhost:11200/ws/path/asset");
31 //連線開啟事件
32 socket.onopen = function() {
33 console.log("Socket 已開啟");
34 // socket.send("訊息傳送測試(From Client)");
35
36 };
37 //收到訊息事件
38 socket.onmessage = msg => {
39 // 當收到訊息呼叫播放音訊資源API
40 console.log(msg.data);
41 this.$refs.audio.play()
42 };
43 //連線關閉事件
44 socket.onclose = function() {
45 console.log("Socket已關閉");
46 };
47 //發生了錯誤事件
48 socket.onerror = function() {
49 alert("Socket發生了錯誤");
50 }
51 //視窗關閉時,關閉連線
52 window.unload=function() {
53 // socket.close();
54 };
55 }
56 }
57 }
58
59 }
60 </script>
四、測試效果
1、啟動裝配有WebSocket 的SpringBoot 工程。
2、啟動VUE 工程,點選登入,連線WebSocket。
後端工程控制檯列印
[2021-07-07 08:45:47] [INFO ] -- 有連線加入,當前連線數為:1
前端瀏覽器的WS 下分別顯示WebSocket 連線狀態:
3、模擬訂單生成,在後臺呼叫訂單生成介面API 。
前端瀏覽器Console 列印,成功監聽並節接收到後端傳送的WebSocket 訊息。
4、音訊試聽
接收到後端傳送的WebSocket 訊息,即刻播放音效。
四、音訊資源
下載連結:https://pan.baidu.com/s/1rqa6Uift3RpWShPytgBLgQ
密碼: 8arh
長安如夢裡
何日是歸期