整合營(yíng)銷(xiāo)服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢(xún)熱線:

          利用Spring WebFlux 整合Bootstr

          利用Spring WebFlux 整合Bootstrap模板引擎技術(shù)實(shí)現(xiàn)單頁(yè)面應(yīng)用?

          以按照以下步驟進(jìn)行來(lái)實(shí)現(xiàn),在Spring Boot中使用Spring WebFlux結(jié)合Bootstrap模板引擎實(shí)現(xiàn)單頁(yè)面應(yīng)用(SPA)。

          配置依賴(lài)

          需要在POM文件中添加Spring Reactive Web、 Thymeleaf、Spring Data Reactive MongoDB等依賴(lài)配置,如下所示。

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-webflux</artifactId>
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- 或者使用其他模板引擎 -->
          </dependency>
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId> <!-- 如果需要持久化 -->
          </dependency>

          如果有特殊的需求,可以在application.yml或application.properties中添加WebFlux相關(guān)的配置信息,如果沒(méi)有就可以不用配置了。

          創(chuàng)建模板文件

          在src/main/resources/templates目錄下創(chuàng)建Thymeleaf模板文件(或者你選擇的其他模板文件)。例如,創(chuàng)建index.html文件。

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>SPA with Spring WebFlux</title>
              <!-- Bootstrap CSS -->
              <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
          </head>
          <body>
              <div class="container">
                  <h1 class="mt-5">Hello, Spring WebFlux with Bootstrap!</h1>
                  <!-- Add your SPA content here -->
                  <div id="app"></div>
              </div>
              <!-- Bootstrap JS -->
              <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
              <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.1/dist/umd/popper.min.js"></script>
              <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
          </body>
          </html>
          

          創(chuàng)建Controller

          定義一個(gè)Controller來(lái)處理前端請(qǐng)求,如下所示。

          import org.springframework.stereotype.Controller;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestMapping;
          import reactor.core.publisher.Mono;
          
          @Controller
          @RequestMapping("/")
          public class MainController {
          
              @GetMapping
              public Mono<String> index() {
                  return Mono.just("index"); // 指向模板文件名
              }
          }

          為了使你的應(yīng)用能夠處理SPA的前端路由,你需要在Controller中添加相應(yīng)的路由處理邏輯,或者利用JavaScript前端框架(如React、Vue.js等)來(lái)管理前端路由。

          啟動(dòng)Spring Boot應(yīng)用,訪問(wèn)http://localhost:8080(默認(rèn)端口),應(yīng)該能夠看到使用Bootstrap樣式的頁(yè)面。

          總結(jié)

          通過(guò)這種方式,你可以利用Spring WebFlux的響應(yīng)式特性和Bootstrap的前端樣式來(lái)構(gòu)建現(xiàn)代化的單頁(yè)面應(yīng)用。如果你需要使用現(xiàn)代SPA框架(如React、Vue.js),你可以將其構(gòu)建后的靜態(tài)文件放在src/main/resources/static目錄中,并配置Spring Boot以提供這些靜態(tài)資源。

          pringboot是如何路由到頁(yè)面?

          現(xiàn)在我們創(chuàng)建了一個(gè)新的springboot的web工程

          非常干凈。

          resources目錄下面有兩個(gè)空的文件夾static和templates

          static是用來(lái)放靜態(tài)資源的,包括靜態(tài)頁(yè)面,css,js,圖片等等

          template用來(lái)放動(dòng)態(tài)頁(yè)面,就是要根據(jù)java后臺(tái)代碼的返回值來(lái)動(dòng)態(tài)生成的

          實(shí)驗(yàn)1:路由到靜態(tài)頁(yè)面

          先什么依賴(lài)都不加,當(dāng)前我的maven依賴(lài)只有

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          

          在static下建一個(gè)login.html

          <!doctype html>
          <html lang="en">
          <head>
          <meta charset="UTF-8"/>
          </head>
          <body>
          <h2>
          這是一個(gè)靜態(tài)登錄頁(yè)面
          </h2>
          </body>
          </html>
          

          啟動(dòng)

          測(cè)試

          可以看到,默認(rèn)情況下springboot會(huì)直接訪問(wèn)到static下的靜態(tài)文件

          必須加文件的后綴名。

          實(shí)驗(yàn)二:建一個(gè)controller,處理下請(qǐng)求

          測(cè)試結(jié)果:

          可以看到,經(jīng)過(guò)controller處理了一下請(qǐng)求,找到了靜態(tài)資源

          但是,我們后臺(tái)開(kāi)發(fā)主要還是使用動(dòng)態(tài)頁(yè)面。現(xiàn)在引入Thymeleaf

          <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-thymeleaf</artifactId>
          </dependency>
          

          實(shí)驗(yàn)三:引入Thymeleaf后測(cè)試

          在templates路徑下也創(chuàng)建一個(gè)login.html

          測(cè)試:

          已經(jīng)找的是templates下的文件了。這說(shuō)明引入Thymeleaf后,通過(guò)java代碼處理的請(qǐng)求,默認(rèn)是找templates下的文件

          當(dāng)然,你測(cè)試

          依然是靜態(tài)頁(yè)面

          另外,此時(shí),你的java代碼返回值帶不帶html后綴都可以

          實(shí)驗(yàn)四:加點(diǎn)數(shù)據(jù)看看

          我們的最終目的是根據(jù)后臺(tái)數(shù)據(jù)動(dòng)態(tài)生成頁(yè)面

          所以傳點(diǎn)數(shù)據(jù)看看

          Hi,大家好,我是希留。

          在項(xiàng)目的開(kāi)發(fā)工程中,可能會(huì)遇到實(shí)時(shí)性比較高的場(chǎng)景需求,例如說(shuō),聊天 IM 即時(shí)通訊功能、消息訂閱服務(wù)、在線客服等等。那遇到這種功能的時(shí)候應(yīng)該怎么去做呢?通常是使用WebSocket去實(shí)現(xiàn)。

          那么,本篇文章就帶大家來(lái)了解一下是什么是WebSocket,以及使用SpringBoot搭建一個(gè)簡(jiǎn)易的聊天室功能。如果對(duì)你有幫助的話,還不忘點(diǎn)贊轉(zhuǎn)發(fā)支持一下,感謝!

          源碼地址:

          https://github.com/277769738/java-sjzl-demo/tree/master/springboot-websocket

          https://gitee.com/huoqstudy/java-sjzl-demo/tree/master/springboot-websocket

          目錄

          • 一、什么是WebSocket
          • 二、Http與WebSocket的區(qū)別
          • 三、代碼實(shí)現(xiàn)

          一、什么是WebSocket

          WebSocket 是HTML5一種新的協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信(full-duplex)。一開(kāi)始的握手需要借助HTTP請(qǐng)求完成。WebSocket是真正實(shí)現(xiàn)了全雙工通信的服務(wù)器向客戶端推的互聯(lián)網(wǎng)技術(shù)。它是一種在單個(gè)TCP連接上進(jìn)行全雙工通訊協(xié)議。Websocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,Websocket API被W3C定為標(biāo)準(zhǔn)。

          全雙工和單工的區(qū)別?

          • 全雙工(Full Duplex)是通訊傳輸?shù)囊粋€(gè)術(shù)語(yǔ)。通信允許數(shù)據(jù)在兩個(gè)方向上同時(shí)傳輸,它在能力上相當(dāng)于兩個(gè)單工通信方式的結(jié)合。全雙工指可以同時(shí)(瞬時(shí))進(jìn)行信號(hào)的雙向傳輸(A→B且B→A)。指A→B的同時(shí)B→A,是瞬時(shí)同步的。
          • 單工、半雙工(Half Duplex),所謂半雙工就是指一個(gè)時(shí)間段內(nèi)只有一個(gè)動(dòng)作發(fā)生,舉個(gè)簡(jiǎn)單例子,一條窄窄的馬路,同時(shí)只能有一輛車(chē)通過(guò),當(dāng)目前有兩輛車(chē)對(duì)開(kāi),這種情況下就只能一輛先過(guò),等到頭兒后另一輛再開(kāi),這個(gè)例子就形象的說(shuō)明了半雙工的原理。早期的對(duì)講機(jī)、以及早期集線器等設(shè)備都是基于半雙工的產(chǎn)品。隨著技術(shù)的不斷進(jìn)步,半雙工會(huì)逐漸退出歷史舞臺(tái)。

          二、Http與WebSocket的區(qū)別

          http協(xié)議是短連接,因?yàn)檎?qǐng)求之后,都會(huì)關(guān)閉連接,下次重新請(qǐng)求數(shù)據(jù),需要再次打開(kāi)鏈接。

          WebSocket協(xié)議是一種長(zhǎng)鏈接,只需要通過(guò)一次請(qǐng)求來(lái)初始化鏈接,然后所有的請(qǐng)求和響應(yīng)都是通過(guò)這個(gè)TCP鏈接進(jìn)行通訊。

          三、代碼實(shí)現(xiàn)


          1.添加依賴(lài)

          Maven 依賴(lài):

          <!--websocket依賴(lài)-->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-websocket</artifactId>
          </dependency>
          
          <!-- 引入 Fastjson ,實(shí)現(xiàn)對(duì) JSON 的序列化,因?yàn)楹罄m(xù)我們會(huì)使用它解析消息 -->
          <dependency>
              <groupId>com.alibaba</groupId>
              <artifactId>fastjson</artifactId>
              <version>1.2.62</version>
          </dependency>

          2.消息

          因?yàn)?WebSocket 協(xié)議,不像 HTTP 協(xié)議有 URI 可以區(qū)分不同的 API 請(qǐng)求操作,所以我們需要在 WebSocket 的 Message 里,增加能夠標(biāo)識(shí)消息類(lèi)型,這里我們采用 type 字段。所以在這個(gè)示例中,我們采用的 Message 采用 JSON 格式編碼,格式如下:

          {
              type : "", //消息類(lèi)型
              boby: {} //消息體
          }
          • type 字段,消息類(lèi)型。通過(guò)該字段,我們知道使用哪個(gè) MessageHandler 消息處理器。關(guān)于 MessageHandler ,我們?cè)?「2.6 消息處理器」 中,詳細(xì)解析。
          • body 字段,消息體。不同的消息類(lèi)型,會(huì)有不同的消息體。

          2.1 Message

          創(chuàng)建 Message 接口,基礎(chǔ)消息體,所有消息體都要實(shí)現(xiàn)該接口。目前作為一個(gè)標(biāo)記接口,未定義任何操作。代碼如下:

          public interface Message {
          
          }

          2.2 認(rèn)證相關(guān) Message

          創(chuàng)建 AuthRequest 類(lèi),用戶認(rèn)證請(qǐng)求。代碼如下:

          public class AuthRequest implements Message{
              public static final String TYPE="AUTH_REQUEST";
          
              /**
               * 認(rèn)證 Token
               */
              private String accessToken;
          
              public String getAccessToken() {
                  return accessToken;
              }
          
              public void setAccessToken(String accessToken) {
                  this.accessToken=accessToken;
              }
          }

          TYPE 靜態(tài)屬性,消息類(lèi)型為 AUTH_REQUEST 。

          accessToken 屬性,認(rèn)證 Token 。在 WebSocket 協(xié)議中,我們也需要認(rèn)證當(dāng)前連接,用戶身份是什么。一般情況下,我們采用用戶調(diào)用 HTTP 登錄接口,登錄成功后返回的訪問(wèn)令牌 accessToken 。

          2.3 創(chuàng)建AuthResponse 類(lèi)

          WebSocket 協(xié)議是基于 Message 模型,進(jìn)行交互。但是,這并不意味著它的操作,不需要響應(yīng)結(jié)果。例如說(shuō),用戶認(rèn)證請(qǐng)求,是需要用戶認(rèn)證響應(yīng)的。所以,我們創(chuàng)建 AuthResponse 類(lèi),作為用戶認(rèn)證響應(yīng)。代碼如下:

          public class AuthResponse implements Message {
          
              public static final String TYPE="AUTH_RESPONSE";
          
              /**
               * 響應(yīng)狀態(tài)碼
               */
              private Integer code;
              /**
               * 響應(yīng)提示
               */
              private String message;
          
              public Integer getCode() {
                  return code;
              }
          
              public void setCode(Integer code) {
                  this.code=code;
              }
          
              public String getMessage() {
                  return message;
              }
          
              public void setMessage(String message) {
                  this.message=message;
              }
          }

          2.4 發(fā)送消息相關(guān) Message

          創(chuàng)建 SendToOneRequest 類(lèi),發(fā)送給指定人的私聊消息的 Message。代碼如下:

          public class SendToOneRequest implements Message {
          
              public static final String TYPE="SEND_TO_ONE_REQUEST";
          
              /**
               * 發(fā)送給的用戶
               */
              private String toUser;
          
              /**
               * 消息編號(hào)
               */
              private String msgId;
          
              /**
               * 發(fā)送的內(nèi)容
               */
              private String content;
          
          
              public String getToUser() {
                  return toUser;
              }
          
              public void setToUser(String toUser) {
                  this.toUser=toUser;
              }
          
              public String getMsgId() {
                  return msgId;
              }
          
              public void setMsgId(String msgId) {
                  this.msgId=msgId;
              }
          
              public String getContent() {
                  return content;
              }
          
              public void setContent(String content) {
                  this.content=content;
              }
          }

          在服務(wù)端接收到發(fā)送消息的請(qǐng)求,需要異步響應(yīng)發(fā)送是否成功。所以,創(chuàng)建 SendResponse 類(lèi),發(fā)送消息響應(yīng)結(jié)果的 Message 。代碼如下:

          public class SendResponse implements Message{
          
              public static final String TYPE="SEND_RESPONSE";
          
              /**
               * 消息編號(hào)
               */
              private String msgId;
              /**
               * 響應(yīng)狀態(tài)碼
               */
              private Integer code;
              /**
               * 響應(yīng)提示
               */
              private String message;
          }

          在服務(wù)端接收到發(fā)送消息的請(qǐng)求,需要轉(zhuǎn)發(fā)消息給對(duì)應(yīng)的人。所以,創(chuàng)建 SendToUserRequest 類(lèi),發(fā)送消息給一個(gè)用戶的 Message 。代碼如下:

          public class SendToUserRequest implements Message{
          
              public static final String TYPE="SEND_TO_USER_REQUEST";
          
              /**
               * 消息編號(hào)
               */
              private String msgId;
              /**
               * 內(nèi)容
               */
              private String content;
          
              public String getMsgId() {
                  return msgId;
              }
          
              public void setMsgId(String msgId) {
                  this.msgId=msgId;
              }
          
              public String getContent() {
                  return content;
              }
          
              public void setContent(String content) {
                  this.content=content;
              }
          }

          2.5 消息處理器

          每個(gè)客戶端發(fā)起的 Message 消息類(lèi)型,我們會(huì)聲明對(duì)應(yīng)的 MessageHandler 消息處理器。這個(gè)就類(lèi)似在 SpringMVC 中,每個(gè) API 接口對(duì)應(yīng)一個(gè) Controller 的 Method 方法。

          2.5.1 MessageHandler

          創(chuàng)建 MessageHandler 接口,消息處理器接口。代碼如下:

          public interface MessageHandler<T extends Message> {
          
              /**
               * 執(zhí)行處理消息
               * @param session 會(huì)話
               * @param message 消息
               */
              void execute(WebSocketSession session, T message);
          
              /**
               * 消息類(lèi)型,即每個(gè) Message 實(shí)現(xiàn)類(lèi)上的 TYPE 靜態(tài)字段
               * @return
               */
              String getType();
          }
          • 定義了泛型 <T> ,需要是 Message 的實(shí)現(xiàn)類(lèi)。
          • 定義的兩個(gè)接口方法。

          2.5.2 AuthMessageHandler

          創(chuàng)建 AuthMessageHandler 類(lèi),處理 AuthRequest 消息。代碼如下:

          @Component
          public class AuthMessageHandler implements MessageHandler<AuthRequest>{
          
              @Override
              public void execute(WebSocketSession session, AuthRequest message) {
                  // 如果未傳遞 accessToken
                  if (StringUtils.isEmpty(message.getAccessToken())) {
                      AuthResponse authResponse=new AuthResponse();
                      authResponse.setCode(1);
                      authResponse.setMessage("認(rèn)證 accessToken 未傳入");
                      WebSocketUtil.send(session, AuthResponse.TYPE,authResponse);
                      return;
                  }
          
                  // 添加到 WebSocketUtil 中,考慮到代碼簡(jiǎn)化,我們先直接使用 accessToken 作為 User
                  WebSocketUtil.addSession(session, message.getAccessToken());
          
                  // 判斷是否認(rèn)證成功。這里,假裝直接成功
                  AuthResponse authResponse=new AuthResponse();
                  authResponse.setCode(0);
                  WebSocketUtil.send(session, AuthResponse.TYPE, authResponse);
              }
          
              @Override
              public String getType() {
                  return AuthRequest.TYPE;
              }
          }
          • 關(guān)于 WebSocketUtil 類(lèi),我們?cè)?「2.6 WebSocketUtil」 中來(lái)看看。

          2.5.3 SendToOneRequest

          創(chuàng)建 SendToOneHandler 類(lèi),處理 SendToOneRequest 消息。代碼如下:

          @Component
          public class SendToOneHandler implements MessageHandler<SendToOneRequest>{
          
              @Override
              public void execute(WebSocketSession session, SendToOneRequest message) {
                  // 這里,假裝直接成功
                  SendResponse sendResponse=new SendResponse();
                  sendResponse.setMsgId(message.getMsgId());
                  sendResponse.setCode(0);
                  WebSocketUtil.send(session, SendResponse.TYPE, sendResponse);
          
                  // 創(chuàng)建轉(zhuǎn)發(fā)的消息
                  SendToUserRequest sendToUserRequest=new SendToUserRequest();
                  sendToUserRequest.setMsgId(message.getMsgId());
                  sendToUserRequest.setContent(message.getContent());
          
                  // 廣播發(fā)送
                  WebSocketUtil.send(message.getToUser(), SendToUserRequest.TYPE, sendToUserRequest);
              }
          
              @Override
              public String getType() {
                  return SendToOneRequest.TYPE;
              }
          }

          2.5.4 SendToAllHandler

          創(chuàng)建 SendToAllHandler 類(lèi),處理 SendToAllRequest 消息。代碼如下:

          @Component
          public class SendToAllHandler implements MessageHandler<SendToAllRequest> {
          
              @Override
              public void execute(WebSocketSession session, SendToAllRequest message) {
                  // 這里,假裝直接成功
                  SendResponse sendResponse=new SendResponse();
                  sendResponse.setMsgId(message.getMsgId());
                  sendResponse.setCode(0);
                  WebSocketUtil.send(session, SendResponse.TYPE, sendResponse);
          
                  // 創(chuàng)建轉(zhuǎn)發(fā)的消息
                  SendToUserRequest sendToUserRequest=new SendToUserRequest();
                  sendToUserRequest.setMsgId(message.getMsgId());
                  sendToUserRequest.setContent(message.getContent());
          
                  // 廣播發(fā)送
                  WebSocketUtil.broadcast(SendToUserRequest.TYPE, sendToUserRequest);
              }
          
              @Override
              public String getType() {
                  return SendToAllRequest.TYPE;
              }
          }

          2.6 WebSocketUtil

          創(chuàng)建 WebSocketUtil 工具類(lèi),代碼如下,主要提供兩方面的功能:

          • Session 會(huì)話的管理
          • 多種發(fā)送消息的方式
          public class WebSocketUtil {
          
              private static final Logger LOGGER=LoggerFactory.getLogger(WebSocketUtil.class);
          
              /**
               * Session 與用戶的映射
               */
              private static final Map<WebSocketSession, String> SESSION_USER_MAP=new ConcurrentHashMap<>();
              /**
               * 用戶與 Session 的映射
               */
              private static final Map<String, WebSocketSession> USER_SESSION_MAP=new ConcurrentHashMap<>();
          
              /**
               * 添加 Session 。在這個(gè)方法中,會(huì)添加用戶和 Session 之間的映射
               * @param session Session
               * @param user 用戶
               */
              public static void addSession(WebSocketSession session, String user) {
                  // 更新 USER_SESSION_MAP
                  USER_SESSION_MAP.put(user, session);
                  // 更新 SESSION_USER_MAP
                  SESSION_USER_MAP.put(session, user);
              }
          
          
              /**
               * 發(fā)送消息給單個(gè)用戶的 Session
               * @param session Session
               * @param type 消息類(lèi)型
               * @param message 消息體
               * @param <T> 消息類(lèi)型
               */
              public static <T extends Message> void send(WebSocketSession  session, String type, T message) {
                  // 創(chuàng)建消息
                  TextMessage  messageText=buildTextMessage(type, message);
                  // 遍歷給單個(gè) Session ,進(jìn)行逐個(gè)發(fā)送
                  sendTextMessage(session, messageText);
              }
              /**
               * 廣播發(fā)送消息給所有在線用戶
               * @param type 消息類(lèi)型
               * @param message 消息體
               * @param <T> 消息類(lèi)型
               */
              public static <T extends Message> void broadcast(String type, T message) {
                  // 創(chuàng)建消息
                  TextMessage messageText=buildTextMessage(type, message);
                  // 遍歷 SESSION_USER_MAP ,進(jìn)行逐個(gè)發(fā)送
                  for (WebSocketSession session : SESSION_USER_MAP.keySet()) {
                      sendTextMessage(session, messageText);
                  }
              }
          
              /**
               * 發(fā)送消息給指定用戶
               * @param user 指定用戶
               * @param type 消息類(lèi)型
               * @param message 消息體
               * @param <T> 消息類(lèi)型
               * @return 發(fā)送是否成功
               */
              public static <T extends Message> boolean send(String user, String type, T message) {
                  // 獲得用戶對(duì)應(yīng)的 Session
                  WebSocketSession session=USER_SESSION_MAP.get(user);
                  if (session==null) {
                      LOGGER.error("[send][user({}) 不存在對(duì)應(yīng)的 session]", user);
                      return false;
                  }
                  // 發(fā)送消息
                  send(session, type, message);
                  return true;
              }
          
              /**
               * 構(gòu)建完整的消息
               * @param type 消息類(lèi)型
               * @param message 消息體
               * @param <T> 消息類(lèi)型
               * @return 消息
               */
              private static <T extends Message> TextMessage  buildTextMessage(String type, T message) {
                  JSONObject messageObject=new JSONObject();
                  messageObject.put("type", type);
                  messageObject.put("body", message);
                  return new TextMessage(messageObject.toString());
              }
          
              /**
               * 真正發(fā)送消息
               *
               * @param session Session
               * @param textMessage 消息
               */
              private static void sendTextMessage(WebSocketSession  session, TextMessage textMessage) {
                  if (session==null) {
                      LOGGER.error("[sendTextMessage][session 為 null]");
                      return;
                  }
                  try {
                      session.sendMessage(textMessage);
                  } catch (IOException e) {
                      LOGGER.error("[sendTextMessage][session({}) 發(fā)送消息{}) 發(fā)生異常",
                              session, textMessage, e);
                  }
              }
          
          }

          3.編寫(xiě)處理類(lèi)MyHandler

          處理類(lèi),在Spring中,處理消息的具體業(yè)務(wù)邏輯,進(jìn)行開(kāi)啟、關(guān)閉連接等操作。

          public class MyHandler extends TextWebSocketHandler implements InitializingBean {
              private Logger logger=LoggerFactory.getLogger(getClass());
              /**
               * 消息類(lèi)型與 MessageHandler 的映射
               * 無(wú)需設(shè)置成靜態(tài)變量
               */
              private final Map<String, MessageHandler> HANDLERS=new HashMap<>();
              @Autowired
              private ApplicationContext applicationContext;
          
          
              @Override
              public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
                  System.out.println("獲取到消息 >> " + message.getPayload());
                  logger.info("[handleMessage][session({}) 接收到一條消息({})]", session, message);
                  // 獲得消息類(lèi)型
                  JSONObject jsonMessage=JSON.parseObject(message.getPayload());
                  String messageType=jsonMessage.getString("type");
                  // 獲得消息處理器
                  MessageHandler messageHandler=HANDLERS.get(messageType);
                  if (messageHandler==null) {
                      logger.error("[onMessage][消息類(lèi)型({}) 不存在消息處理器]", messageType);
                      return;
                  }
                  // 解析消息
                  Class<? extends Message> messageClass=this.getMessageClass(messageHandler);
                  // 處理消息
                  Message messageObj=JSON.parseObject(jsonMessage.getString("body"), messageClass);
                  messageHandler.execute(session, messageObj);
              }
          
              /**
               * 連接建立時(shí)觸發(fā)
               **/
              @Override
              public void afterConnectionEstablished(WebSocketSession session) throws Exception {
                  logger.info("[afterConnectionEstablished][session({}) 接入]", session);
                  // 解析 accessToken
                  String accessToken=(String) session.getAttributes().get("accessToken");
                  // 創(chuàng)建 AuthRequest 消息類(lèi)型
                  AuthRequest authRequest=new AuthRequest();
                  authRequest.setAccessToken(accessToken);
                  // 獲得消息處理器
                  MessageHandler<AuthRequest> messageHandler=HANDLERS.get(AuthRequest.TYPE);
                  if (messageHandler==null) {
                      logger.error("[onOpen][認(rèn)證消息類(lèi)型,不存在消息處理器]");
                      return;
                  }
                  messageHandler.execute(session, authRequest);
              }
          
              /**
               * 關(guān)閉連接時(shí)觸發(fā)
               **/
              @Override
              public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
                  System.out.println("斷開(kāi)連接!");
              }
          
              @Override
              public void afterPropertiesSet() throws Exception {
                  // 通過(guò) ApplicationContext 獲得所有 MessageHandler Bean
                  applicationContext.getBeansOfType(MessageHandler.class).values()
                          // 添加到 handlers 中
                          .forEach(messageHandler -> HANDLERS.put(messageHandler.getType(), messageHandler));
                  logger.info("[afterPropertiesSet][消息處理器數(shù)量:{}]", HANDLERS.size());
              }
          
              private Class<? extends Message> getMessageClass(MessageHandler handler) {
                  // 獲得 Bean 對(duì)應(yīng)的 Class 類(lèi)名。因?yàn)橛锌赡鼙?AOP 代理過(guò)。
                  Class<?> targetClass=AopProxyUtils.ultimateTargetClass(handler);
                  // 獲得接口的 Type 數(shù)組
                  Type[] interfaces=targetClass.getGenericInterfaces();
                  Class<?> superclass=targetClass.getSuperclass();
                  // 此處,是以父類(lèi)的接口為準(zhǔn)
                  while ((Objects.isNull(interfaces) || 0==interfaces.length) && Objects.nonNull(superclass)) {
                      interfaces=superclass.getGenericInterfaces();
                      superclass=targetClass.getSuperclass();
                  }
                  if (Objects.nonNull(interfaces)) {
                      // 遍歷 interfaces 數(shù)組
                      for (Type type : interfaces) {
                          // 要求 type 是泛型參數(shù)
                          if (type instanceof ParameterizedType) {
                              ParameterizedType parameterizedType=(ParameterizedType) type;
                              // 要求是 MessageHandler 接口
                              if (Objects.equals(parameterizedType.getRawType(), MessageHandler.class)) {
                                  Type[] actualTypeArguments=parameterizedType.getActualTypeArguments();
                                  // 取首個(gè)元素
                                  if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) {
                                      return (Class<Message>) actualTypeArguments[0];
                                  } else {
                                      throw new IllegalStateException(String.format("類(lèi)型(%s) 獲得不到消息類(lèi)型", handler));
                                  }
                              }
                          }
                      }
                  }
                  throw new IllegalStateException(String.format("類(lèi)型(%s) 獲得不到消息類(lèi)型", handler));
              }
          }

          4.創(chuàng)建攔截器MyHandshakeInterceptor

          在Spring中提供了websocket攔截器,可以在建立連接之前寫(xiě)些業(yè)務(wù)邏輯,比如校驗(yàn)登錄等。

          public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
          
              /**
              * @Description 握手之前,若返回false,則不建立鏈接
              * @Date 21:59 2021/5/16
              * @return boolean
              **/
              @Override
              public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
                  //獲得 accessToken ,將用戶id放入socket處理器的會(huì)話(WebSocketSession)中
                  if (serverHttpRequest instanceof ServletServerHttpRequest) {
                      ServletServerHttpRequest serverRequest=(ServletServerHttpRequest) serverHttpRequest;
                      attributes.put("accessToken", serverRequest.getServletRequest().getParameter("accessToken"));
                  }
                  // 調(diào)用父方法,繼續(xù)執(zhí)行邏輯
                  return super.beforeHandshake(serverHttpRequest, serverHttpResponse, webSocketHandler, attributes);
              }

          5.創(chuàng)建配置類(lèi)

          @Configuration
          @EnableWebSocket //開(kāi)啟spring websocket功能
          public class WebSocketConfig implements WebSocketConfigurer {
          
              @Override
              public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                  //配置處理器
                  registry.addHandler(this.myHandler(), "/")
                          //配置攔截器
                          .addInterceptors(new MyHandshakeInterceptor())
                          .setAllowedOrigins("*");
              }
          
              @Bean
              public WebSocketHandler myHandler() {
                  return new MyHandler();
              }
          
              @Bean
              public MyHandshakeInterceptor webSocketShakeInterceptor() {
                  return new MyHandshakeInterceptor();
              }
          }

          6.創(chuàng)建啟動(dòng)類(lèi)

          @SpringBootApplication
          public class MyWebsocketApplication {
          
              public static void main(String[] args) {
                  SpringApplication.run(MyWebsocketApplication.class,args);
              }
          }

          7.實(shí)現(xiàn)效果


          打開(kāi)三個(gè)瀏覽器,輸入在線測(cè)試websocket地址:

          http://www.easyswoole.com/wstool.html

          創(chuàng)建三個(gè)連接。分別設(shè)置服務(wù)地址如下:

          • ws://localhost:8080/?accessToken=1001
          • ws://localhost:8080/?accessToken=1002
          • ws://localhost:8080/?accessToken=1003

          發(fā)送單人消息

          {
             tpye: "SEND_TO_ONE_REQUEST",
             boby: {
                          toUser: "1002",
                          msgId: "qwwerqrsfd123",
                          centent: "這是1001發(fā)送給1002的單聊消息"
              }
          }

          可以看到1002收到了1001發(fā)的單聊信息,1003未收到。效果圖如下:

          發(fā)送多人消息

          {
             tpye: "SEND_TO_ALL_REQUEST",
             boby: {
                          msgId: "qwerqcfwwerqrsfd123",
                          centent: "我是一條群聊消息"
              }
          }

          可以看到1001,1002,1003都收到了消息,效果圖如下:


          結(jié)語(yǔ)

          好了,以上就是今天要講的內(nèi)容,本文介紹了WebSocket協(xié)議以及使用它簡(jiǎn)單的實(shí)現(xiàn)及時(shí)聊天的場(chǎng)景。

          感謝大家的閱讀,喜歡的朋友,歡迎點(diǎn)贊支持一下。


          主站蜘蛛池模板: 精品无人乱码一区二区三区| 69福利视频一区二区| 国产情侣一区二区| 亚洲熟妇无码一区二区三区导航| 中文字幕人妻无码一区二区三区| 亚洲国产综合无码一区二区二三区 | 天天看高清无码一区二区三区| 国产怡春院无码一区二区| 精品视频一区二区三区在线播放 | 日本精品夜色视频一区二区| 视频一区视频二区日韩专区| 亚洲AV美女一区二区三区| 韩国一区二区视频| 亚洲老妈激情一区二区三区| 亚洲一区二区三区深夜天堂| 无码欧精品亚洲日韩一区夜夜嗨 | 国产免费私拍一区二区三区| 国产天堂一区二区综合| 日韩一区二区视频| 日韩免费视频一区二区| 国产精品一区二区无线| 亚洲一区二区三区播放在线| 日韩精品一区二区三区不卡| 亚洲欧洲日韩国产一区二区三区| 538国产精品一区二区在线| 国产一区风间由美在线观看| 伊人久久精品无码麻豆一区| 无码国产精品一区二区免费16| 无码精品人妻一区| 一区二区三区免费在线视频 | 久久久综合亚洲色一区二区三区| 国产午夜一区二区在线观看| 国产综合精品一区二区| 亚洲AV成人精品一区二区三区 | 亚洲综合在线成人一区| 国产波霸爆乳一区二区| 亚洲一区二区三区免费| 大香伊蕉日本一区二区| 日韩精品一区二区三区视频| 一区二区三区免费在线视频| 国产91一区二区在线播放不卡|