Skip to content

核心原理

  • 基于 HTTP 协议,建立「客户端 → 服务器」的单向长连接
  • 服务器通过该连接持续向客户端推送数据(支持文本、JSON 等格式)
  • 适合服务器单向推送场景(如实时日志、数据看板、股票行情)

使用Nestjs实现SSE

服务启动在3001端口上,结合前端实现对接推送数据,在modules/notify/notify.controller.ts中实现SSE接口:

ts
import { Controller, Get, Post, Body, Patch, Param, Delete, Sse } from '@nestjs/common';
import { NotifyService } from './notify.service';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { ApiOperation, ApiTags } from '@nestjs/swagger';

@ApiTags('通知模块')
@Controller('notify')
export class NotifyController {
  constructor(private readonly notifyService: NotifyService) {}

  @ApiOperation({ summary: 'SSE流式数据' })
  @Sse('stream')
  stream(): Observable<{ data: string }> {
    // 添加 take(100) 限制只发送100次数据,然后自动结束流
    return interval(200).pipe(
      take(200), // 只发送100次
      map((count) => ({ data: `这是第${count + 1}次发送的SSE数据` })),
    );
  }
}

前端对接(Vue3)

vue
<template>
  <div class="sse">
    <h2>服务器发送事件(SSE)</h2>
    <p>机制:服务器单向推送,客户端被动接收</p>
    <button @click="toggleSSE">
      {{ isConnected ? "断开连接" : "建立连接" }}
    </button>
    <div class="message-box">
      <h3>推送历史:</h3>
      <ul>
        <li v-for="(item, index) in messageList" :key="index">{{ item }}</li>
      </ul>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onUnmounted } from "vue";

const messageList = ref<string[]>(["等待服务器推送..."]);
const isConnected = ref(false);
let eventSource: EventSource | null = null;

// 建立/断开 SSE 连接
const toggleSSE = () => {
  if (isConnected.value) {
    // 断开连接
    eventSource?.close();
    eventSource = null;
    isConnected.value = false;
    messageList.value.push("已断开 SSE 连接");
  } else {
    // 建立连接(使用 Nest.js 内置 SSE 接口)
    eventSource = new EventSource("http://localhost:3001/notify/stream");

    // 监听正常推送(type: 'message')
    eventSource.onmessage = (event) => {
      console.log("event", event.data);
      const data = JSON.parse(event.data);
      messageList.value.push(data);
    };

    // 监听自定义事件类型(可选)
    eventSource.addEventListener("customEvent", (event) => {
      messageList.value.push(`自定义事件:${event.data}`);
    });

    // 监听连接错误
    eventSource.onerror = (error) => {
      console.error("SSE 连接错误:", error);
      messageList.value.push("SSE 连接异常,已断开");
      isConnected.value = false;
      eventSource?.close();
    };

    // 监听连接成功
    eventSource.onopen = () => {
      isConnected.value = true;
      messageList.value.push("已建立 SSE 连接");
    };
  }
};

// 组件卸载时断开连接
onUnmounted(() => {
  eventSource?.close();
});
</script>

<style scoped>
.sse {
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

.message-box {
  flex: 1;
  min-height: 0;
  margin: 20px 0;
  padding: 15px;
  border: 1px solid #eee;
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

ul {
  padding: 0 32px;
  margin-left: 20px;
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
}

li {
  margin: 5px 0;
}

button {
  padding: 8px 16px;
  background: #42b983;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  margin-top: 20px;
}
</style>

SSE的优缺点对比

优点

  • 单向推送,节省客户端资源
  • 自动重连(浏览器原生支持)
  • 轻量级,无需复杂协议

缺点

  • 仅支持服务器 → 客户端单向通信
  • 单个连接只能推送文本数据(需序列化)
  • 部分代理服务器可能不支持长连接