最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Springboot使用SSE推送消息到客戶端的實(shí)現(xiàn)

 更新時間:2026年01月23日 08:25:11   作者:愛吃的強(qiáng)哥  
文章瀏覽閱讀246次,點(diǎn)贊4次,收藏4次,本文介紹了在SpringBoot中實(shí)現(xiàn)SSE(Server-Sent Events)服務(wù)端推送功能,用于Electron桌面應(yīng)用的消息推送,感興趣的可以了解一下

1、場景:

服務(wù)端主動推動消息到客戶端(Electron 桌面應(yīng)用)

普通的HTTP/HTTPS請求要先客戶端發(fā)送請求,然后服務(wù)端才能返回結(jié)果

服務(wù)端主動推送數(shù)據(jù)到客戶端,有兩種方案:

SSE:只能從服務(wù)端主動推送數(shù)據(jù)到客戶端(單向),客戶端發(fā)送數(shù)據(jù)還是要使用HTTP/HTTPS請求

Socket 通信:客戶端和服務(wù)端都可以發(fā)送數(shù)據(jù)給對方(雙向)

先記錄SSE的配置(我的服務(wù)端之前是用nodejs express 寫的,已經(jīng)使用 socket.io與客戶端適配好了,現(xiàn)在用Springboot重構(gòu))

2、實(shí)現(xiàn):

1、springboot 配置:

1、先實(shí)現(xiàn)Server層,方便其他控制器調(diào)用

package com.xxx.controller;
 
import com.qiang.service.SseService;
import com.qiang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
@RestController
@RequestMapping("/sse")
public class SseController {
 
    // 注入 SSE 服務(wù)類
    @Autowired
    private SseService sseService;
    @Autowired
    private UserService userService;
 
    /**
     * 建立 SSE 連接(調(diào)用 Service 的注冊方法)
     */
    @GetMapping("/connect")
    public SseEmitter connect(@RequestParam String clientId) {
        // 查詢會話成員表,userId通過客戶端id獲取該用戶所有的群聊
        String userId = clientId.split("_")[2].trim();
        List<String> groupIdList = userService.getUserGroupChat(userId);
        Set<String> groupIdSet = new HashSet<>();
        if (groupIdList != null && !groupIdList.isEmpty()) {
            groupIdSet = new HashSet<>(groupIdList); // List → Set,自動去重
        }
        return sseService.registerClient(clientId, groupIdSet);
    }
 
    /**
     * 手動推送(調(diào)用 Service 的推送方法)
     */
    @PostMapping("/push")
    public String push(@RequestBody Map<String, String> params) {
        String clientId = params.get("clientId");
        String message = params.get("message");
        return sseService.sendMessage(clientId, "business", message);
    }
 
    /**
     * 獲取在線客戶端數(shù)量
     */
    @GetMapping("/count")
    public String getClientCount() {
        return "當(dāng)前在線客戶端數(shù)量:" + sseService.getConnectedClientCount();
    }
}

2、再實(shí)現(xiàn)控制層:

package com.xxx.controller;
 
import com.qiang.service.SseService;
import com.qiang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
@RestController
@RequestMapping("/sse")
public class SseController {
 
    // 注入 SSE 服務(wù)類
    @Autowired
    private SseService sseService;
    @Autowired
    private UserService userService;
 
    /**
     * 建立 SSE 連接(調(diào)用 Service 的注冊方法)
     */
    @GetMapping("/connect")
    public SseEmitter connect(@RequestParam String clientId) {
        // 查詢會話成員表,userId通過客戶端id獲取該用戶所有的群聊
        String userId = clientId.split("_")[2].trim();
        List<String> groupIdList = userService.getUserGroupChat(userId);
        Set<String> groupIdSet = new HashSet<>();
        if (groupIdList != null && !groupIdList.isEmpty()) {
            groupIdSet = new HashSet<>(groupIdList); // List → Set,自動去重
        }
        return sseService.registerClient(clientId, groupIdSet);
    }
 
