springboot整合websocket實現消息推送

  • 2019 年 12 月 7 日
  • 筆記

maven依賴

<?xml version="1.0" encoding="UTF-8"?>  <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">  	<modelVersion>4.0.0</modelVersion>    	<groupId>net.dopan</groupId>  	<artifactId>websocket</artifactId>  	<version>0.0.1-SNAPSHOT</version>  	<packaging>jar</packaging>    	<name>websocket</name>  	<description>Demo project for Spring Boot</description>    	<parent>  		<groupId>org.springframework.boot</groupId>  		<artifactId>spring-boot-starter-parent</artifactId>  		<version>1.5.12.RELEASE</version>  		<relativePath/> <!-- lookup parent from repository -->  	</parent>    	<properties>  		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>  		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>  		<java.version>1.8</java.version>  	</properties>    	<dependencies>  		<dependency>  			<groupId>org.springframework.boot</groupId>  			<artifactId>spring-boot-starter-web</artifactId>  		</dependency>  <dependency>  			<groupId>org.mybatis.spring.boot</groupId>  			<artifactId>mybatis-spring-boot-starter</artifactId>  			<version>1.3.2</version>  		</dependency>    		<dependency>  			<groupId>org.springframework.boot</groupId>  			<artifactId>spring-boot-starter-test</artifactId>  			<scope>test</scope>  		</dependency>    		<dependency>  			<groupId>org.springframework.boot</groupId>  			<artifactId>spring-boot-starter-websocket</artifactId>  		</dependency>    		<dependency>  			<groupId>org.assertj</groupId>  			<artifactId>assertj-core</artifactId>  		</dependency>    	</dependencies>    	<build>  		<plugins>  			<plugin>  				<groupId>org.springframework.boot</groupId>  				<artifactId>spring-boot-maven-plugin</artifactId>  			</plugin>  		</plugins>  	</build>  </project>

常量類

