java配置webSocket、前端使用uniapp連接

一、這個管理系統是基于若依框架,配置webSocKet的maven依賴

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

二、配置類配置webSocket的端點和相關的參數

1、WebSocketConfig - webSocket配置類

注意:ws://yourdomain:port/ws/order?token=yourTokenValue。
可以使用cpolar 工具把IP地址解析成可訪問的域名。

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate WebSocketHandler webSocketHandler;/*** 注冊websocket的端點* 客戶端連接格式: ws://yourdomain:port/ws/order?token=yourTokenValue* token參數必須提供,系統會通過token從Redis獲取對應的openId用于用戶識別* @param registry WebSocketHandlerRegistry*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(webSocketHandler, "/ws/order").setAllowedOrigins("*"); // 允許跨域訪問}/*** 配置WebSocket服務器的參數* 包括:連接超時時間、心跳超時時間、最大消息大小等* @return ServletServerContainerFactoryBean*/@Beanpublic ServletServerContainerFactoryBean createWebSocketContainer() {ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();// 設置異步發送超時時間為25秒container.setAsyncSendTimeout(25000L);// 設置最大會話空閑時間為60秒container.setMaxSessionIdleTimeout(60000L);// 設置最大文本消息緩沖區大小為8KBcontainer.setMaxTextMessageBufferSize(8192);// 設置最大二進制消息緩沖區大小為8KBcontainer.setMaxBinaryMessageBufferSize(8192);return container;}
}

2、WebSocketHandler - webSocket處理器
?

