一文带你入门SSE | 使用useEventSource实现在线用户统计功能

3,746 阅读5分钟

一、介绍

做前端开发的我们,最怕的就是和后端的接口打交道,尤其是像 SSE 这种实时通讯的接口,没少让人头疼。还好有了 VueUse 这个神奇的工具,不然我又得写一堆重复冗余的代码了!用 VueUse,简化你的工作量,省下的时间还能多喝几杯咖啡。

简单介绍一下 VueUse

官方地址 : github.com/vueuse/vueu…

VueUse 是一个基于 Vue 3 的工具库,它包含了很多方便的组合式 API(Composition API)扩展。它能帮助我们快速处理常见的功能需求,比如状态管理、DOM 操作、事件监听等,而不需要编写过多的样板代码。特别是针对某些复杂场景,如服务端推送事件(SSE),VueUse 提供了现成的 API,帮助开发者简化代码,提高开发效率。

简单介绍一下 SSE

SSE(Server-Sent Events)  是一种允许服务器通过 HTTP 协议推送更新给客户端的技术。客户端通过一个持久的 HTTP 连接,从服务器接收事件流,不需要客户端主动轮询。浏览器原生支持 SSE,它适合一些对数据更新频率要求不太高的场景,如实时通知、股票行情、天气更新等。

二、useEventSource

Source • Docs

核心功能
  1. 响应式的状态管理

    • status: 连接的当前状态(CONNECTINGOPENCLOSED),可以用来监听连接的状态变化。
    • data: 最新接收到的数据,可以在组件中响应式地处理消息内容。
    • error: 当前的错误状态,当连接失败时可以捕获异常。
  2. 事件管理

    • event: 可以处理命名事件。SSE 支持服务器发送不同类型的事件,useEventSource 可以监听并响应指定的事件类型。
    • lastEventId: 记录最新的事件 ID,用于追踪接收到的消息。
  3. 连接控制

    • open(): 手动打开或重新建立与服务器的连接。即便当前连接是活跃的,也可以通过此方法重启连接。
    • close(): 关闭当前的 SSE 连接。
  4. 自动重连

    • autoReconnect: 允许在连接断开后自动重试。支持配置最大重试次数、重连延迟、以及自定义的失败处理逻辑。

三、实践一个简单功能 : SSE实时推送在线用户进行Echarts展示

只是为了介绍如何使用,请不要考虑功能是否复杂、实用

后端接口结果

后端会实时传输目前在线用户数量

image.png

API对接

  1. 后端需要进行身份认证、权限认证
  2. 后端需要记录日志信息
  3. 后端会对接口进行限流、防重复提交等操作

因为我们没有使用插件,所以需要在路径中传递token信息

const API_BASE = '/online_user';

const API_SUFFIXES = {
    /** 在线用户实时流动 */
    STREAM_SSE: '/user-activity-sse',
    /** 省略其他接口 */
};

export class OnlineUserAPI {
    /**
     * 在线用户实时数据展示
     */
    static STREAM_SSE = {
        endpoint: (token: string) => {
            return `${import.meta.env.VITE_APP_API_URL}${API_BASE}${API_SUFFIXES.STREAM_SSE}?Authorization=${token}`
        },
        permission: 'monitor:online-user:list',
        chartOptions: (): EChartsOption => {
            return {
                title: {
                    text: '在线用户统计',   // 图表标题
                    left: 'center'        // 标题居中显示
                },
                xAxis: {
                    type: 'category',
                    data: ['35s', '30s', '25s', '20s', '15s', '10s', '5s'] // 可以根据需要修改为时间段或其他表示
                },
                yAxis: {
                    type: 'value'
                },
                series: [
                    {
                        data: ["0", "0", "0", "0", "0", "0", "0"],  // 数据
                        type: 'line'
                    }
                ]
            }
        }
    }
}

介绍一下 API 信息

  1. OnlineUserAPI.STREAM_SSE.endpoint() 中已经构建好了路径信息,只需要传递一个token即可
  2. OnlineUserAPI.STREAM_SSE.permission 为接口权限信息
  3. OnlineUserAPI.STREAM_SSE.chartOptions() 会返回 ECharts 的图表结构

当我们维护的时候,可以在OnlineUserAPI.STREAM_SSE看到所有接口信息

页面编写

