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

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

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

一、SSE 簡(jiǎn)介

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

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

二、 SSE 客戶端代碼

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

import { getToken, getUserInfo } from '@/utils/tools';
class SSEClient {
    constructor() {
        this.source = null; // SSE 核心實(shí)例
        // 基礎(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}`;
        // 請(qǐng)求頭(攜帶 Token 做權(quán)限校驗(yàn),適配 HTTPS 鑒權(quán)),目前這種方式并不支持自定義headers
        this.headers = {
            'satoken': getToken(),
        };
        this.reconnectInterval = 30000; // 自動(dòng)重連間隔(30秒,可配置)
        this.isConnected = false; // 連接狀態(tài)標(biāo)識(shí)
        this.isReconnecting = false; // 重連狀態(tài)標(biāo)識(shí)(避免重復(fù)重連)
        this.eventHandlers = new Map(); // 事件處理器映射表(支持多事件)
        this.maxReconnectTimes = 10; //最大重連次數(shù)(避免無(wú)限重連)
        this.reconnectCount = 0; // 當(dāng)前重連次數(shù)
    }
    /**
     * 動(dòng)態(tài)拼接 URL
     * 避免重復(fù)拼接相同參數(shù),異常時(shí)返回基礎(chǔ)URL
     */
    getUrlWithParams() {
        try {
            const userInfo = getUserInfo();
            // 無(wú)用戶信息時(shí)返回基礎(chǔ)URL
            if (!userInfo || !userInfo.userId) {
                return this.url;
            }
            const userId = userInfo.userId;
            // 拼接參數(shù)
            const params = new URLSearchParams();
            params.append('userId', userId);
            // 拼接完整URL,避免重復(fù)參數(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 - 配置項(xiàng)
     * @param {boolean} options.withCredentials - 是否攜帶跨域憑證
     * @param {number} options.reconnectInterval - 自定義重連間隔
     */
    connect(options = {}) {
        // 避免重復(fù)連接或重連中重復(fù)觸發(fā)
        if (this.isConnected || this.isReconnecting) {
            return;
        }
        // 合并配置項(xiàng)(支持自定義重連間隔)
        const withCredentials = options.withCredentials || false;
        this.reconnectInterval = options.reconnectInterval || this.reconnectInterval;
        // 先斷開(kāi)已有連接(防止殘留連接)
        this.disconnect();
        // 更新最新URL(確保參數(shù)實(shí)時(shí))
        this.url = this.getUrlWithParams();
        try {
            // SSE 初始化配置(適配 HTTPS 與跨域)
            const eventSourceInitDict = { withCredentials };
            this.source = new EventSource(this.url, eventSourceInitDict);
            // 監(jiān)聽(tīng)連接成功事件
            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)聽(tīng)服務(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)聽(tīng)連接錯(cuò)誤(核心:錯(cuò)誤處理與自動(dòng)重連)
            this.source.onerror = (error) => {
                this.isConnected = false;
                const errorMsg = `SSE 連接錯(cuò)誤(狀態(tài)碼:${this.source.readyState})`;
                console.error(errorMsg, error);
                this.emit('error', { message: errorMsg, error, readyState: this.source.readyState });
                // 僅在連接關(guān)閉時(shí)觸發(fā)重連(排除臨時(shí)錯(cuò)誤)
                if (this.source.readyState === EventSource.CLOSED && !this.isReconnecting) {
                    this.reconnectCount++;
                    // 超過(guò)最大重連次數(shù)停止重連
                    if (this.reconnectCount > this.maxReconnectTimes) {
                        this.isReconnecting = false;
                        this.emit('reconnect-failed', { maxTimes: this.maxReconnectTimes });
                        console.error(`SSE 重連失?。ㄒ殉^(guò)最大次數(shù) ${this.maxReconnectTimes})`);
                        return;
                    }
                    this.isReconnecting = true;
                    console.log(`SSE 準(zhǔn)備重連(第 ${this.reconnectCount}/${this.maxReconnectTimes} 次),間隔 ${this.reconnectInterval}ms`);
                    // 延遲重連(避免頻繁請(qǐng)求)
                    setTimeout(() => {
                        this.connect(options);
                    }, this.reconnectInterval);
                }
            };
            // 注冊(cè)已綁定的自定義事件(避免重連后事件丟失)
            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 });
        }
    }
    /**
     * 綁定自定義事件(抽離復(fù)用,適配重連場(chǎng)景)
     * @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)聽(tīng)(避免重復(fù)綁定)
        if (this.source) {
            this.source.removeEventListener(eventName, handler);
            // 綁定新監(jiān)聽(tīng)
            this.source.addEventListener(eventName, (event) => {
                try {
                    const data = JSON.parse(event.data);
                    handler(data);
                } catch (e) {
                    handler(event.data);
                }
            });
        }
    }
    /**
     * 注冊(cè)事件監(jiān)聽(tīng)(支持系統(tǒng)事件與自定義事件)
     * @param {string} eventName - 事件名
     * @param {Function} handler - 事件處理器
     */
    on(eventName, handler) {
        if (typeof handler !== 'function') {
            console.warn(`SSE 事件 ${eventName} 處理器必須是函數(shù)`);
            return;
        }
        // 存儲(chǔ)事件處理器
        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)聽(tīng)
     * @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 - 是否手動(dòng)關(guān)閉(區(qū)別于異常斷開(kāi))
     */
    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; // 手動(dòng)關(guān)閉重置重連次數(shù)
            this.emit('close', { isManual });
            console.log('SSE 手動(dòng)關(guān)閉連接');
        }
    }
    /**
     * 強(qiáng)制重連(主動(dòng)觸發(fā)重連,重置重連次數(shù))
     * @param {Object} options - 配置項(xiàng)
     */
    forceReconnect(options = {}) {
        this.reconnectCount = 0;
        this.isReconnecting = false;
        this.connect(options);
    }
    /**
     * 更新連接參數(shù)(如Token過(guò)期后刷新URL)
     */
    updateParams() {
        this.url = this.getUrlWithParams();
        // 已連接狀態(tài)下重新連接(刷新參數(shù))
        if (this.isConnected) {
            this.forceReconnect();
        }
    }
}
// 全局單例模式(確保全項(xiàng)目唯一 SSE 實(shí)例,避免多連接沖突)
export const sseClient = new SSEClient();

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