@Component
@Slf4j
public class WebSocketHandler extends TextWebSocketHandler {@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 用線程安全的集合來管理所有連接的 WebSocket 會話private static final Set<WebSocketSession> sessions = new CopyOnWriteArraySet<>();// 使用ConcurrentHashMap來存儲openId到session的映射關系private static final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>();// 使用ConcurrentHashMap來存儲session到openId的映射關系(反向映射)private static final Map<WebSocketSession, String> sessionUsers = new ConcurrentHashMap<>();// 記錄每個session最后一次活躍時間private static final Map<String, Long> sessionLastActiveTime = new ConcurrentHashMap<>();// 心跳檢查的定時任務執行器private final ScheduledExecutorService heartbeatScheduler = Executors.newSingleThreadScheduledExecutor();// 心跳超時時間,單位毫秒private static final long HEARTBEAT_TIMEOUT = 50000L; // 50秒// 用于解析JSON的對象映射器private static final ObjectMapper objectMapper = new ObjectMapper();/*** 構造方法,啟動心跳檢測任務*/public WebSocketHandler() {// 每15秒檢查一次心跳heartbeatScheduler.scheduleAtFixedRate(this::checkHeartbeats, 15, 15, TimeUnit.SECONDS);}/*** 心跳檢查方法,清理那些超時的連接*/private void checkHeartbeats() {long currentTime = System.currentTimeMillis();for (Map.Entry<String, Long> entry : sessionLastActiveTime.entrySet()) {String openId = entry.getKey();long lastActive = entry.getValue();// 如果超過超時時間沒有活動,則關閉會話if (currentTime - lastActive > HEARTBEAT_TIMEOUT) {WebSocketSession session = userSessions.get(openId);if (session != null && session.isOpen()) {try {log.warn("會話心跳超時,主動斷開連接 - openId: {}, 上次活躍: {}ms前", openId, currentTime - lastActive);session.close(CloseStatus.NORMAL);} catch (IOException e) {log.error("關閉超時WebSocket會話異常 - openId: {}, 錯誤: {}", openId, e.getMessage());} finally {// 確保從會話映射中移除sessions.remove(session);sessionUsers.remove(session);userSessions.remove(openId);sessionLastActiveTime.remove(openId);}} else {// 會話已關閉或不存在,直接清理userSessions.remove(openId);sessionLastActiveTime.remove(openId);}}}}/*** 新客戶端連接時,加入到 sessions 集合中* @param session 會話*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.add(session);// 從URL中獲取token參數,格式應為 /ws/order?token=xxxString token = extractToken(session);if (token != null) {// 從Redis中獲取對應的openIdString openId = getOpenIdFromToken(token);if (openId != null) {userSessions.put(openId, session);sessionUsers.put(session, openId);sessionLastActiveTime.put(openId, System.currentTimeMillis()); // 記錄初始活躍時間log.info("WebSocket連接已建立 - token: {}, openId: {}, 當前連接數: {}", token, openId, sessions.size());} else {log.warn("找不到token對應的openId,token可能已過期 - token: {}", token);// 可以選擇關閉這個無效的連接session.close(CloseStatus.NOT_ACCEPTABLE);}} else {log.warn("WebSocket連接未提供token參數,無法識別用戶");// 可以選擇關閉這個無效的連接session.close(CloseStatus.NOT_ACCEPTABLE);}}/*** 客戶端斷開連接時,從 sessions 集合中移除* @param session 會話* @param status*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {sessions.remove(session);// 從用戶會話映射中也移除String openId = sessionUsers.remove(session);if (openId != null) {userSessions.remove(openId);sessionLastActiveTime.remove(openId);log.info("WebSocket連接已關閉 - openId: {}, 狀態: {}", openId, status);}}/*** 處理收到的文本消息* 對于心跳消息進行特殊處理*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String openId = sessionUsers.get(session);String payload = message.getPayload();try {// 嘗試解析為JSONJsonNode jsonNode = objectMapper.readTree(payload);// 檢查是否是心跳消息if (jsonNode.has("type") && "ping".equals(jsonNode.get("type").asText())) {// 更新最后活躍時間if (openId != null) {sessionLastActiveTime.put(openId, System.currentTimeMillis());}// 發送pong響應session.sendMessage(new TextMessage("{\"type\":\"pong\",\"time\":" + System.currentTimeMillis() + "}"));return;}} catch (Exception e) {// 不是JSON格式的消息,忽略錯誤繼續處理}// 更新最后活躍時間if (openId != null) {sessionLastActiveTime.put(openId, System.currentTimeMillis());}log.debug("收到消息 - openId: {}, 內容: {}", openId, payload);// 在這里可以添加其他消息處理邏輯}/*** 從WebSocketSession中提取token* @param session WebSocket會話* @return token,如果不存在則返回null*/private String extractToken(WebSocketSession session) {String query = session.getUri().getQuery();if (query != null && query.contains("token=")) {String[] params = query.split("&");for (String param : params) {String[] keyValue = param.split("=");if (keyValue.length == 2 && "token".equals(keyValue[0])) {log.info("WebSocket連接已獲取token - token: {}", keyValue[1]);return keyValue[1];}}}return null;}/*** 從token獲取對應的openId* @param token 用戶token* @return openId,如果token無效則返回null*/private String getOpenIdFromToken(String token) {if (token == null || token.isEmpty()) {return null;}try {// 從Redis中獲取token對應的openIdreturn stringRedisTemplate.opsForValue().get(WECHAT_KEY + token);} catch (Exception e) {log.error("從Redis獲取token信息異常 - token: {}, 錯誤: {}", token, e.getMessage());return null;}}/*** 發送支付成功的通知給所有連接的客戶端* @param message 消息體*/public void sendPaymentSuccessNotification(String message) {for (WebSocketSession session : sessions) {try {// 通過 WebSocket 向每個客戶端發送消息session.sendMessage(new TextMessage(message));} catch (IOException e) {log.error("發送支付成功通知失敗", e);}}}/*** 向指定用戶發送消息* @param openId 用戶的openId* @param message 消息內容* @return 是否發送成功*/public boolean sendMessageToUser(String openId, String message) {WebSocketSession session = userSessions.get(openId);if (session != null && session.isOpen()) {try {session.sendMessage(new TextMessage(message));log.info("消息已發送給用戶 - openId: {}", openId);return true;} catch (IOException e) {log.error("發送消息給用戶失敗 - openId: {}", openId, e);return false;}} else {log.info("用戶未通過WebSocket連接 - openId: {}", openId);return false;}}/*** 向所有用戶發送心跳檢測消息*/public void sendHeartbeat() {String heartbeatMsg = "{\"type\":\"heartbeat\",\"time\":" + System.currentTimeMillis() + "}";for (WebSocketSession session : sessions) {if (session.isOpen()) {try {session.sendMessage(new TextMessage(heartbeatMsg));} catch (IOException e) {log.error("發送心跳消息失敗", e);}}}}
}

注意:這里發送消息給指定用戶需要前端傳遞token,獲取存儲在redis中的openId(微信小程序用戶標識)