    /**
     * 手動推送(調(diào)用 Service 的推送方法)
     */
    @PostMapping("/push")
    public String push(@RequestBody Map<String, String> params) {
        String clientId = params.get("clientId");
        String message = params.get("message");
        return sseService.sendMessage(clientId, "business", message);
    }
 
    /**
     * 獲取在線客戶端數(shù)量
     */
    @GetMapping("/count")
    public String getClientCount() {
        return "當(dāng)前在線客戶端數(shù)量:" + sseService.getConnectedClientCount();
    }
}

2、客戶端(Electron 配置):

eventsource 是瀏覽器對象

我是在客戶端主進(jìn)程中接收消息的,要下載依賴。如果是在渲染進(jìn)程或者是普通前端使用則不需要下載依賴

"eventsource": "^2.0.2"

1、工具函數(shù):

// src/util/sseClient.js(主進(jìn)程專用)
const EventSource = require('eventsource');
 
class SseClient {
    constructor(clientId) {
        this.clientId = clientId;
        this.isConnected = false; // 標(biāo)記是否真正連接成功
        this.initSse();
    }
 
    initSse() {
        this.close(); // 關(guān)閉舊連接
 
        const sseUrl = `http://localhost:8088/sse/connect?clientId=${this.clientId}`;
        console.log(`[SSE] 嘗試連接:${sseUrl}`);
 
        this.es = new EventSource(sseUrl);
 
        // 1. 連接成功(標(biāo)記真正的連接狀態(tài))
        this.es.onopen = () => {
            this.isConnected = true;
            console.log('[SSE] 連接成功');
        };
 
        // 2. 優(yōu)化錯誤處理(過濾無害錯誤)
        this.es.onerror = (e) => {
            // 過濾:連接成功前的無消息錯誤(無害)
            if (!this.isConnected && e.message === undefined) {
                console.log('[SSE] 初始化階段臨時錯誤(無害):', e.type);
                return; // 不打印錯誤,避免干擾
            }
 
            // 真正的錯誤(連接斷開/失?。?
            this.isConnected = false;
            console.error('[SSE] 真正的連接錯誤:', {
                type: e.type,
                message: e.message || '未知錯誤',
                readyState: this.es.readyState // 0:連接中, 1:已連接, 2:已關(guān)閉
            });
 
            // 僅在連接關(guān)閉時重連
            if (this.es.readyState === EventSource.CLOSED) {
                console.log(`[SSE] 3秒后嘗試重連...`);
                setTimeout(() => this.initSse(), 3000);
            }
        };
 
        // 3. 正常接收消息
        this.es.onmessage = (e) => {
            try {
                const cleanData = e.data.replace(/^data: /, '').trim();
                const messageObj = JSON.parse(cleanData);
                // 打印解析結(jié)果(驗(yàn)證)
                console.log('[SSE] 解析后的完整對象:', messageObj);
            } catch (err) {
                // 解析失敗時的容錯
                console.warn('[SSE] 解析失敗,原始數(shù)據(jù):', e.data);
                console.error('[SSE] 解析錯誤詳情:', err);
            }
        };
 
        // 4. 監(jiān)聽自定義事件(如 notification/business)
        this.es.addEventListener('notification', (e) => {
            const data = JSON.parse(e.data);
            console.log('[SSE] 通知消息:', data);
        });
    }
 
    close() {
        if (this.es) {
            this.es.close();
            this.es = null;
            this.isConnected = false;
        }
    }
 
    // 手動推送(修復(fù)后的 POST 版本)
    async triggerPush(message) {
        if (!this.isConnected) {
            console.warn('[SSE] 未連接,無法推送');
            return null;
        }
        try {
            const response = await fetch(`${this.serverUrl || 'http://localhost:8088'}/sse/push`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ clientId: this.clientId, message })
            });
            const result = await response.text();
            console.log('[SSE] 手動推送結(jié)果:', result);
            return result;
        } catch (err) {
            console.error('[SSE] 手動推送失?。?, err);
            return null;
        }
    }
}
 
module.exports = SseClient;

