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

JavaScript實現(xiàn)HTTPS?SSE連接完整示例代碼

 更新時間:2026年03月26日 08:49:03   作者:Dreamy?smile  
服務(wù)器推送事件是一項技術(shù),允許服務(wù)器向瀏覽器推送實時更新,這篇文章主要介紹了JavaScript實現(xiàn)HTTPS?SSE連接的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下

一、SSE 簡介

SSE(Server-Sent Events,服務(wù)器推送事件)是一種基于 HTTP 的單向通信協(xié)議,允許服務(wù)器主動向客戶端推送數(shù)據(jù),適用于實時通知、消息提醒、狀態(tài)同步等場景。與 WebSocket 雙向通信不同,SSE 僅支持服務(wù)器到客戶端的單向傳輸,開銷更小,適配 HTTPS 協(xié)議更簡潔。

本文實現(xiàn)的 SSE 客戶端具備以下特性:HTTPS 適配、自動重連、事件監(jiān)聽、單例模式、錯誤處理、動態(tài)拼接用戶參數(shù),可直接集成到 Vue3/Vite 等前端項目中。

二、 SSE 客戶端代碼

核心文件:utils/sseClient.js,采用類封裝,提供完整的連接、監(jiān)聽、斷開、重連能力,支持自定義事件與參數(shù)動態(tài)拼接。