3、發送消息我定義了一個定時器發送消息和心跳測試

3.1、根據自己業務封裝的消息體
?

@ApiModel(value = "MessageVo",discriminator = "websocket的消息體")
public class MessageVo {@ApiModelProperty(value = "消息標題",dataType = "string")private String title;@ApiModelProperty(value = "消息內容",dataType = "string")private String content;@ApiModelProperty(value = "車牌號碼",dataType = "string")private String plateNumber;@ApiModelProperty(value = "訂單編號",dataType = "string")private String orderNumber;@ApiModelProperty(value = "創建時間",dataType = "date")private Date createTime;}
/*** 定時發送提醒消息給待過磅狀態的用戶* 每1分鐘執行一次,提醒用戶進行過磅操作*/public void sendWeighingReminder() {log.info("開始執行待過磅用戶提醒任務");try {// 查詢所有待過磅的訂單WeighingRecords pendingQuery = new WeighingRecords();pendingQuery.setStatus(0L); // 待過磅List<WeighingRecords> pendingWeighingOrders = weighingRecordsMapper.selectWeighingRecordsList(pendingQuery);// 如果沒有待過磅訂單,直接返回if (pendingWeighingOrders == null || pendingWeighingOrders.isEmpty()) {log.info("沒有查詢到待過磅訂單,跳過發送提醒");return;}log.info("查詢到 {} 條待過磅訂單,開始發送提醒", pendingWeighingOrders.size());int successCount = 0;// 遍歷所有待過磅訂單,發送提醒消息for (WeighingRecords order : pendingWeighingOrders) {// 檢查是否有有效的openIdString openId = order.getOpenId();if (openId == null || openId.trim().isEmpty()) {log.warn("訂單 {} 缺少有效的openId,無法發送提醒", order.getOrderNumber());continue;}// 創建消息體MessageVo messageVo = new MessageVo();messageVo.setTitle("過磅提醒");messageVo.setContent("您有一條待過磅的訂單,請及時前往過磅點進行過磅操作。");messageVo.setOrderNumber(order.getOrderNumber());messageVo.setPlateNumber(order.getPlateNumber()); // 設置車牌號messageVo.setCreateTime(DateUtils.getNowDate());try {// 轉換為JSON字符串String messageJson = objectMapper.writeValueAsString(messageVo);// 直接使用openId發送消息(WebSocketHandler內部會通過openId查找對應的會話)boolean sent = webSocketHandler.sendMessageToUser(openId, messageJson);if (sent) {successCount++;log.info("成功向用戶 {} 發送過磅提醒消息,訂單號: {}", openId, order.getOrderNumber());} else {log.info("用戶 {} 未連接WebSocket,無法發送過磅提醒消息,訂單號: {}", openId, order.getOrderNumber());}} catch (JsonProcessingException e) {log.error("消息序列化異常,訂單號: {}, 錯誤: {}", order.getOrderNumber(), e.getMessage());} catch (Exception e) {log.error("發送消息異常,訂單號: {}, 錯誤: {}", order.getOrderNumber(), e.getMessage());}}log.info("過磅提醒任務完成,共嘗試: {} 條,成功: {} 條", pendingWeighingOrders.size(), successCount);} catch (Exception e) {log.error("過磅提醒任務異常: {}", e.getMessage(), e);}}/*** 定期發送心跳消息,保持WebSocket連接活躍* 每25秒執行一次,低于WebSocketConfig中設置的60秒超時時間*/public void sendHeartbeat() {log.debug("開始執行WebSocket心跳任務");try {webSocketHandler.sendHeartbeat();log.debug("WebSocket心跳消息發送完成");} catch (Exception e) {log.error("WebSocket心跳任務異常: {}", e.getMessage(), e);}}

4、由于這個管理系統是基于若依所以需要配置鑒權,否則會被攔截
這個是部分配置代碼

@Beanprotected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{return httpSecurity// CSRF禁用,因為不使用session.csrf(csrf -> csrf.disable())// 禁用HTTP響應標頭.headers((headersCustomizer) -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})// 認證失敗處理類.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))// 基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解標記允許匿名訪問的url.authorizeHttpRequests((requests) -> {permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());// 對于登錄login 注冊register 驗證碼captchaImage 允許匿名訪問requests.antMatchers("/login", "/register", "/captchaImage","/weiXin/login","/weiXin/returnNotify","/ws/**").permitAll()
..........}

注意:端點配置的是“/ws/order",所以在這了配置為”/ws/**“

三、小程序端的部分代碼配置

注意:需要在路徑上面傳遞token,為了后端獲取openId向指定用戶發送消息

這個是小程序的webSocket的地址示例:“wss://5aa7e45c.r11.cpolar.top/ws/order?token=${this.token}”

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

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

相關文章

基于Yolov8+PyQT的老人摔倒識別系統源碼

概述 ??基于Yolov8PyQT的老人摔倒識別系統??&#xff0c;該系統通過深度學習算法實時檢測人體姿態&#xff0c;精準識別站立、摔倒中等3種狀態&#xff0c;為家庭或養老機構提供及時預警功能。 主要內容 ??完整可運行代碼?? 項目采用Yolov8目標檢測框架結合PyQT5開發…

Oracle 創建外部表

找別人要一下數據&#xff0c;但是他發來一個 xxx.csv 文件&#xff0c;怎么辦&#xff1f; 1、使用視圖化工具導入 使用導入工具導入&#xff0c;如 DBeaver&#xff0c;右擊要導入的表&#xff0c;選擇導入數據。 選擇對應的 csv 文件&#xff0c;下一步就行了&#xff08;如…

【華為OD- B卷 01 - 傳遞悄悄話 100分(python、java、c、c++、js)】

【華為OD- B卷 01 - 傳遞悄悄話 100分(python、java、c、c++、js)】 題目 給定一個二叉樹,每個節點上站一個人,節點數字表示父節點到該節點傳遞悄悄話需要花費的時間。 初始時,根節點所在位置的人有一個悄悄話想要傳遞給其他人,求二叉樹所有節點上的人都接收到悄悄話花…

房貸利率計算前端小程序

利率計算前端小程序 視圖效果展示如下&#xff1a; 在這里插入代碼片 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0&qu…

自制操作系統day8 (鼠標數據取得、通往32位模式之路、A20GATE、切換到保護模式、控制寄存器cr0-cr4以及cr8、ALIGNB)

day8 鼠標數據取得方法 fifo8_init(&mousefifo, 128, mousebuf); for (;;) { io_cli(); if (fifo8_status(&keyfifo) fifo8_status(&mousefifo) 0) { io_stihlt(); } else { if (fifo8_status(&keyfifo) ! 0) { i fifo8_get(&keyfifo); io_sti(); spr…

IP大科普:住宅IP、機房IP、原生IP、雙ISP

不同類型的IP在跨境電商、廣告營銷、網絡技術、數據收集等領域都有廣泛應用&#xff0c;比如常見的住宅IP、機房IP、原生IP、雙ISP等&#xff0c;這些IP分別都有什么特點&#xff0c;發揮什么作用&#xff0c;適合哪些業務場景&#xff1f; 一、IP類型及其作用 1.住宅IP 住宅…

Elasticsearch面試題帶答案

Elasticsearch面試題帶答案 Elasticsearch面試題及答案【最新版】Elasticsearch高級面試題大全(2025版),發現網上很多Elasticsearch面試題及答案整理都沒有答案,所以花了很長時間搜集,本套Elasticsearch面試題大全,Elasticsearch面試題大匯總,有大量經典的Elasticsearch面…

Eigen與OpenCV矩陣操作全面對比:最大值、最小值、平均值

功能對比總表 功能Eigen 方法OpenCV 方法主要區別最大值mat.maxCoeff(&row, &col)cv::minMaxLoc(mat, NULL, &maxVal, NULL, &maxLoc)Eigen需要分開調用&#xff0c;OpenCV一次獲取最小值mat.minCoeff(&row, &col)cv::minMaxLoc(mat, &minVal, NU…

echarts之雙折線漸變圖

vue3echarts實現雙折線漸變圖 echarts中文官網&#xff1a;https://echarts.apache.org/examples/zh/index.html 效果圖展示&#xff1a; 整體代碼如下&#xff1a; <template><div id"lineChart" style"width:100%;height:400px;"></di…

MD編輯器推薦【Obsidian】含下載安裝和實用教程

為什么推薦 Obsidian &#xff1f; 免費 &#xff08;Typora 開始收費了&#xff09;Typora 實現的功能&#xff0c;它都有&#xff01;代碼塊可一鍵復制 文件目錄支持文件夾 大綱支持折疊、搜索 特色功能 – 白板 特色功能 – 關系圖譜 下載 https://pan.baidu.com/s/1I1fSly…

vue 鼠標經過時顯示/隱藏其他元素

方式一&#xff1a; 使用純css方式 , :hover是可以控制其他元素 1、 當兩個元素是父子關系 <div class"all_" ><div> <i class"iconfont icon-sun sun"></i></div> </div> .all_{} .sun {display: none; /* 默認…

靜態網站部署:如何通過GitHub免費部署一個靜態網站

GitHub提供的免費靜態網站托管服務可以無需擔心昂貴的服務器費用和復雜的設置步驟&#xff0c;本篇文章中將一步步解如何通過GitHub免費部署一個靜態網站&#xff0c;幫助大家將創意和作品快速展現給世界。 目錄 了解基礎情況 創建基礎站點 在線調試站點 前端項目部署 部署…

Pytorch里面多任務Loss是加起來還是分別backward? | Pytorch | 深度學習

當你在深度學習中進入“多任務學習(Multi-task Learning)”的領域,第一道關卡可能不是設計網絡結構,也不是準備數據集,而是:多個Loss到底是加起來一起backward,還是分別backward? 這個問題看似簡單,卻涉及PyTorch計算圖的構建邏輯、自動求導機制、內存管理、任務耦合…

基于DPABI提取nii文件模板的中心點坐標

基于DPABI提取nii文件模板的中心點坐標 在使用DPABI&#xff08;Data Processing Assistant for Resting-State fMRI&#xff09;處理NIfTI&#xff08;.nii&#xff09;文件時&#xff0c;可以通過以下步驟提取模板中每個坐標點的中心點坐標&#xff1a;https://wenku.csdn.n…

redis 基本命令-17 (KEYS、EXISTS、TYPE、TTL)

Redis 基本命令&#xff1a;KEYS、EXISTS、TYPE、TTL Redis 提供了一套基本命令&#xff0c;這些命令對于管理密鑰和了解數據庫中存儲的數據至關重要。這些命令雖然簡單&#xff0c;但提供了對 Redis 實例的結構和狀態的重要見解。具體來說&#xff0c;KEYS、EXISTS、TYPE 和 …

加速leveldb查詢性能之Cache技術

加速leveldb查詢性能之Cache技術 目錄 1.兩種Cache2.Table Cache3.Block Cache 注&#xff1a;本節所有內容更新至星球。 學習本節之前最好提前需要學習前面兩篇文章&#xff0c;這樣便好理解本節內容。 多圖文講解leveldb之SST/LDB文件格式 【深入淺出leveldb】LRU與哈希表 1.…

5.2.3 使用配置文件方式整合MyBatis

本實戰通過使用Spring Boot和MyBatis技術棧&#xff0c;實現了文章列表顯示功能。首先&#xff0c;通過創建ArticleMapper接口和對應的ArticleMapper.xml配置文件&#xff0c;實現了對文章數據的增刪改查操作&#xff0c;并通過單元測試驗證了功能的正確性。接著&#xff0c;通…

Node.js 源碼架構詳解

Node.js 的源碼是一個龐大且復雜的項目&#xff0c;它主要由 C 和 JavaScript 構成。要完全理解每一部分需要大量的時間和精力。我會給你一個高層次的概述&#xff0c;并指出一些關鍵的目錄和組件&#xff0c;幫助你開始探索。 Node.js 的核心架構 Node.js 的核心可以概括為以…

【NLP 76、Faiss 向量數據庫】

壓抑與痛苦&#xff0c;那些輾轉反側的夜&#xff0c;終會讓我們更加強大 —— 25.5.20 Faiss&#xff08;Facebook AI Similarity Search&#xff09;是由 Facebook AI 團隊開發的一個開源庫&#xff0c;用于高效相似性搜索的庫&#xff0c;特別適用于大規模向…

Go 語言簡介

1. Go 語言簡介 1.1 什么是 Go 語言 Go語言&#xff0c;通常被稱為Golang&#xff0c;是由Google在2007年開始開發&#xff0c;并在2009年正式發布的一種開源編程語言。Go語言的設計初衷是解決大型軟件開發中的效率和可維護性問題&#xff0c;特別是在多核處理器和網絡化系統…