整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Next.js+React聊天室|next.js桌面

          Next.js+React聊天室|next.js桌面端聊天

          近一直在搗鼓Next項目,Next.js是一個基于React.js的服務端ssr渲染框架,能夠讓你的react頁面擁有SEO功能。

          Next.js的star高達58.8K+。一款非常受開發者青睞的React SSR框架。

          https://www.nextjs.cn/
          https://github.com/vercel/next.js

          項目簡介

          Next-WebChat 基于next.js+react+redux+antd+rlayer等技術開發的PC桌面端聊天實例。實現了發現消息/表情gif、圖片/視頻預覽、彈窗菜單、紅包/朋友圈等功能。

          技術實現

          • 技術框架:Next.js+React.js+Redux
          • UI組件庫:Antd(螞蟻金服PC桌面端組件庫)
          • 彈框組件:RLayer
          • 字體圖標:阿里iconfont字體圖標庫
          • 動態圖片:next-images(通過require動態引入本地圖片)

          效果預覽

          react自定義彈窗組件

          項目中用到的所有彈窗功能均是自己開發的RLayer.js彈出框組件。

          前段時間有分享過一篇關于react.js實現自定義彈窗組件,感興趣的可以去看看。

          React.js PC桌面端自定義彈出框組件

          react自定義虛擬滾動條組件

          項目中用到的滾動條均是基于react.js構建的輕量級美化滾動條組件RScroll。支持原生滾動條、是否自動隱藏、滾動條尺寸/層級/顏色等功能。

          由于之前有過一篇分享文章,這里就不作過多的介紹了。

          React.js輕量級虛擬滾動條組件

          Next.js公共布局

          next.js中自定義公共布局模板,在layouts目錄下的index.js頁面。

          function Layout(props) {
              // console.log(props)
              const router=useRouter()
          
              // 登錄驗證攔截
              useEffect(()=> {
                  // ...
              }, [])
          
              return (
              <>
                  {/* 配置公共頭部信息 */}
                  <Head>
                      <title>Next.js聊天室</title>
                      <link rel="icon" href="/favicon.ico" />
                      <meta name="keywords" content="Next.js|React.js|Next.js聊天室|Next.js仿微信|React聊天實例"></meta>
                      <meta name="description" content="Next-WebChat 基于Next.js+React+Redux構建的服務端渲染聊天應用程序"></meta>
                  </Head>
          
                  <div className="next__container flexbox flex-alignc flex-justifyc">
                      <div className={utils.classNames('next__wrapper', props.isWinMaximize&&'maximize')} style={{ backgroundImage: `url(${props.skin})` }}>
                          <div className="next__board flexbox">
                              {/* 右上角按鈕 */}
                              <WinBar {...props} />
          
                              {/* 側邊欄 */}
                              <Sidebar {...props} />
          
                              {/* 中間欄 */}
                              <Middle />
          
                              {/* 主體布局 */}
                              <div className="nt__mainbox flex1 flexbox flex-col">
                                  {props.children}
                              </div>
                          </div>
                      </div>
                  </div>
              </>
              )
          }

          Head組件里配置一些頁面頭部信息,如:標題、關鍵詞、描述及圖標等信息。

          Next.js聊天模塊

          聊天編輯器區域單獨抽離了一個組件,用來進行聊天輸入處理。

          // react實現contenteditable功能
          return (
          	<div 
          		ref={editorRef}
          		className="editor"
          		contentEditable="true"
          		dangerouslySetInnerHTML={{__html: state.editorText}}
          		onClick={handleClicked}
          		onInput={handleInput}
          		onFocus={handleFocus}
          		onBlur={handleBlur}
          		style={{userSelect: 'text', WebkitUserSelect: 'text'}}>
          	</div>
          )

          編輯器支持多行文本輸入、光標處插入表情、粘貼截圖發送、輸入鏈接等功能。

          如上圖:視頻播放是基于rlayer彈窗實現。

          handlePlayVideo=(item, e)=> {
          	rlayer({
          		content: (
          			<div className="flexbox flex-col" style={{height: '100%'}}>
          				<div className="ntDrag__head" style={{color:'#696969',padding:'0 60px 0 15px',lineHeight:'42px'}}><i className="iconfont icon-bofang"></i> 視頻預覽</div>
          				<div className="ntMain__cont flex1 flexbox flex-col">
          					{/* 視頻video */}
          					<video className="vplayer" src={item.videosrc} poster={item.imgsrc} autoPlay preload="auto" controls
          						x5-video-player-fullscreen="true"
          						webkit-playsinline="true"
          						x-webkit-airplay="true"
          						playsInline
          						x5-playsinline="true"
          						style={{height: '100%', width: '100%', objectFit: 'contain', outline: 'none'}}
          					/>
          				</div>
          			</div>
          		),
          		layerStyle: {background: '#f6f5ef'},
          		opacity: .2,
          		area: ['550px', '450px'],
          		drag: '.ntDrag__head',
          		resize: true,
          		maximize: true,
          	})
          }

          聊天區域還支持拖拽發送圖片功能。

          handleDragEnter=(e)=> {
          	e.stopPropagation()
          	e.preventDefault()
          }
          handleDragOver=(e)=> {
          	e.stopPropagation()
          	e.preventDefault()
          }
          handleDrop=(e)=> {
          	e.stopPropagation()
          	e.preventDefault()
          	console.log(e.dataTransfer)
          
          	this.handleFileList(e.dataTransfer)
          }
          // 獲取拖拽文件列表
          handleFileList=(filelist)=> {
          	let files=filelist.files
          	if(files.length >=2) {
          		rlayer.message({icon: 'error', content: '暫時支持拖拽一張圖片', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
          		return false
          	}
          	for(let i=0; i < files.length; i++) {
          		if(files[i].type !='') {
          			this.handleFileAdd(files[i])
          		}else {
          			rlayer.message({icon: 'error', content: '目前不支持文件夾拖拽功能', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
          		}
          	}
          }
          handleFileAdd=(file)=> {
          	if(file.type.indexOf('image')==-1) {
          		rlayer.message({icon: 'error', content: '目前不支持非圖片拖拽功能', shade: true, layerStyle: {background:'#ffefe6',color:'#ff3838'}})
          	}else {
          		let reader=new FileReader()
          		reader.readAsDataURL(file)
          		reader.onload=function() {
          			let img=this.result
          
          			console.log(img)
          		}
          	}
          }

          大家如果感興趣的話,可以試一試。

          好了,今天就分享到這里。希望對大家有所幫助哈!

          基于Nuxt.js+Vue仿微信App聊天實例

          文地址:https://dwz.cn/Z1O56dnu

          作者:Rude3Knife

          前言

          本文中搭建了一個簡易的多人聊天室,使用了WebSocket的基礎特性。

          源代碼來自老外的一篇好文:

          https://www.callicoder.com/spring-boot-websocket-chat-example/

          本文內容摘要:

          • 初步理解WebSocket的前后端交互邏輯
          • 手把手使用 SpringBoot + WebSocket 搭建一個多人聊天室Demo
          • 代碼源碼及其解釋
          • 前端展示頁面

          此外,在下一篇文章中,我們將做到:

          • 對該WebSocket聊天室進行分布式改造,同時部署多臺機器來作為集群,支撐高并發。
          • 保存用戶session,并且在集群上實現session同步,比如實時展示當前在線的用戶!

          正文

          WebSocket多人在線聊天室

          本文工程源代碼:

          https://github.com/qqxx6661/springboot-websocket-demo


          新建工程

          我們新建一個SpringBoot2的項目工程,在默認依賴中,添加websocket依賴:

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


          WebSocket 配置

          我們先來設置websocket的配置,新建config文件夾,在里面新建類WebSocketConfig

          import org.springframework.context.annotation.Configuration;
          import org.springframework.messaging.simp.config.MessageBrokerRegistry;
          import org.springframework.web.socket.config.annotation.*;
          @Configuration
          @EnableWebSocketMessageBroker
          public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
           @Override
           public void registerStompEndpoints(StompEndpointRegistry registry) {
           registry.addEndpoint("/ws").withSockJS();
           }
           @Override
           public void configureMessageBroker(MessageBrokerRegistry registry) {
           registry.setApplicationDestinationPrefixes("/app");
           registry.enableSimpleBroker("/topic");
           }
          }
          


          代碼解釋:

          @EnableWebSocketMessageBroker用于啟用我們的WebSocket服務器。

          我們實現了WebSocketMessageBrokerConfigurer接口,并實現了其中的方法。

          在第一種方法中,我們注冊一個websocket端點,客戶端將使用它連接到我們的websocket服務器。

          withSockJS()是用來為不支持websocket的瀏覽器啟用后備選項,使用了SockJS。

          方法名中的STOMP是來自Spring框架STOMP實現。STOMP代表簡單文本導向的消息傳遞協議。它是一種消息傳遞協議,用于定義數據交換的格式和規則。為啥我們需要這個東西?因為WebSocket只是一種通信協議。它沒有定義諸如以下內容:如何僅向訂閱特定主題的用戶發送消息,或者如何向特定用戶發送消息。我們需要STOMP來實現這些功能。

          在configureMessageBroker方法中,我們配置一個消息代理,用于將消息從一個客戶端路由到另一個客戶端。

          第一行定義了以“/app”開頭的消息應該路由到消息處理方法(之后會定義這個方法)。

          第二行定義了以“/topic”開頭的消息應該路由到消息代理。消息代理向訂閱特定主題的所有連接客戶端廣播消息。

          在上面的示例中,我們使用的是內存中的消息代理。

          之后也可以使用RabbitMQ或ActiveMQ等其他消息代理。


          創建 ChatMessage 實體

          ChatMessage用來在客戶端和服務端中交互

          我們新建model文件夾,創建實體類ChatMessage。

          實體中,有三個字段:

          • type:消息類型
          • content:消息內容
          • sender:發送者

          類型有三種:

          • CHAT: 消息
          • JOIN:加入
          • LEAVE:離開


          創建Controller來接收和發送消息

          創建controller文件夾,在controller文件夾添加類ChatController

          代碼解釋:

          我們在websocket配置中,從目的地以/app開頭的客戶端發送的所有消息都將路由到這些使用@MessageMapping注釋的消息處理方法。

          例如,具有目標/app/chat.sendMessage的消息將路由到sendMessage()方法,并且具有目標/app/chat.addUser的消息將路由到addUser()方法


          添加WebSocket事件監聽

          完成了上述代碼后,我們還需要對socket的連接和斷連事件進行監聽,這樣我們才能廣播用戶進來和出去等操作。

          創建listener文件夾,新建WebSocketEventListener類

          代碼解釋:

          我們已經在ChatController中定義的addUser()方法中廣播了用戶加入事件。因此,我們不需要在SessionConnected事件中執行任何操作。

          在SessionDisconnect事件中,編寫代碼用來從websocket會話中提取用戶名,并向所有連接的客戶端廣播用戶離開事件。


          創建前端聊天室頁面

          我們在src/main/resources文件下創建前端文件,結構類似這樣:

          static
           └── css
           └── main.css
           └── js
           └── main.js
           └── index.html 
          


          1. HTML文件 index.html

          HTML文件包含用于顯示聊天消息的用戶界面。它包括sockjs和stomp 兩個js庫。

          SockJS是一個WebSocket客戶端,它嘗試使用本機WebSockets,并為不支持WebSocket的舊瀏覽器提供支持。STOMP JS是javascript的stomp客戶端。

          筆者在文件里使用了國內的CDN源

          2. JavaScript main.js

          添加連接到websocket端點以及發送和接收消息所需的javascript。

          代碼解釋:

          connect()函數使用SockJS和stomp客戶端連接到我們在Spring Boot中配置的/ws端點。

          成功連接后,客戶端訂閱/topic/public,并通過向/app/chat.addUser目的地發送消息將該用戶的名稱告知服務器。

          stompClient.subscribe()函數采用一種回調方法,只要消息到達訂閱主題,就會調用該方法。

          其它的代碼用于在屏幕上顯示和格式化消息。

          3. CSS main.css

          整個項目結構如下:

          啟動

          啟動SpringBoot項目

          效果入下:

          補充:使用RabbitMQ代替內存作為消息代理

          添加依賴:

          然后將WebSocketConfig類中configureMessageBroker方法改為使用RabbitMq,完成!

          如此一來,便可以通過RabbitMq進行消息的訂閱。

          前兩周經常有大學生小伙伴私信給我,問我可否有償提供畢設幫助,我說暫時沒有這個打算,因為工作實在太忙,現階段無法投入到這樣的領域內,其中有兩個小伙伴又問到我websocket該怎么使用,想給自己的項目中加入這樣的技術。

          剛好我所在的公司有做問診服務,里面就使用了websocket實現聊天通訊,就在閑暇之余專門把部分代碼摘取出來,做了一個簡單的demo分享給他們了,之后想想這塊可以再豐富一下,就花時間又做了一個更完整的小項目出來,且加了詳細的注釋說明,分享給對websocket感興趣的小伙伴們。

          案例展示


          技術棧

          考慮到不同群體對vue等前端技術的接受程度,本案例采用了HTML+CSS+JQuery來實現,代碼直接復制到vue項目中也是一樣的,只是賦值和取值的方式改變而已,很多Java程序員其實對于一門簡單案例的學習不喜歡牽扯太多前端技術,而是單純學習想知道的這門技術就好,太多其他的引入反而影響跟蹤調試,而原始的HTML+JS方式更有利于我們學習和理解,只需要右鍵HTML頁面在瀏覽器打開進行F12調試即可。

          技術

          版本

          Java

          1.8

          SpringBoot

          2.3.12.RELEASE

          WebSocket

          2.3.12.RELEASE

          Hutools

          5.8.0.M1

          SockJS

          1.6.0

          StompJS

          1.7.1


          實現過程

          1、引入依賴

          <!-- websocket -->
          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-websocket</artifactId>
          </dependency>
          
          <!-- Hutools工具類 -->
          <dependency>
              <groupId>cn.hutool</groupId>
              <artifactId>hutool-all</artifactId>
              <version>5.8.0.M1</version>
          </dependency>
          復制代碼


          2、訂閱常量類

          后面的websocket配置類會用到這幾個常量

          stomp端點地址: 連接websocket時的后綴地址,比如127.0.0.1:8888/websocket。

          websocket前綴:前端調服務端消息接口時的URL都加上了這個前綴,比如默認是/send,變成/app/send。

          點對點代理地址:如果websocket配置類中設置了代理路徑,一般點對點訂閱路徑喜歡用/queue。

          廣播代理地址:如果websocket配置類中設置了代理路徑,一般廣播訂閱路徑喜歡用這個/topic。

          package com.simple.ws.constants;
          
          /**
           * <p>
           * websocket常量
           * </p>
           *
           * @author 福隆苑居士,公眾號:【Java分享客?!?
           * @since 2022-04-02 10:11
           */
          public class WsConstants {
          
             // stomp端點地址
             public static final String WEBSOCKET_PATH="/websocket";
          
             // websocket前綴
             public static final String WS_PERFIX="/app";
          
             // 消息訂閱地址常量
             public static final class BROKER {
                // 點對點消息代理地址
                public static final String BROKER_QUEUE="/queue/";
                // 廣播消息代理地址
                public static final String BROKER_TOPIC="/topic";
             }
          }
          復制代碼


          3、WebSocket配置類

          核心內容講解:

          1)、@EnableWebSocketMessageBroker:用于開啟stomp協議,這樣就能支持@MessageMapping注解,類似于@requestMapping一樣,同時前端可以使用Stomp客戶端進行通訊;

          2)、registerStompEndpoints實現:主要用來注冊端點地址、開啟跨域授權、增加攔截器、聲明SockJS,這也是前端選擇SockJS的原因,因為spring項目本身就支持;

          3)、configureMessageBroker實現:主要用來設置客戶端訂閱消息的路徑(可以多個)、點對點訂閱路徑前綴的設置、訪問服務端@MessageMapping接口的前綴路徑、心跳設置等;

          package com.simple.ws.config;
          
          import com.simple.ws.constants.WsConstants;
          import org.springframework.context.annotation.Configuration;
          import org.springframework.messaging.simp.config.MessageBrokerRegistry;
          import org.springframework.web.socket.config.annotation.*;
          
          /**
           * <p>
           * websocket核心配置類
           * </p>
           *
           * @author 福隆苑居士,公眾號:【Java分享客?!?
           * @since 2022/4/1 22:57
           */
          @Configuration
          @EnableWebSocketMessageBroker
          public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
          
             /**
              * 注冊stomp端點
              *
              * @param registry stomp端點注冊對象
              */
             @Override
             public void registerStompEndpoints(StompEndpointRegistry registry) {
                registry.addEndpoint(WsConstants.WEBSOCKET_PATH)
                      .setAllowedOrigins("*")
                      .withSockJS();
             }
          
             /**
              * 配置消息代理
              *
              * @param registry 消息代理注冊對象
              */
             @Override
             public void configureMessageBroker(MessageBrokerRegistry registry) {
          
                // 配置服務端推送消息給客戶端的代理路徑
                registry.enableSimpleBroker(WsConstants.BROKER.BROKER_QUEUE, WsConstants.BROKER.BROKER_TOPIC);
                
                // 定義點對點推送時的前綴為/queue
                registry.setUserDestinationPrefix(WsConstants.BROKER.BROKER_QUEUE);
                
                // 定義客戶端訪問服務端消息接口時的前綴
                registry.setApplicationDestinationPrefixes(WsConstants.WS_PERFIX);
             }
          }
          復制代碼

          特別說明:如果對于配置類中這幾個路徑的設置看不明白,沒關系,后面的前端部分你一看就懂了。


          4、消息接口

          說明:

          1)、消息接口使用@MessageMapping注解,前面講的配置類@EnableWebSocketMessageBroker注解開啟后才能使用這個;

          2)、這里稍微提一下,真正線上項目都是把websocket服務做成單獨的網關形式,提供rest接口給其他服務調用,達到共用的目的,本項目因為不涉及任何數據庫交互,所以直接用@MessageMapping注解,后續完整IM項目接入具體業務后會做一個獨立的websocket服務,敬請關注哦!

          package com.simple.ws.controller;
          
          import com.simple.ws.constants.WsConstants;
          import lombok.extern.slf4j.Slf4j;
          import org.springframework.messaging.handler.annotation.MessageMapping;
          import org.springframework.messaging.simp.SimpMessagingTemplate;
          import org.springframework.web.bind.annotation.*;
          
          import java.util.Map;
          
          /**
           * <p>
           * 消息接口
           * </p>
           *
           * @author 福隆苑居士,公眾號:【Java分享客棧】
           * @since 2022-04-02 12:00
           */
          @RestController
          @RequestMapping("/api")
          @Slf4j
          public class MsgController {
          
             private final SimpMessagingTemplate messagingTemplate;
          
             public MsgController(SimpMessagingTemplate messagingTemplate) {
                this.messagingTemplate=messagingTemplate;
             }
          
             /**
              * 發送廣播消息
              * -- 說明:
              *       1)、@MessageMapping注解對應客戶端的stomp.send('url');
              *       2)、用法一:要么配合@SendTo("轉發的訂閱路徑"),去掉messagingTemplate,同時return msg來使用,return msg會去找@SendTo注解的路徑;
              *       3)、用法二:要么設置成void,使用messagingTemplate來控制轉發的訂閱路徑,且不能return msg,個人推薦這種。
              *
              * @param msg 消息
              */
             @MessageMapping("/send")
             public void sendAll(@RequestParam String msg) {
          
                log.info("[發送消息]>>>> msg: {}", msg);
          
                // 發送消息給客戶端
                messagingTemplate.convertAndSend(WsConstants.BROKER.BROKER_TOPIC, msg);
             }
             
          復制代碼


          5、前端項目結構

          很簡單,就是HTML+CSS和幾個js文件,sockjs和stompjs就是和服務端通信的實現,可以從GitHub官網下載,而websocket.js是我們自己封裝的和服務端通信的內容。

          6、Stomp客戶端使用

          頁面結構樣式這里就省略不講了,直接開始正文。

          stompjs,是對websocket原生使用的一層封裝,提供了更簡單的調用方法。

          這里先看看我們自己封裝的websocket.js的實現:

          1)、聲明URL

          也就是服務端配置類的端點地址

          var url="http://127.0.0.1:8888/websocket"; // 改成自己的服務端地址
          復制代碼


          2)、建立websocket連接

          stompClient=Stomp.over(socket)就是覆蓋了sockjs使用自己的客戶端來操作ws;

          進入stompClient.connect()中就代表連接成功,可以進行自己的業務處理比如廣播通知某人上線等等,重要的是連接成功后要聲明訂閱列表,這樣服務端轉發的消息才會根據這些訂閱地址發送過來,否則收不到;

          最后就是有一個回調可以捕獲異常情況,在里面可以做一些操作比如重連等等。

          /**
           * 連接
           */
          function connect() {
              userId=GetUrlParam("userId");
              var socket=new SockJS(url, null, { timeout: 15000});
              stompClient=Stomp.over(socket); // 覆蓋sockjs使用stomp客戶端
              stompClient.connect({}, function (frame) {
          
                  console.log('frame: ' + frame)
          
                  // 連接成功后廣播通知
                  sendNoticeMsg(userId, "in");
          
                  /**
                   * 訂閱列表,訂閱路徑和服務端發消息路徑一致就能收到消息。
                   * -- /topic: 服務端配置的廣播訂閱路徑
                   * -- /queue/: 服務端配置的點對點訂閱路徑
                   */
                  stompClient.subscribe("/topic", function (response) {
                          showMsg(response.body);
                  });
          
                  stompClient.subscribe("/queue/" + userId + "/topic", function (response) {
                          showMsg(response.body);
                  });
          
                  // 異常時進行重連
                  }, function (error) {
                      console.log('connect error: ' + error)
                      if (reConnectCount > 10) {
                              console.log("溫馨提示:您的連接已斷開,請退出后重新進入。")
                              reConnectCount=0;
                      } else {
                              wsReconnect && clearTimeout(wsReconnect);
                              wsReconnect=setTimeout(function () {
                                      console.log("開始重連...");
                                      connect();
                                      console.log("重連完畢...");
                                      reConnectCount++;
                              }, 1000);
                      }
                  }
              )
          }
          復制代碼


          3)、斷開websocket連接

          斷開很簡單,但要注意一點,不要根據關閉窗口或瀏覽器的事件來控制斷開,這是一個誤區,首先瀏覽器兼容性差異較大,傳統的js在監聽窗口關閉事件的兼容性上是很差的,這個可以自己試驗就知道了,有些瀏覽器可以有些不可以;

          其次,可以參考QQ,你自己在退出一個群聊的時候實際上你就單純是關閉了,并沒有離線,而是你退出QQ時才真正離線,所以真正控制這個斷開方法的位置應該是點擊退出按鈕時,這一點不要理解錯了。

          /**
           * 斷開
           */
          function disconnect() {
              if (stompClient !=null) {
                  // 斷開連接時進行廣播通知
                  sendNoticeMsg(userId, "out");
                  // 斷開連接
                  stompClient.disconnect(function(){
                          // 有效斷開的回調
                          console.log(userId + "斷開連接....")
                  });
              }
          }
          復制代碼


          4)、消息滾動到底部

          這個沒什么說的,在進入頁面以及發送消息后渲染頁面時使用即可。

          // 消息窗口滾動到底部
          function scrollBotton(){
              var div=document.getElementById("content");
              div.scrollTop=div.scrollHeight;
          }
          復制代碼


          5)、聊天消息渲染到頁面

          這里就是單純的JQuery操作了,注意的一點是這里加了個type判斷是系統消息還是聊天消息,在本案例中,系統消息就是某人上下線的提示,聊天消息就是發送出來的內容。

          在vue這樣的框架中,這部分的操作其實會很簡單。

          /**
           * 聊天消息渲染到頁面中
           */
          function showMsg(obj) {
              obj=JSON.parse(obj);
              var userId=obj.userId;
              var sendTime=obj.sendTime;
              var info=obj.info;
              var type=obj.type;
          
              if (1===type) {
                  // 聊天消息
                  console.log("聊天消息...")
                  var msgHtml="<div class=\"msg\" id=\"msg\">" + 
                                    "  <div class=\"first-line\">" + 
                                    "	   <div class=\"userName\" id=\"userName\">" + userId + "</div>" +  
                                    "	   <div class=\"sendTime\" id=\"sendTime\">" + sendTime + "</div>" + 
                                    "  </div>" + 
                                    "  <div class=\"second-line\">" + 
                                    "    <div class=\"sendMsg\" id=\"sendMsg\">" + info + "</div>" + 
                                    "  </div>" + 
                                    "</div>";
          
                  // 渲染到頁面	
                  $("#content").html($("#content").html() + "\n" + msgHtml);
          
          } else if (2===type) {
                  // 系統消息
                  console.log("系統消息...")
                  var msgHtml="<div class=\"notice\">" + 
                                          "<div class=\"notice-info\">" + info + "</div>" + 
                                    "</div>";
          
                  // 渲染到頁面	
                  $("#content").html($("#content").html() + "\n" + msgHtml);
              }
          
              // 消息窗口滾動到底部
              scrollBotton();
          }
          復制代碼


          6)、發送群聊消息

          這里傳遞的obj定義了一個消息體,就是一個對象,真正項目中也是這般使用,而不是單純傳遞一個文本;

          stompClient.send中的url,其中/app是服務端配置類中設置的ApplicationDestinationPrefixes,而/send就是controller接口中@MessageMapping("/send")的路徑,兩個加在一起就是這里前端發送的路徑,少一個或多一個斜杠都會導致服務端收不到消息。

          /**
          * 發送群聊消息
          * -- 這里我們傳遞消息體對象 
          * 	{
          *         "userId": userId, // 發送者
          *         "sendTime": sendTime, // 發送時間
          *         "info": info, // 發送內容
          *         "type": 1  // 消息類型,1-聊天消息,2-系統消息
          *       }
          */
          function sendAll(obj) {
              stompClient.send("/app/send", {}, JSON.stringify(obj));
          }
          復制代碼


          7)、發送系統消息

          就是傳遞type=2即可,info做了下判斷返回不同的消息內容。

          /**
           * 發送系統通知消息
           * @param userId 用戶id 
           */
          function sendNoticeMsg(userId, action) {
              var obj={
                  "userId": userId,
                  "sendTime": new Date().Format("HH:mm:ss"),
                  "info": "in"===action ? userId + "進入房間" : userId + "離開房間",
                  "type": 2
              }
              sendAll(obj);
          }
          復制代碼


          7、聊天頁發消息

          index.html就是聊天主頁面,直接調用我們前面封裝好的websocket.js方法即可。

          主要步驟為:進入頁面時建立websocket連接 --> 獲取登錄用戶信息 --> 監聽按鈕點擊事件和鍵盤事件 --> 發送websocket消息 --> 清空文本框內容

          這樣,一旦發送消息成功,服務端就可以看到接收到的消息體并根據發送路徑進行轉發,前端websocket.js中訂閱列表中的路徑一旦和服務端轉發的路徑匹配上,就會收到消息,我們把消息渲染到頁面上即可。

          這個過程其實也就是websocket全雙工通信的原理

          <script>
          
              $(function() {
                  // 啟動websocket
                  connect();
          
                  // 獲取用戶信息
                  getUser();
          
                  // 消息窗口滾動到底部
                  scrollBotton();
          
                  // 監聽鍵盤Enter鍵,要用keyup,否則無法清除換行符。
                  $("#send-info").keyup(function(e) {
                      var eCode=e.keyCode ? e.keyCode : e.which ? e.which : e.charCode;
                      if (eCode==13){
                          $("#send-btn").click();
                      }
                  });
          
                  // 監聽發送按鈕點擊事件
                  $("#send-btn").click(function() {
                      send();
                  });
          
                  // 監聽退出按鈕點擊事件
                  $("#exit-btn").click(function() {
                      layer.confirm('你確定要退出嗎?', {
                          time: 0, // 不自動關閉
                          btn: ['確定退出', '再玩玩'],
                          yes: function(index){
                              layer.close(index);
                              disconnect();
                              window.location.href='login.html';
                          }
                      });
                  });
          
          
              });
          
              // 獲取用戶信息
              function getUser() {
                  var userId=GetUrlParam("userId");
                  $("#userId").text(userId);
              }
          
              // 發送廣播消息,這里定義一個type:1-聊天消息,2-系統消息。
              function send() {
                  var userId=$("#userId").text().trim();
                  var sendTime=new Date().Format("HH:mm:ss");
                  var info=$("#send-info").val().replace("\n", "");
                  var msg={
                      "userId": userId,
                      "sendTime": sendTime,
                      "info": info,
                      "type": 1
                  }
                  // 發送消息
                  sendAll(msg);
          
                  // 清空文本域內容
                  $("#send-info").val("");
              }
          
          </script>
          復制代碼


          避坑指南

          1)、版本問題,經本人專門花時間測試,SpringBoot2.4.0以下版本才能整合SockJS和StompJS成功,以上的版本都不行,會報 Main site uses: "1.6.0", the iframe: "1.0.0" 這樣的錯誤,將StompJS換成低版本也不行,所以這里整合時用了SpringBoot2.3.12.RELEASE版本,但這個沒關系,websocket服務一般都是單獨做成一個服務的,如果是微服務,你的其他業務服務使用高版本的SpringBoot就行了;

          2)、監聽窗口關閉事件不可取,這個在前面已經講過了,瀏覽器兼容性差,我試過好幾個瀏覽器監聽效果都各不相同甚至完全無效,其次本身這樣操作也不合理,我們只要保證退出時觸發斷開事件即可,無需在這樣的事情上浪費時間,可以參考QQ;

          3)、服務端編寫消息接口時推薦使用SimpMessagingTemplate來控制發送,而不是@SendTo注解,因為前者更符合程序員開發思路,后期獨立websocket服務暴露rest接口時也更簡單;

          4)、配置類中其實還有很多其他配置項,比如心跳配置、攔截器配置等,本案例沒有加入進來,因為我自己公司的項目中其實使用過心跳,但后來又去掉了,因為對這塊了解不深入的話貿然使用容易出現稀奇古怪的問題。

          講個趣事,我們前端工程師當初就因為心跳這塊調試了挺久,上線后依然會出現時好時壞的情況,因為他之前也沒做過websocket都是現學的,而且線上環境和測試環境差異難明,包含程序缺陷、網絡環境因素等等,后來我們決定去掉心跳檢測,之后兩年也沒出任何問題。

          所以有時候保證項目穩定性反而更有用,但處于學習的角度而言,心跳檢測是一定需要的,否則所有的socket框架也不會專門提供這樣的方案了。


          總結

          SpringBoot+websocket的實現其實不難,你可以使用原生的實現,也就是websocket本身的OnOpen、OnClosed等等這樣的注解來實現,以及對WebSocketHandler的實現,類似于netty的那種使用方式,而且原生的還提供了對websocket的監聽,服務端能更好的控制及統計。

          但根據我個人的經驗而言,真實項目中還是使用Stomp實現的居多,因為獨立服務更方便,便于后期搭建集群環境做橫向擴展,且內置的方法也很簡單,既然如此,我們還是以主流實現方式為準來學習吧。


          原文鏈接:https://juejin.cn/post/7083020131412115464


          主站蜘蛛池模板: 美女福利视频一区二区| 精品黑人一区二区三区| 国产福利91精品一区二区三区 | 亚洲毛片不卡av在线播放一区| 亚洲av综合av一区| 国产观看精品一区二区三区| 无码一区二区三区在线| 91福利视频一区| 国产精品伦一区二区三级视频| 国产91一区二区在线播放不卡| 八戒久久精品一区二区三区 | 亚洲熟妇AV一区二区三区浪潮| 日本成人一区二区| 人妻免费一区二区三区最新| 亚洲成a人一区二区三区| 国产日韩精品一区二区三区| 日韩色视频一区二区三区亚洲| 亚洲一区二区三区高清不卡 | 国产精品一区二区无线| 亚洲一区二区三区丝袜| 亚洲变态另类一区二区三区| 亚洲熟妇无码一区二区三区 | 无码丰满熟妇浪潮一区二区AV| 无码福利一区二区三区| 无码国产精品一区二区免费| 亚无码乱人伦一区二区| 国产激情无码一区二区三区| 福利视频一区二区牛牛| 久久一区二区精品综合| 日本不卡一区二区视频a| 亚洲国产国产综合一区首页| 99热门精品一区二区三区无码| 国产手机精品一区二区| 国内精品视频一区二区八戒| 国产成人一区二区动漫精品 | 国产AV午夜精品一区二区三| 国产一区二区三区久久精品| 日韩精品无码免费一区二区三区| 亚洲无圣光一区二区 | 精品亚洲AV无码一区二区三区| 一区二区三区在线观看|