import { getToken, getUserInfo } from '@/utils/tools';
class SSEClient {
    constructor() {
        this.source = null; // SSE 核心實例
        // 基礎(chǔ) URL(從環(huán)境變量讀取,適配不同環(huán)境)
        this.baseUrl = import.meta.env.VITE_API_BASE_URL;
        this.path = '/sse/talent/adminConnect'; // SSE 接口路徑
        this.url = `${this.baseUrl}${this.path}`;
        // 請求頭(攜帶 Token 做權(quán)限校驗,適配 HTTPS 鑒權(quán)),目前這種方式并不支持自定義headers
        this.headers = {
            'satoken': getToken(),
        };
        this.reconnectInterval = 30000; // 自動重連間隔(30秒,可配置)
        this.isConnected = false; // 連接狀態(tài)標識
        this.isReconnecting = false; // 重連狀態(tài)標識(避免重復重連)
        this.eventHandlers = new Map(); // 事件處理器映射表(支持多事件)
        this.maxReconnectTimes = 10; //最大重連次數(shù)(避免無限重連)
        this.reconnectCount = 0; // 當前重連次數(shù)
    }
    /**
     * 動態(tài)拼接 URL
     * 避免重復拼接相同參數(shù),異常時返回基礎(chǔ)URL
     */
    getUrlWithParams() {
        try {
            const userInfo = getUserInfo();
            // 無用戶信息時返回基礎(chǔ)URL
            if (!userInfo || !userInfo.userId) {
                return this.url;
            }
            const userId = userInfo.userId;
            // 拼接參數(shù)
            const params = new URLSearchParams();
            params.append('userId', userId);
            // 拼接完整URL,避免重復參數(shù)
            const fullUrl = `${this.url}?${params.toString()}`;
            return fullUrl;
        } catch (error) {
            console.warn('SSE 拼接參數(shù)失敗,使用基礎(chǔ)URL:', error);
            return this.url;
        }
    }
    /**
     * 初始化 SSE 連接
     * @param {Object} options - 配置項
     * @param {boolean} options.withCredentials - 是否攜帶跨域憑證
     * @param {number} options.reconnectInterval - 自定義重連間隔
     */
    connect(options = {}) {
        // 避免重復連接或重連中重復觸發(fā)
        if (this.isConnected || this.isReconnecting) {
            return;
        }
        // 合并配置項(支持自定義重連間隔)
        const withCredentials = options.withCredentials || false;
        this.reconnectInterval = options.reconnectInterval || this.reconnectInterval;
        // 先斷開已有連接(防止殘留連接)
        this.disconnect();
        // 更新最新URL(確保參數(shù)實時)
        this.url = this.getUrlWithParams();
        try {
            // SSE 初始化配置(適配 HTTPS 與跨域)
            const eventSourceInitDict = { withCredentials };
            this.source = new EventSource(this.url, eventSourceInitDict);
            // 監(jiān)聽連接成功事件
            this.source.onopen = () => {
                this.isConnected = true;
                this.isReconnecting = false;
                this.reconnectCount = 0; // 重置重連次數(shù)
                this.emit('open', { status: 'connected', url: this.url });
                console.log('SSE 連接成功:', this.url);
            };
            // 監(jiān)聽服務(wù)器推送的通用消息
            this.source.onmessage = (event) => {
                try {
                    // 嘗試解析 JSON 格式數(shù)據(jù)(兼容字符串與JSON)
                    const data = JSON.parse(event.data);
                    this.emit('message', data);
                } catch (e) {
                    // 非JSON格式直接返回原始數(shù)據(jù)
                    this.emit('message', event.data);
                }
            };
            // 監(jiān)聽連接錯誤(核心:錯誤處理與自動重連)
            this.source.onerror = (error) => {
                this.isConnected = false;
                const errorMsg = `SSE 連接錯誤(狀態(tài)碼:${this.source.readyState})`;
                console.error(errorMsg, error);
                this.emit('error', { message: errorMsg, error, readyState: this.source.readyState });
                // 僅在連接關(guān)閉時觸發(fā)重連(排除臨時錯誤)
                if (this.source.readyState === EventSource.CLOSED && !this.isReconnecting) {
                    this.reconnectCount++;
                    // 超過最大重連次數(shù)停止重連
                    if (this.reconnectCount > this.maxReconnectTimes) {
                        this.isReconnecting = false;
                        this.emit('reconnect-failed', { maxTimes: this.maxReconnectTimes });
                        console.error(`SSE 重連失敗(已超過最大次數(shù) ${this.maxReconnectTimes})`);
                        return;
                    }
                    this.isReconnecting = true;
                    console.log(`SSE 準備重連(第 ${this.reconnectCount}/${this.maxReconnectTimes} 次),間隔 ${this.reconnectInterval}ms`);
                    // 延遲重連(避免頻繁請求)
                    setTimeout(() => {
                        this.connect(options);
                    }, this.reconnectInterval);
                }
            };
            // 注冊已綁定的自定義事件(避免重連后事件丟失)
            this.eventHandlers.forEach((handler, eventName) => {
                this.bindCustomEvent(eventName, handler);
            });
        } catch (e) {
            this.isReconnecting = false;
            const errorMsg = '創(chuàng)建 SSE 連接失敗(初始化異常)';
            console.error(errorMsg, e);
            this.emit('error', { message: errorMsg, error: e });
        }
    }
    /**
     * 綁定自定義事件(抽離復用,適配重連場景)
     * @param {string} eventName - 自定義事件名
     * @param {Function} handler - 事件處理器
     */
    bindCustomEvent(eventName, handler) {
        // 排除系統(tǒng)內(nèi)置事件(open/message/error)
        const systemEvents = ['open', 'message', 'error'];
        if (systemEvents.includes(eventName)) return;
        // 移除已有監(jiān)聽(避免重復綁定)
        if (this.source) {
            this.source.removeEventListener(eventName, handler);
            // 綁定新監(jiān)聽
            this.source.addEventListener(eventName, (event) => {
                try {
                    const data = JSON.parse(event.data);
                    handler(data);
                } catch (e) {
                    handler(event.data);
                }
            });
        }
    }
    /**
     * 注冊事件監(jiān)聽(支持系統(tǒng)事件與自定義事件)
     * @param {string} eventName - 事件名
     * @param {Function} handler - 事件處理器
     */
    on(eventName, handler) {
        if (typeof handler !== 'function') {
            console.warn(`SSE 事件 ${eventName} 處理器必須是函數(shù)`);
            return;
        }
        // 存儲事件處理器
        this.eventHandlers.set(eventName, handler);
        // 已連接狀態(tài)下立即綁定事件
        if (this.source && this.isConnected) {
            this.bindCustomEvent(eventName, handler);
        }
    }
    /**
     * 觸發(fā)事件(供內(nèi)部調(diào)用,分發(fā)消息)
     * @param {string} eventName - 事件名
     * @param {any} data - 事件數(shù)據(jù)
     */
    emit(eventName, data) {
        const handler = this.eventHandlers.get(eventName);
        if (handler) {
            try {
                handler(data);
            } catch (e) {
                console.error(`SSE 觸發(fā)事件 ${eventName} 失?。篳, e);
            }
        }
    }
    /**
     * 移除事件監(jiān)聽
     * @param {string} eventName - 事件名
     */
    off(eventName) {
        this.eventHandlers.delete(eventName);
        if (this.source) {
            const handler = this.eventHandlers.get(eventName);
            if (handler) {
                this.source.removeEventListener(eventName, handler);
            }
        }
    }
    /**
     * 關(guān)閉 SSE 連接(重置狀態(tài),避免內(nèi)存泄漏)
     * @param {boolean} isManual - 是否手動關(guān)閉(區(qū)別于異常斷開)
     */
    disconnect(isManual = false) {
        if (this.source) {
            this.source.close();
            this.source = null;
        }
        // 重置狀態(tài)
        this.isConnected = false;
        this.isReconnecting = false;
        if (isManual) {
            this.reconnectCount = 0; // 手動關(guān)閉重置重連次數(shù)
            this.emit('close', { isManual });
            console.log('SSE 手動關(guān)閉連接');
        }
    }
    /**
     * 強制重連(主動觸發(fā)重連,重置重連次數(shù))
     * @param {Object} options - 配置項
     */
    forceReconnect(options = {}) {
        this.reconnectCount = 0;
        this.isReconnecting = false;
        this.connect(options);
    }
    /**
     * 更新連接參數(shù)(如Token過期后刷新URL)
     */
    updateParams() {
        this.url = this.getUrlWithParams();
        // 已連接狀態(tài)下重新連接(刷新參數(shù))
        if (this.isConnected) {
            this.forceReconnect();
        }
    }
}
// 全局單例模式(確保全項目唯一 SSE 實例,避免多連接沖突)
export const sseClient = new SSEClient();

三、使用示例(Vue3/Vite 集成)

3.1 全局引入(main.js)

將 SSE 實例掛載到全局,便于全項目使用:

import { createApp } from 'vue';
import App from './App.vue';
import { sseClient } from '@/utils/sseClient';
const app = createApp(App);
// 全局掛載 SSE 實例
app.config.globalProperties.$sse = sseClient;
app.mount('#app');

3.2 組件內(nèi)使用(頁面/組件中)

<script setup>
import { onMounted, onUnmounted } from 'vue';
import { sseClient } from '@/utils/sseClient';
onMounted(() => {
    // 1. 注冊事件監(jiān)聽
    // 連接成功事件
    sseClient.on('open', (res) => {
        console.log('SSE 連接成功', res);
        // 可在這里處理連接成功后的邏輯(如更新UI狀態(tài))
    });
    // 通用消息事件(服務(wù)器推送的所有消息)
    sseClient.on('message', (data) => {
        console.log('收到 SSE 消息', data);
        // 處理業(yè)務(wù)邏輯(如實時通知、數(shù)據(jù)更新)
    });
    // 自定義事件(后端指定事件名,如"notice")
    sseClient.on('notice', (data) => {
        console.log('收到自定義通知', data);
        // 處理自定義消息(如系統(tǒng)通知、業(yè)務(wù)提醒)
    });
    // 錯誤事件
    sseClient.on('error', (err) => {
        console.error('SSE 異常', err);
        // 處理錯誤(如提示用戶、降級處理)
    });
    // 重連失敗事件
    sseClient.on('reconnect-failed', (res) => {
        console.error('SSE 重連失敗', res);
        // 提示用戶手動刷新頁面
    });
    // 2. 啟動 SSE 連接(配置跨域憑證)
    sseClient.connect({ withCredentials: true });
});
onUnmounted(() => {
    // 組件卸載時移除事件監(jiān)聽、關(guān)閉連接(避免內(nèi)存泄漏)
    sseClient.off('open');
    sseClient.off('message');
    sseClient.off('notice');
    sseClient.off('error');
    sseClient.off('reconnect-failed');
    sseClient.disconnect(true); // 手動關(guān)閉連接
});
</script>

3.3可以用pinia全局管理并調(diào)用,配合路由守衛(wèi)也可以全局使用

3.4 特殊場景處理

  1. Token 過期刷新:Token 刷新后調(diào)用 sseClient.updateParams(),自動更新URL并重新連接。

  2. 用戶切換:切換用戶時先調(diào)用 sseClient.disconnect(true) 關(guān)閉連接,再調(diào)用 connect() 重新連接。

  3. 手動觸發(fā)重連:需要主動重連時調(diào)用 sseClient.forceReconnect()

四、HTTPS 適配注意事項

  • SSE 基于 HTTP/HTTPS 協(xié)議,HTTPS 環(huán)境下需確保后端 SSE 接口配置正確的 SSL 證書,避免證書無效導致連接失敗。

  • 請求頭鑒權(quán):HTTPS 場景下建議將 Token 放在 URL 參數(shù)或請求頭中(本文采用 URL 參數(shù),適配部分后端不支持 SSE 自定義請求頭的場景)。

  • 跨域配置:若前端與后端跨域,需后端配置 CORS 允許 EventSource 請求,同時前端開啟 withCredentials: true 攜帶跨域憑證。

  • 連接超時:SSE 無內(nèi)置超時機制,可通過后端設(shè)置心跳消息(如每30秒推送一次空消息),前端監(jiān)聽心跳判斷連接狀態(tài)。

五、后端配合要求

  1. 響應(yīng)頭配置:后端需返回正確的 SSE 響應(yīng)頭 Content-Type: text/event-stream,同時設(shè)置 Cache-Control: no-cache 禁止緩存。

  2. 消息格式:后端推送消息需遵循 SSE 格式規(guī)范,如 data: { "msg": "hello" }\n\n(注意結(jié)尾雙換行)。

  3. 自定義事件:后端推送自定義事件時需指定事件名,如 event: notice\ndata: { "msg": "通知" }\n\n。

  4. 連接保持:后端需保持 SSE 連接長駐,避免主動斷開,同時處理客戶端斷開后的資源釋放。

六、常見問題排查

  1. 連接失?。?03 無權(quán)限):檢查 Token 是否有效、用戶ID是否正確,后端是否對 SSE 接口 給做了權(quán)限校驗。

  2. 重連頻繁觸發(fā):排查后端是否主動斷開連接、網(wǎng)絡(luò)是否不穩(wěn)定,可調(diào)整 reconnectInterval 重連間隔。

  3. 消息無法解析:檢查后端推送消息格式是否符合 SSE 規(guī)范,是否為合法 JSON(非JSON格式會走字符串解析分支)。

  4. 跨域錯誤:協(xié)調(diào)后端配置 CORS,允許前端域名訪問,同時前端開啟 withCredentials 配置。

  5. 內(nèi)存泄漏:組件卸載時必須移除事件監(jiān)聽并關(guān)閉連接,避免殘留實例導致內(nèi)存泄漏。

七、拓展方向

  1. 添加日志記錄:集成日志工具,記錄 SSE 連接狀態(tài)、消息內(nèi)容、錯誤信息,便于線上問題排查。

  2. 心跳檢測:前端定時發(fā)送心跳請求(或后端定時推送心跳),實現(xiàn)連接狀態(tài)精準判斷。

  3. 多實例支持:修改單例模式為工廠模式,支持同時創(chuàng)建多個 SSE 實例,適配多接口推送場景。

  4. TypeScript 適配:添加類型定義,提升代碼提示與類型安全,適配 TS 項目。

  5. 失敗重試策略:支持指數(shù)退避重連(重連間隔逐漸延長),減少后端壓力。

到此這篇關(guān)于JavaScript實現(xiàn)HTTPS SSE連接的文章就介紹到這了,更多相關(guān)JS實現(xiàn)HTTPS SSE連接內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論

绥德县| 万盛区| 江口县| 永安市| 论坛| 和林格尔县| 仪征市| 平乐县| 德令哈市| 武邑县| 临漳县| 沁水县| 大兴区| 米脂县| 汶川县| 亳州市| 泰和县| 桐城市| 马边| 余庆县| 嵊泗县| 明水县| 永靖县| 临清市| 富裕县| 安龙县| 淮安市| 泸州市| 西青区| 吴川市| 山东| 丰都县| 镇赉县| 仲巴县| 绍兴县| 黔江区| 阿图什市| 璧山县| 财经| 灵石县| 密山市|