<template>
  <div>
    <el-row :gutter="20">
      <!-- 实时数据 -->
      <el-col :lg="12" :md="12" :sm="12" :span="12" :xs="24">
        <el-card>
          <div ref="onlineChat" style="height: 300px;"></div>
        </el-card>
      </el-col>
      <!--   在线用户表格     -->
      <el-col :lg="12" :md="12" :sm="12" :span="12" :xs="24">
        <el-table :data="tableData" border style="height:300px;width: 100%">
          <el-table-column label="用户名" prop="username"/>
          <el-table-column label="昵称" prop="nickname"/>
          <el-table-column v-permission="[OnlineUserAPI.KICK_OUT.permission]" label="操作">
            <template #default="scope">
              <el-button
                  v-if="userId !== scope.row.userId"
                  link
                  size="small"
                  type="primary"
                  @click.stop="kickOutOnlineUser(scope.row.username,scope.row.userId)"
              >
                踢出用户
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-col>
    </el-row>
  </div>
</template>

<script lang="ts" setup>
import {RequestConstant} from "@/constants/request";
import {useUserStore} from "@/store/modules/user";
import {useECharts} from "@/hooks/useECharts";
import {OnlineUserAPI} from "@/api/monitor/online-user";
import {OnlineUserVO} from "@/api/monitor/online-user/type";

defineOptions({
  name: "OnlineUser",
  inheritAttrs: false,
});

const userStore = useUserStore();
const token = RequestConstant.Header.AuthorizationPrefix + userStore.authInfo.accessToken;
const userId = computed(() => userStore.userInfo.userId);
const {
  data,
  eventSource,
  close
} = useEventSource(OnlineUserAPI.STREAM_SSE.endpoint(token), ['message'] as const, {
  autoReconnect: {
    retries: 3,
    delay: 2000,
    onFailed() {
      ElMessage.error("服务异常,已关闭")
    },
  }
});     // SSE连接

const onlineChat = ref<HTMLDivElement | null>(null);
const {options} = useECharts(onlineChat, OnlineUserAPI.STREAM_SSE.chartOptions()) // ECharts 图表绘画

const loading = ref(false);
const tableData = ref<OnlineUserVO[]>([]);

// 定义事件监听器
function handleMessage(event) {
  const data = event.data;

  // 将新数据添加到数组末尾
  options.series[0].data.push(data);

  // 检查数组长度是否超过 7,如果是则移除最早的一个
  if (options.series[0].data.length > 7) {
    options.series[0].data.shift();
  }
}

function kickOutOnlineUser(username: string, userId: string) {
  // 踢出在线用户
  ElMessageBox.confirm(`确认踢出用户 【<b>${username}</b>】 ?`, '警告', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
    draggable: true,
    dangerouslyUseHTMLString: true
  }).then(() => {
    OnlineUserAPI.KICK_OUT.request(userId).then(() => {
      handelPage();
    });
  })
}

function handelPage() {
  loading.value = true;
  // 获取在线用户
  OnlineUserAPI.PAGE.request().then(({data}) => {
    tableData.value = data;
  }).finally(() => {
    loading.value = false;
  })
}

watch(data, () => {
  // 数据更新则 , 更新在线用户
  if (!loading.value) {
    handelPage()
  }
})

// 添加事件监听器
eventSource.value?.addEventListener('message', handleMessage);

// 在组件卸载时关闭 EventSource 并移除事件监听器
onUnmounted(() => {
  if (eventSource.value) {
    eventSource.value.removeEventListener('message', handleMessage);
    eventSource.value.close();
    console.log("EventSource 连接已关闭且监听器已移除。");
  }
});

onMounted(() => {
  if (!loading.value) {
    handelPage()
  }
})

</script>

代码核心点

  1. SSE 连接 : 使用 useEventSource 进行 SSE 连接
  2. ECharts 图表 : 采用项目封装好的 ECharts Hooks 进行展示 ,ECharts Hooks文章
  3. 实时更新表格 : 并不是所有数据都需要后端通过SSE发送给我们,当在线用户人数改变的时候,我们自己主动去请求在线用户接口即可

效果展示

QQ录屏20240905140238.gif

四、源码 & 结束语

本文只是实践一个小功能,但是SSE是非常强大的,后续更文如何对接后端的AI功能

online-user/index.vue