2、調(diào)用:

// clientId 要唯一,否則服務(wù)端推送消息時會有影響 
new SseClient("自定義的clientId");

到此這篇關(guān)于Springboot使用SSE推送消息到客戶端的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Springboot SSE推送內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot+dubbo+validation 進(jìn)行rpc參數(shù)校驗(yàn)的實(shí)現(xiàn)方法

    springboot+dubbo+validation 進(jìn)行rpc參數(shù)校驗(yàn)的實(shí)現(xiàn)方法

    這篇文章主要介紹了springboot+dubbo+validation 進(jìn)行rpc參數(shù)校驗(yàn)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • 四步輕松搞定java web每天定時執(zhí)行任務(wù)

    四步輕松搞定java web每天定時執(zhí)行任務(wù)

    本篇文章主要介紹了四步輕松搞定java web每天定時執(zhí)行任務(wù),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • SpringBoot中的application.properties無法加載問題定位技巧

    SpringBoot中的application.properties無法加載問題定位技巧

    這篇文章主要介紹了SpringBoot中的application.properties無法加載問題定位技巧,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • SpringBoot?Cache?二級緩存的使用

    SpringBoot?Cache?二級緩存的使用

    本文主要介紹了SpringBoot?Cache?二級緩存的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • IDEA中設(shè)置Tab健為4個空格的方法

    IDEA中設(shè)置Tab健為4個空格的方法

    這篇文章給大家介紹了代碼縮進(jìn)用空格還是Tab?(IDEA中設(shè)置Tab健為4個空格)的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-03-03
  • Mybatis的動態(tài)Sql組合模式詳情

    Mybatis的動態(tài)Sql組合模式詳情

    這篇文章主要介紹了Mybatis的動態(tài)Sql組合模式詳情,這篇文章從組合模式的角度分析了Mybatis動態(tài)sql的部分,SqlNode是組合模式的Component接口,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-08-08
  • 基于SpringBoot實(shí)現(xiàn)多文件批量下載并打包為ZIP壓縮包的完整解決方案

    基于SpringBoot實(shí)現(xiàn)多文件批量下載并打包為ZIP壓縮包的完整解決方案

    在日常的 Web 開發(fā)中,文件下載是非常常見的功能需求,而多文件批量下載并打包為 ZIP 壓縮包 更是高頻場景(比如批量下載合同、圖片、報表等),本文將基于 SpringBoot 框架,手把手教你實(shí)現(xiàn)這一功能,從核心思路到完整代碼,讓你快速掌握,需要的朋友可以參考下
    2026-02-02
  • java實(shí)現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫

    java實(shí)現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)批量導(dǎo)入Excel表格數(shù)據(jù)到數(shù)據(jù)庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-08-08
  • Java中常用的6種排序算法詳細(xì)分解

    Java中常用的6種排序算法詳細(xì)分解

    這篇文章主要介紹了Java中常用的6種排序算法詳細(xì)分解,著重說明每個算法的計算過程分解,是探究實(shí)現(xiàn)原理級的文章,對于深入理解這些算法有很大幫助,需要的朋友可以參考下
    2014-07-07
  • 詳解SpringBoot如何自定義自己的Starter組件

    詳解SpringBoot如何自定義自己的Starter組件

    這篇文章主要為大家詳細(xì)介紹了在SpringBoot中如何自定義自己的Starter組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-03-03

最新評論

西昌市| 栖霞市| 翁牛特旗| 黑龙江省| 宁阳县| 哈尔滨市| 时尚| 开远市| 东明县| 德保县| 张家界市| 芮城县| 滨州市| 台南市| 舟曲县| 高碑店市| 达日县| 综艺| 石楼县| 万荣县| 宝应县| 新晃| 宕昌县| 徐州市| 泸定县| 横峰县| 晋中市| 原阳县| 南皮县| 茂名市| 金湖县| 平安县| 西昌市| 连江县| 山阳县| 阜阳市| 沿河| 连云港市| 磐石市| 南安市| 德昌县|