3.1 全局引入(main.js)

將 SSE 實(shí)例掛載到全局,便于全項(xiàng)目使用:

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

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

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

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

3.4 特殊場(chǎng)景處理

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

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

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

四、HTTPS 適配注意事項(xiàng)

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

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

  • 跨域配置:若前端與后端跨域,需后端配置 CORS 允許 EventSource 請(qǐng)求,同時(shí)前端開(kāi)啟 withCredentials: true 攜帶跨域憑證。

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

五、后端配合要求

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

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

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

  4. 連接保持:后端需保持 SSE 連接長(zhǎng)駐,避免主動(dòng)斷開(kāi),同時(shí)處理客戶端斷開(kāi)后的資源釋放。

六、常見(jiàn)問(wèn)題排查

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

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

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

  4. 跨域錯(cuò)誤:協(xié)調(diào)后端配置 CORS,允許前端域名訪問(wèn),同時(shí)前端開(kāi)啟 withCredentials 配置。

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

七、拓展方向

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

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

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

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

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

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

相關(guān)文章

最新評(píng)論

安泽县| 河池市| 饶河县| 仲巴县| 淮阳县| 哈尔滨市| 白玉县| 清徐县| 厦门市| 进贤县| 高尔夫| 广汉市| 容城县| 乌拉特后旗| 盱眙县| 云阳县| 三亚市| 容城县| 漯河市| 丹阳市| 兰坪| 灌南县| 延庆县| 金沙县| 合水县| 榆社县| 浑源县| 武汉市| 古田县| 桐城市| 柘城县| 波密县| 仲巴县| 濮阳县| 斗六市| 阿勒泰市| 中西区| 合肥市| 卓尼县| 连城县| 琼结县|