WebSocket實現多人實時在線聊天

? ? ? 最近公司在做一個婚戀app,需要增加一個功能,實現多人實時在線聊天。基于WebSocket在Springboot中的使用,前端使用vue開發。

一:后端?

1. 引入 websocket 的?maven?依賴

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

2. 進行 config 配置 ServerEndpointExporter 確保【后續在使用 @ServerEndpoint 】時候能被 SpringBoot 自動檢測并注冊

?

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration // 這個類為配置類,Spring 將掃描這個類中定義的 Beans
public class WebSocketConfig {/*** serverEndpointExporter 方法的作用是將 ServerEndpointExporter 注冊為一個 Bean,* 這個 Bean 負責自動檢測帶有 @ServerEndpoint 注解的類,并將它們注冊為 WebSocket 服務器端點,* 這樣,這些端點就可以接收和處理 WebSocket 請求**/@Bean // 這個方法返回的對象應該被注冊為一個 Bean 在 Spring 應用上下文中public ServerEndpointExporter serverEndpointExporter() {// 創建并返回 ServerEndpointExporter 的實例,其中ServerEndpointExporter 是用來處理 WebSocket 連接的關鍵組件return new ServerEndpointExporter();}}

3.后端注冊webSocket服務

? ?后端:廣播給所有客戶端?,在@OnMessage 將單一廣播,切換為群體廣播

? ?存儲所有用戶會話userId

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;//為 ConcurrentHashMap<String,WebSocketServer> 加入一個會話userId
@ServerEndpoint("/chatWebSocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {/***  [關于@OnOpen、@OnMessage、@OnClose、@OnError 中 Session session 的用意]**  Session session: 主要用于代表一個單獨的 WebSocket 連接會話.每當一個 WebSocket 客戶端與服務器端點建立連接時,都會創建一個新的 Session 實例*      標識連接:每個 Session 對象都有一個唯一的 ID,可以用來識別和跟蹤每個單獨的連接。   ——>     可以使用 session.getId() 方法來獲取這個 ID.對于日志記錄、跟蹤用戶會話等方面非常有用。*      管理連接:可以通過 Session 對象來管理對應的 WebSocket 連接,例如發送消息給客戶端、關閉連接等    ——>     session.getBasicRemote().sendText(message) 同步地發送文本消息,*                                                                                                 或者使用 session.getAsyncRemote().sendText(message) 異步地發送.可以調用 session.close() 來關閉 WebSocket 連接。*      獲取連接信息:Session 對象提供了方法來獲取連接的詳細信息,比如連接的 URI、用戶屬性等。    ——>     可以使用 session.getRequestURI() 獲取請求的 URI* **///存儲所有用戶會話//ConcurrentHashMap<String,WebSocketServer> 中String 鍵(String類型)通常是用戶ID或其他唯一標識符。允許服務器通過這個唯一標識符快速定位到對應的 WebSocketServer 實例,從而進行消息發送、接收或其他與特定客戶端相關的操作//ConcurrentHashMap<String,WebSocketServer> 中為什么寫 WebSocketServer 而不是其他,因為 WebSocketServer 作為一個實例,用于存儲每個客戶端連接。//所以在接下來@Onopen等使用中,當使用 ConcurrentHashMap<String,WebSocketServer> 時候,就不能單獨使用 session, 需要添加一個諸如 userId 這樣的會話來作為鍵。private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();private Session session;private String userId="";//建立連接時@OnOpen//獲取會話userId//@PathParam: 是Java JAX-RS API(Java API for RESTful Web Services)的一部分,用于WebSocket和RESTful Web服務. 在WebSocket服務器端,@PathParam 注解用于提取客戶端連接URL中的參數值。public void onOpen(Session session, @PathParam("userId") String userId){this.session = session;             //當前WebSocket連接的 Session 對象存儲在 WebSocketServer 實例 【這樣做是為了在后續的通信過程中(例如在處理消息、關閉連接時),您可以使用 this.session 來引用當前連接的 Session 對象。】this.userId = userId;               //存儲前端傳來的 userId;webSocketMap.put(userId,this);      //WebSocketServer 實例與用戶userId關聯,并將這個關聯存儲在 webSocketMap 中。【其中this: 指的是當前的 WebSocketServer 實例】log.info("會話id:" + session.getId() + "對應的會話用戶:" + userId + "【進行鏈接】");log.info("【websocket消息】有新的連接, 總數:{}", webSocketMap.size());System.out.println("會話id:" + session.getId() + " 對應的會話用戶:" + userId + " 【進行鏈接】");System.out.println("【websocket消息】有新的連接, 總數: "+webSocketMap.size());}//接收客戶端消息@OnMessagepublic void onMessage(String message,Session session) throws IOException {//當從客戶端接收到消息時調用log.info("會話id"+ session.getId() +"對應的會話用戶:" + userId + "的消息:" + message);System.out.println("會話id: "+ session.getId() +" 對應的會話用戶:" + userId + " 的消息: " + message);//修改 onMessage 方法來實現廣播: 當服務器接收到消息時,不是只發送給消息的發送者,而是廣播給所有連接的客戶端。 ——> (實現群聊)//判斷message傳來的消息不為空時,才能在頁面上進行顯示if(message != null && !message.isEmpty()){JSONObject obj = new JSONObject();obj.put("userId", userId);obj.put("message", message);// 封裝成 JSON (Java對象轉換成JSON格式的字符串。)String json = new ObjectMapper().writeValueAsString(obj);for(WebSocketServer client :webSocketMap.values()){client.session.getBasicRemote().sendText(json);}}}//鏈接關閉時@OnClosepublic void onClose(Session session){//關閉瀏覽器時清除存儲在 webSocketMap 中的會話對象。webSocketMap.remove(userId);log.info("會話id:" + session.getId() + "對應的會話用戶:" + userId + "【退出鏈接】");log.info("【websocket消息】有新的連接, 總數:{}", webSocketMap.size());System.out.println("會話id:" + session.getId() + " 對應的會話用戶:" + userId + " 【退出鏈接】");System.out.println("【websocket消息】有新的連接, 總數: "+ webSocketMap.size());}//鏈接出錯時@OnErrorpublic void onError(Session session,Throwable throwable){//錯誤提示log.error("出錯原因 " + throwable.getMessage());System.out.println("出錯原因 " + throwable.getMessage());//拋出異常throwable.printStackTrace();}
}

如果不是群發,一對一對話 單一廣播, onMessage方法如下:

//接收客戶端消息@OnMessagepublic void onMessage(String message,Session session) throws IOException {//當從客戶端接收到消息時調用log.info("會話id:" + session.getId() + ": 的消息" + message);session.getBasicRemote().sendText("回應" + "[" + message + "]");
}