package net.dopan.websocket.utils;    public class Constant {        //webSocket相關配置      //鏈接地址      public static String WEBSOCKETPATHPERFIX = "/ws-push";      public static String WEBSOCKETPATH = "/wsendpoint";      //消息代理路徑      public static String WEBSOCKETBROADCASTPATH = "/topic";      //前端發送給服務端請求地址      public static final String FORETOSERVERPATH = "/welcome";      //服務端生產地址,客戶端訂閱此地址以接收服務端生產的消息      public static final String PRODUCERPATH = "/topic/getResponse";      //點對點消息推送地址前綴      public static final String P2PPUSHBASEPATH = "/user";      //點對點消息推送地址後綴,最後的地址為/user/用戶識別碼/msg      public static final String P2PPUSHPATH = "/msg";  }

controller

package net.dopan.websocket.controller;    import net.dopan.websocket.po.Message;  import net.dopan.websocket.po.Response;  import net.dopan.websocket.service.WebSocketService;  import net.dopan.websocket.utils.Constant;  import org.assertj.core.util.Lists;  import org.springframework.messaging.handler.annotation.MessageMapping;  import org.springframework.messaging.handler.annotation.SendTo;  import org.springframework.stereotype.Controller;    import javax.annotation.Resource;  import java.util.List;    @Controller  public class WsController {        @Resource      WebSocketService webSocketService;        // 測試地址 http://127.0.0.1:8080/index.html?userId=admin      @MessageMapping(Constant.FORETOSERVERPATH)//@MessageMapping和@RequestMapping功能類似,用於設置URL映射地址,瀏覽器向服務器發起請求,需要通過該地址。      @SendTo(Constant.PRODUCERPATH)//如果服務器接受到了消息,就會對訂閱了@SendTo括號中的地址傳送消息。      public Response say(Message message) throws Exception {          List<String> users = Lists.newArrayList();          users.add(message.getName());//此處只是為了方便測試,此值需要對應頁面中訂閱個人消息的userId          webSocketService.send2Users(users, new Response("hello " + message.getName()));            return new Response("Welcome, " + message.getName() + "!");      }  }

service

package net.dopan.websocket.service;    import net.dopan.websocket.po.Response;  import net.dopan.websocket.utils.Constant;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.messaging.simp.SimpMessagingTemplate;  import org.springframework.stereotype.Service;    import java.util.List;    @Service  public class WebSocketService {        @Autowired      private SimpMessagingTemplate template;        /**       * 廣播       * 發給所有在線用戶       *       * @param msg       */      public void sendMsg(Response msg) {            template.convertAndSend(Constant.PRODUCERPATH, msg);      }        /**       * 發送給指定用戶       *       * @param users       * @param msg       */      public void send2Users(List<String> users, Response msg) {          users.forEach(userName -> {              template.convertAndSendToUser(userName, Constant.P2PPUSHPATH, msg);          });      }  }

2個po類

package net.dopan.websocket.po;    public class Message {      private String name;        public String getName() {          return name;      }        public void setName(String name) {          this.name = name;      }  }
package net.dopan.websocket.po;    public class Response {      private String responseMessage;        public Response(String responseMessage){          this.responseMessage = responseMessage;      }        public String getResponseMessage() {          return responseMessage;      }        public void setResponseMessage(String responseMessage) {          this.responseMessage = responseMessage;      }  }

websocket類

package net.dopan.websocket;    import net.dopan.websocket.utils.Constant;  import org.springframework.context.annotation.Configuration;  import org.springframework.messaging.simp.config.MessageBrokerRegistry;  import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;  import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;  import org.springframework.web.socket.config.annotation.StompEndpointRegistry;    @Configuration  // @EnableWebSocketMessageBroker註解用於開啟使用STOMP協議來傳輸基於代理(MessageBroker)的消息,這時候控制器(controller)  // 開始支持@MessageMapping,就像是使用@requestMapping一樣。  @EnableWebSocketMessageBroker  public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {        @Override      public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {          //註冊一個Stomp的節點(endpoint),並指定使用SockJS協議。          stompEndpointRegistry.addEndpoint(Constant.WEBSOCKETPATH).withSockJS();      }        @Override      public void configureMessageBroker(MessageBrokerRegistry registry) {          //服務端發送消息給客戶端的域,多個用逗號隔開          registry.enableSimpleBroker(Constant.WEBSOCKETBROADCASTPATH, Constant.P2PPUSHBASEPATH);          //定義一對一推送的時候前綴          registry.setUserDestinationPrefix(Constant.P2PPUSHBASEPATH);          //定義websoket前綴          registry.setApplicationDestinationPrefixes(Constant.WEBSOCKETPATHPERFIX);      }  }

測試html

<!DOCTYPE html>  <html xmlns:th="http://www.thymeleaf.org">  <head>      <meta charset="UTF-8" />      <title>WebSocket</title>        <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>      <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>      <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>      <script th:inline="javascript">          var $_GET = (function(){              var url = window.document.location.href.toString();              var u = url.split("?");              if(typeof(u[1]) == "string"){                  u = u[1].split("&");                  var get = {};                  for(var i in u){                      var j = u[i].split("=");                      get[j[0]] = j[1];                  }                  return get;              } else {                  return {};              }          })();            var stompClient = null;          //此值有服務端傳遞給前端,實現方式沒有要求          var userId = $_GET['userId'];            function setConnected(connected) {              document.getElementById('connect').disabled = connected;              document.getElementById('disconnect').disabled = !connected;              document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';              $('#response').html();          }            function connect() {              var socket = new SockJS('/wsendpoint'); //1連接SockJS的endpoint是「wsendpoint」,與後台代碼中註冊的endpoint要一樣。              stompClient = Stomp.over(socket);//2創建STOMP協議的webSocket客戶端。              stompClient.connect({}, function(frame) {//3連接webSocket的服務端。                  setConnected(true);                  console.log('開始進行連接Connected: ' + frame);                  //4通過stompClient.subscribe()訂閱服務器的目標是'/topic/getResponse'發送過來的地址,與@SendTo中的地址對應。                  stompClient.subscribe('/topic/getResponse', function(respnose){                      showResponse(JSON.parse(respnose.body).responseMessage);                  });                  //4通過stompClient.subscribe()訂閱服務器的目標是'/user/' + userId + '/msg'接收一對一的推送消息,其中userId由服務端傳遞過來,用於表示唯一的用戶,通過此值將消息精確推送給一個用戶                  stompClient.subscribe('/user/' + userId + '/msg', function(respnose){                      console.log(respnose);                      showResponse1(JSON.parse(respnose.body).responseMessage);                  });              });          }              function disconnect() {              if (stompClient != null) {                  stompClient.disconnect();              }              setConnected(false);              console.log("Disconnected");          }            function sendName() {              var name = $('#name').val();              // 通過stompClient.send()向地址為"/welcome"的服務器地址發起請求,與@MessageMapping里的地址對應。              // 因為我們配置了registry.setApplicationDestinationPrefixes(Constant.WEBSOCKETPATHPERFIX);所以需要增加前綴/ws-push/              stompClient.send("/ws-push/welcome", {}, JSON.stringify({ 'name': name }));          }            function showResponse(message) {              var response = $("#response");              response.html(message);          }          function showResponse1(message) {              var response = $("#response1");              response.html(message);          }      </script>  </head>  <body onload="disconnect()">  <noscript><h2 style="color: #ff0000">貌似你的瀏覽器不支持websocket</h2></noscript>  <div>      <div>          <button id="connect" onclick="connect();">連接</button>          <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接</button>      </div>      <div id="conversationDiv">          <label>發送給對方的名字</label><input type="text" id="name" />          <button id="sendName" onclick="sendName();">發送</button>          <br />          廣播<p id="response"></p>          <br />          私發<p id="response1"></p>      </div>  </div>    </body>  </html>