2025年10月25日 星期六

Vue 3 + WebSocket 即時聊天室:打造生產級應用的架構與實戰

Vue 3 WebSocket 聊天室架構設計與實戰教學

實現高彈性、高安全的即時通訊服務

By [您的名字或暱稱] | 資深全端工程師與系統架構師


當我們從傳統的 HTTP 架構轉向 即時(Real-Time) 應用時,WebSocket 幾乎是唯一的選擇。然而,一個生產環境可用的 WebSocket 服務,遠不只是一個簡單的廣播器。它需要處理連線韌性、身份認證和流量安全等複雜問題。

本篇分享將以一套 Vue 3 + Node.js/WS + Docker 的全端即時聊天室專案為基礎,深入解析我們如何透過 「連線韌性三板斧」、「後端安全門禁」和「DevOps 自動化」,來打造一個高可用、高彈性的生產級應用。

專案架構概覽 (Architecture Overview)

我們的專案採用前後端解耦和容器化部署,確保了開發環境與生產環境的一致性。

類別技術棧核心職責
前端Vite + Vue 3 + TypeScript + PiniaUI 渲染、狀態管理、連線韌性管理
後端Node.js + Express + wsWebSocket 處理、認證握手、速率限制、訊息廣播
容器化Docker Compose / Nginx開發環境熱重載;生產環境多階段建置與 Nginx 代理

關鍵檔案結構

專案結構清晰,將關注點分離 (Separation of Concerns):

  • frontend/src/composables/useWebSocket.ts:前端連線韌性的核心。

  • frontend/src/stores/authStore.ts:Pinia 儲存使用者暱稱狀態。

  • backend/index.js:後端 WebSocket 服務器邏輯(認證、限速)。

  • docker-compose.yml / docker-compose.prod.yml:定義開發與生產環境。


1. 掌握連線韌性:前端的「打不死」策略

在網路不穩定的真實世界中,前端必須主動管理連線狀態,以確保服務品質 (QoS)。我們將所有複雜邏輯封裝在 useWebSocket.ts 這個 Composition API Hook 中。

1.1 指數退避自動重連 (Exponential Backoff)

如果伺服器短暫重啟或宕機,所有客戶端應避免在同一時刻同時發起連線,否則會形成連線雪崩

實作思路:

我們不使用固定間隔重連,而是讓等待時間成倍增長,並設定上限:getReconnectDelay = (attempt) => Math.min(1000 * 2**attempt, 30000)。這是一種基礎的流量管制 (Throttling) 機制。

1.2 心跳機制 (PING/PONG)

網路連線可能因防火牆或負載平衡器逾時而失效(半開連線)。

實作思路:

  • 後端定時向客戶端發送 PING

  • 客戶端收到 PING 必須回覆 PONG

  • 後端監聽 PONG 事件,若長時間未收到回覆,則視為死連線,主動執行 ws.terminate() 終止該連線,釋放資源。

1.3 狀態驅動連線

前端使用 Pinia 儲存使用者暱稱(authStore.nickname),並透過 Vue 的 watch 監聽這個狀態。

  • 登入時nickname 變化,自動觸發 connect()

  • 登出或組件卸載時:自動執行 disconnect()

這種 狀態驅動 (State-Driven) 的設計,讓 Vue 組件完全不用關心 WebSocket 的複雜生命週期。

TypeScript
// frontend/src/composables/useWebSocket.ts (關鍵片段)

// ...

// 狀態驅動連線:當 Pinia 的 nickname 變化時觸發
watch(() => authStore.nickname, (n, old) => { 
    if (n && !old) connect(); // 登入:連線
    if (!n && old) disconnect() // 登出:斷開
}, { immediate: true })

// ...

2. 鞏固後端安全:認證握手與限流

後端 (backend/index.js) 負責確保所有訊息的合法性與服務的穩定性。

2.1 強制認證握手 (HELLO/WELCOME)

這是防止身份偽造 (Identity Spoofing) 的關鍵。我們絕不信任客戶端發送的 sender 字段。

認證流程:

  1. 客戶端連線:連線後狀態為 isAuthenticated = false

  2. 發送 HELLO:客戶端必須先發送 { type: 'HELLO', nickname: 'Alice' }

  3. 後端驗證:後端接收 HELLO,進行基礎清洗,然後將 ws.nickname = 'Alice' 綁定到該連線物件上,並回覆 { type: 'WELCOME' }

  4. 發言封包:所有後續的 MESSAGE 請求,後端廣播時一律採用 綁定的 ws.nickname 作為發言者,忽略客戶端傳來的任何 sender 字段。

JavaScript
// backend/index.js (認證與發言片段)