?

二:前端

在?Vue?中使用 WebSocket 并不需要引入專門的庫或框架,因為 WebSocket 是一個瀏覽器內置的 API,可以直接在任何現代瀏覽器中使用。但是,你可能需要編寫一些代碼來適當地處理?WebSocket 連接、消息的發送與接收、錯誤處理以及連接的關閉。

前端: (前端整體沒有發生太大變化,只加了一個userId用于向后端傳輸)

<template><div class="iChat"><div class="container"><div class="content"><div class="item item-center"><span>今天 10:08</span></div><div class="item" v-for="(item, index) in receivedMessage" :key="index" :class="{'item-right':isCurrentUser(item),'item-left':!isCurrentUser(item)}"><!-- 右結構 --><div v-if="isCurrentUser(item)" style="display: flex"><div class="bubble" :class="{'bubble-right':isCurrentUser(item),'bubble-left':!isCurrentUser(item)}">{{item.message}}</div><div class="avatar"><imgsrc="http://192.168.0.134/img/20250701114048Tclu5k.png"/>{{item.userId}}</div></div><!-- 左結構 --><div v-else style="display: flex"><div class="avatar">{{item.userId}}<imgsrc="http://192.168.0.134/img/202507031603386lQ4ft.png"/></div><div class="bubble" :class="{'bubble-right':isCurrentUser(item),'bubble-left':!isCurrentUser(item)}">{{item.message}}</div></div></div></div><div class="input-area"><!-- 文本框 --><textarea v-model="message" id="textarea"></textarea><div class="button-area"><button id="send-btn" @click="sendMessage()">發 送</button></div></div></div></div>
</template><script>
export default {data() {return {ws:null,message:'',receivedMessage:[],currentUserId:"用戶1" + Math.floor(Math.random() * 1000)};},mounted() {this.initWebSocket()},methods: {//建立webSocket連接initWebSocket(){//定義用戶的,并加入到下述鏈接中,且記不要少了/const userId = this.currentUserId;//鏈接接口this.ws = new WebSocket('ws://localhost:9000/jsonflow/chatWebSocket/' + userId)console.log('ws://localhost:9000/jsonflow/chatWebSocket/' + userId);//打開事件this.ws.onopen = function(){console.log("websocket已打開");}//消息事件this.ws.onmessage = (event) => {//接到后端傳來數據 - 并對其解析this.receivedMessage.push(JSON.parse(event.data));console.log(this.receivedMessage)}//關閉事件this.ws.onclose = function() {console.log("websocket已關閉");};//錯誤事件this.ws.onerror = function() {console.log("websocket發生了錯誤");};},//發送消息到服務器sendMessage(){this.ws.send(this.message);this.message = '';},//判斷是否是當前用戶(boolean值)isCurrentUser(item){return item.userId == this.currentUserId}},
};
</script>
<style lang="scss" scoped>
.container{height: 666px;border-radius: 4px;border: 0.5px solid #e0e0e0;background-color: #f5f5f5;display: flex;flex-flow: column;overflow: hidden;
}
.content{width: calc(100% - 40px);padding: 20px;overflow-y: scroll;flex: 1;
}
.content:hover::-webkit-scrollbar-thumb{background:rgba(0,0,0,0.1);
}
.bubble{max-width: 400px;padding: 10px;border-radius: 5px;position: relative;color: #000;word-wrap:break-word;word-break:normal;
}
.item-left .bubble{margin-left: 15px;background-color: #fff;
}
.item-left .bubble:before{content: "";position: absolute;width: 0;height: 0;border-left: 10px solid transparent;border-top: 10px solid transparent;border-right: 10px solid #fff;border-bottom: 10px solid transparent;left: -20px;
}
.item-right .bubble{margin-right: 15px;background-color: #9eea6a;
}
.item-right .bubble:before{content: "";position: absolute;width: 0;height: 0;border-left: 10px solid #9eea6a;border-top: 10px solid transparent;border-right: 10px solid transparent;border-bottom: 10px solid transparent;right: -20px;
}
.item{margin-top: 15px;display: flex;width: 100%;
}
.item.item-right{justify-content: flex-end;
}
.item.item-center{justify-content: center;
}
.item.item-center span{font-size: 12px;padding: 2px 4px;color: #fff;background-color: #dadada;border-radius: 3px;-moz-user-select:none; /*火狐*/-webkit-user-select:none; /*webkit瀏覽器*/-ms-user-select:none; /*IE10*/-khtml-user-select:none; /*早期瀏覽器*/user-select:none;
}.avatar img{width: 42px;height: 42px;border-radius: 50%;
}
.input-area{border-top:0.5px solid #e0e0e0;height: 150px;display: flex;flex-flow: column;background-color: #fff;
}
textarea{flex: 1;padding: 5px;font-size: 14px;border: none;cursor: pointer;overflow-y: auto;overflow-x: hidden;outline:none;resize:none;
}
.button-area{display: flex;height: 40px;margin-right: 10px;line-height: 40px;padding: 5px;justify-content: flex-end;
}
.button-area button{width: 80px;border: none;outline: none;border-radius: 4px;float: right;cursor: pointer;
}/* 設置滾動條的樣式 */
::-webkit-scrollbar {width:10px;
}
/* 滾動槽 */
::-webkit-scrollbar-track {-webkit-box-shadow:inset006pxrgba(0,0,0,0.3);border-radius:8px;
}
/* 滾動條滑塊 */
::-webkit-scrollbar-thumb {border-radius:10px;background:rgba(0,0,0,0);-webkit-box-shadow:inset006pxrgba(0,0,0,0.5);
}
</style>

三:最終效果

?頁面效果:

?

?

后端日志:

四:后續:

? ? ? ?代碼中用戶頭像我是用nginx代理的,寫死了一個頭像。后期前端可以根據系統當前登錄人取他的頭像,選中頭像后,與某個人對話。總之基本對話功能都實現了,歡迎白嫖黨一鍵三連,哈哈哈哈哈哈~~~~~~~~~~~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/90793.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/90793.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/90793.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

學習筆記隨記-FPGA/硬件加速

一、FPGA&#xff1a;Field Programmable Gate Array 現場可編程門陣列 可編程輸入/輸出單元、基本可編程邏輯單元、嵌入式塊RAM、豐富的布線資源、底層嵌入功能單元和內嵌專用硬核。 可編程輸入/輸出單元&#xff08;I/O&#xff09;單元 輸入/輸出&#xff08;Input/Ouput&…

docker宿主機修改ip后起不來問題解決

確保容器已經連接到了正確的網絡。如果沒有&#xff0c;你可以使用以下命令將容器連接到網絡&#xff1a; 1、停止docker網絡 ifconfig docker0 down1. 停止 Docker 服務 sudo systemctl stop docker2. 刪除 docker0 接口 sudo ip link delete docker03、刪除舊的網橋 docker n…

G1 垃圾回收算法詳解

目錄 簡介 G1 GC 的設計目標 內存結構 回收過程 初始標記&#xff08;Initial Mark&#xff09;并發標記&#xff08;Concurrent Mark&#xff09;最終標記&#xff08;Final Mark / Remark&#xff09;篩選回收&#xff08;Cleanup / Evacuation&#xff09; 混合回收&…

JavaEE多線程——鎖策略 CAS synchronized優化

目錄前言1.鎖策略1.1 樂觀鎖和悲觀鎖1.2 重量級鎖和輕量級鎖1.3 掛起等待鎖和自旋鎖1.4 公平鎖和非公平鎖1.5 可重入鎖和不可重入鎖1.6 讀寫鎖2.CAS2.1 CAS的應用2.2 CAS的ABA問題3.synchronized優化3.1鎖升級3.2鎖消除3.3鎖粗化總結前言 本篇文章主要介紹多線程中鎖策略、CAS…

Windows符號鏈接解決vscode和pycharm占用C盤空間太大的問題

Windows符號鏈接解決vscode和pycharm占用C盤空間太大的問題 參考文章&#xff1a;Windows符號鏈接 1、找到vscode和pycharm在C盤的緩存文件夾。 C:\Users\用戶名\AppData\Roaming\Code C:\Users\用戶名\.vscode\extensionsC:\Users\用戶名\AppData\Local\JetBrains C:\Users…

賦能家庭、行業與工業場景,智微智能新一代Twin Lake 全棧智能終端發布

在數字化浪潮席卷全球的今天&#xff0c;智能終端已成為連接物理世界與數字世界的核心樞紐。智微智能基于Intel Twin Lake平臺&#xff0c;推出覆蓋家庭/行業應用及工業物聯網的全場景產品矩陣&#xff0c;為不同場景下的用戶提供高效、可靠的產品和解決方案。Intel Twin Lake架…

復習筆記 31

前言 好好復習。今天距離考研初試還剩一百六十一天。我的時間其實沒剩多少了呀。我得好好加油。 歸并排序 #include<algorithm> #include<iostream>using namespace std;const int N 100010; int n; int a[N], tmp[N];void merge ( int a[], int l, int r ) {if (…

el-tree 懶加載 loadNode

el-tree 是 Element UI 提供的樹形組件&#xff0c;其懶加載功能通過 loadNode 方法實現&#xff0c;可以在用戶展開節點時動態加載子節點數據&#xff0c;避免一次性加載大量數據。下面介紹 loadNode 的具體用法和示例。基本用法loadNode 是 el-tree 的一個重要屬性&#xff0…

【機器學習入門巨詳細】(研0版)二創OPEN MLSYS

自學機器學習&#xff0c;從入門到精通導論機器學習的基本框架設計目標機器學習框架基本組成原理機器學習生態機器學習工作流環境配置數據處理模型定義損失函數和優化器訓練及保存模型測試及驗證模型定義深度神經網絡以層為核心定義神經網絡神經網絡層實現原理自定義神經網絡層…

Excel 轉 JSON by WTSolutions API 文檔

Excel 轉 JSON by WTSolutions API 文檔 簡介 Excel 轉 JSON API 提供了一種簡單的方式將 Excel 和 CSV 數據轉換為 JSON 格式。該 API 接受制表符分隔或逗號分隔的文本數據&#xff0c;并返回結構化的 JSON。 接口端點 POST https://mcp.wtsolutions.cn/excel-to-json-api 請求…

git版本發布

cvs和svn都是集中式版本控制系統,而git是分布式版本控制系統。 1、集中式版本控制系統必須聯網才能工作&#xff0c;如果在局域網內還好&#xff0c;帶寬夠大&#xff0c;速度夠快&#xff0c;可如果在互聯網上&#xff0c;遇到網速慢的話&#xff0c;呵呵。分布式版本控制系統…

138-EMD-KPCA-CPO-CNN-BiGRU-Attention模型!

138-EMD-KPCA-CPO-CNN-BiGRU-Attention基于經驗模態分解和核主成分分析的長短期記憶網絡改進多維時間序列預測MATLAB代碼&#xff01;其中&#xff08;含CPO-CNN-BiGRU-attention、EMD-CPO-CNN-BiGRU-Attention、EMD-KPCA-CPO-CNN-BiGRU-Attention三個模型的對比&#xff09; 可…

系統思考:多元勝過能力

系統思考&#xff1a;從整體出發&#xff0c;打破瓶頸&#xff0c;擁抱多元 我們是否曾經陷入過這樣的困境&#xff1f; 1、專注能力提升&#xff0c;卻無法突破瓶頸&#xff1a;我和團隊日復一日地努力提升個人能力&#xff0c;投入無數時間和精力&#xff0c;但始終無法打破現…

qt樣式整合持續更新中(實測有效的)

// 僅顯示上邊框 一般可以作為直線使用 border-top: 2px solid black; //畫虛線 border-bottom: 2px dashed white; //單個圓角 border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; //透明背景 background:rgba(0,0,0,0); //設置字體 font:15pt; //給button設置…

[java][springboot]@PostConstruct的介紹和用法

在 Spring Boot&#xff08;以及整個 Spring Framework&#xff09;中&#xff0c;PostConstruct 是一個非常常用的注解&#xff0c;用于在 依賴注入完成后 執行一些初始化操作。import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component;Co…

Leaflet面試題及答案(41-60)

查看本專欄目錄 文章目錄 ?? 面試問題及答案(41-60)41. 如何判斷某個點是否在地圖可視區域內?42. 如何動態更新 Marker 位置?43. 如何清除地圖上的所有圖層?44. 如何保存地圖截圖?45. 如何檢測瀏覽器是否支持觸摸?46. Leaflet 是否支持 TypeScript?47. 如何修改默認圖…

Redis事件機制

Redis 采用事件驅動機制來處理大量的網絡IO。它并沒有使用 libevent 或者 libev 這樣的成熟開源方案&#xff0c;而是自己實現一個非常簡潔的事件驅動庫 ae_event。事件機制Redis中的事件驅動庫只關注網絡IO&#xff0c;以及定時器。該事件庫處理下面兩類事件&#xff1a;文件事…

Linux基礎開發工具

目錄 1.寫在前面 2.權限 3.file命令 4.基礎開發工具 1.軟件包管理器 5.編輯器vim 1.寫在前面 我們在上一講解中講解了權限是人事物屬性&#xff0c;還知道了擁有者所屬組其他人這三個概念&#xff0c;知道了33一組&#xff0c;rwx分別代表什么。那么下面我們繼續進行權限…

ICCV2025 特征點檢測 圖像匹配 RIPE

目測對剛性物體效果比較好代碼&#xff1a;https://github.com/fraunhoferhhi/RIPE 論文&#xff1a;https://arxiv.org/abs/2507.04839import cv2 import kornia.feature as KF import kornia.geometry as KG import matplotlib.pyplot as plt import numpy as np import torc…

Ubuntu22.0.4安裝PaddleNLP

Ubuntu22.0.4安裝PaddleNLP環境說明安裝底層框架Paddle安裝PddleNLP1. pip安裝2. 驗證安裝3. 最后問題集錦環境說明 1. miniconda 25.5.1 2. python 3.12.11 3. pip 25.1 4. nvidia 570.144 5. cuda 12.8**注意&#xff1a;**安裝過程可能遇到的一些問題&#xff0c;參考末尾的…