// ...
ws.on('message', (data) => {
    // ... PING/PONG 處理 ...

    if (msg.type === 'HELLO') { 
        // 1. 進行清洗,設定 ws.nickname
        ws.isAuthenticated = true; 
        ws.nickname = sanitizeText(msg.nickname); 
        ws.send(JSON.stringify({ type: 'WELCOME', nickname: ws.nickname }));
        return;
    }

    // 2. 拒絕未認證用戶的訊息
    if (!ws.isAuthenticated) return sendError(ws, 'AUTH_REQUIRED');

    // 3. 處理已認證的 MESSAGE
    if (msg.type === 'MESSAGE') {
        // ... 速率限制檢查 ...

        broadcast({ 
            text: sanitizedText(msg.text), 
            sender: ws.nickname, // ❗ 永遠使用後端綁定的名稱
            // ... 
        });
    }
});
// ...

2.2 速率限制 (Rate Limiting) 與 XSS 清洗

  • 速率限制: 我們在 ws.on('message') 中,以連線 ID (或更嚴謹的 Session ID) 為 Key,追蹤發言頻率(例如:每秒最多 10 條訊息)。一旦超限,立即回傳 {type: 'ERROR', code: 'RATE_LIMIT'},防止單一用戶發動灌水攻擊。

  • XSS 清洗: 這是深度防禦 (Defense in Depth) 的要求。即使前端已對輸入進行轉義,後端仍必須對 msg.text 進行 HTML 標籤轉義,避免惡意腳本透過 API 或其他管道注入。


3. DevOps 自動化:Docker Compose 與 Nginx 代理

一個好的架構必須具備一致且高效的部署流程。Docker Compose 完美地實現了這個目標。

3.1 開發模式 (docker-compose.yml)

我們使用 Volume 掛載 (frontend:/app) 實現前後端的熱重載 (HMR),並利用 Docker 網路特性解決服務發現問題:

  • Vite 配置:在 docker-compose.yml 中注入 VITE_WS_URL=ws://backend:3000

  • 服務發現:前端容器透過服務名稱 backend,就能連線到後端容器的 3000 埠。開發者無需關心 IP 地址。

3.2 生產模式 (docker-compose.prod.yml) 與 Nginx 代理

生產環境需要極致的效能和穩定性。

  1. 多階段建置 (Multi-stage Build):

    前端 Dockerfile.prod 在建置後,僅將生產環境的靜態 dist 文件複製到一個極小的 Nginx 基礎 Image 中。最終 Image 不包含任何 Node.js 運行環境或開發依賴,大幅縮小 Image 體積和攻擊面。

  2. Nginx WebSocket 代理:

    Nginx 作為唯一的對外入口,負責靜態文件服務,並將 WebSocket 流量(例如 /ws)轉發到後端 Node.js 服務。

    Nginx 代理的兩項關鍵配置:

    • 協定升級: 必須設定 proxy_set_header Upgrade $http_upgradeproxy_set_header Connection "upgrade",這是 WebSocket 協定升級所必需。

    • 即時性優化: 關閉 proxy_buffering off,確保訊息能夠即時傳輸,同時延長 proxy_read_timeout,以維持長連線。


4. E2E 測試與驗收流程

我們的專案包含了 tests/e2e-chat.js,這是一個端到端測試腳本,用於驗證:

  1. 兩個獨立客戶端(Alice/Bob)是否能成功開啟連線。

  2. HELLO/WELCOME 認證流程 是否正確執行。

  3. 客戶端是否能成功發送和接收來自對方的訊息,並驗證 sender 欄位 正確(確認後端是使用綁定的暱稱廣播)。

驗收流程: 執行 npm run test:e2e,即可自動化驗證核心聊天功能和認證邏輯的完整性。


總結:資深工程師的價值

這個專案藍圖體現了資深工程師的價值:預見問題,並在代碼層面解決它。

  • 韌性 來自於前端的指數退避和心跳機制。

  • 安全 來自於後端的強制認證、限流和清洗。

  • 效率 來自於 Pinia 狀態驅動和 Docker 的自動化部署。

歡迎您基於此架構,進一步擴展功能,例如加入 JWT 身份驗證、Redis 訊息儲存或多個後端實例的負載平衡!

沒有留言:

張貼留言

📦 LogiFlow WMS:打造 SaaS 多租戶倉儲管理系統的技術實踐

📦 LogiFlow WMS:打造 SaaS 多租戶倉儲管理系統的技術實踐 在企業數位化的浪潮下,倉儲管理系統 (WMS) 不再只是單一公司的內部工具,而是需要支援 多租戶 (Multi-Tenant) 的 SaaS 架構。這意味著系統必須在共享基礎設施的同時,保有嚴格的資...