一、介绍
做前端开发的我们,最怕的就是和后端的接口打交道,尤其是像 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
核心功能
-
响应式的状态管理
status
: 连接的当前状态(CONNECTING
、OPEN
、CLOSED
),可以用来监听连接的状态变化。data
: 最新接收到的数据,可以在组件中响应式地处理消息内容。error
: 当前的错误状态,当连接失败时可以捕获异常。
-
事件管理
event
: 可以处理命名事件。SSE 支持服务器发送不同类型的事件,useEventSource
可以监听并响应指定的事件类型。lastEventId
: 记录最新的事件 ID,用于追踪接收到的消息。
-
连接控制
open()
: 手动打开或重新建立与服务器的连接。即便当前连接是活跃的,也可以通过此方法重启连接。close()
: 关闭当前的 SSE 连接。
-
自动重连
autoReconnect
: 允许在连接断开后自动重试。支持配置最大重试次数、重连延迟、以及自定义的失败处理逻辑。
三、实践一个简单功能 : SSE实时推送在线用户进行Echarts展示
只是为了介绍如何使用,请不要考虑功能是否复杂、实用
后端接口结果
后端会实时传输目前在线用户数量
API对接
- 后端需要进行身份认证、权限认证
- 后端需要记录日志信息
- 后端会对接口进行限流、防重复提交等操作
因为我们没有使用插件,所以需要在路径中传递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 信息
OnlineUserAPI.STREAM_SSE.endpoint()
中已经构建好了路径信息,只需要传递一个token即可OnlineUserAPI.STREAM_SSE.permission
为接口权限信息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>
代码核心点
- SSE 连接 : 使用
useEventSource
进行 SSE 连接 - ECharts 图表 : 采用项目封装好的 ECharts Hooks 进行展示 ,ECharts Hooks文章
- 实时更新表格 : 并不是所有数据都需要后端通过SSE发送给我们,当在线用户人数改变的时候,我们自己主动去请求在线用户接口即可
效果展示
四、源码 & 结束语
本文只是实践一个小功能,但是SSE是非常强大的,后续更文如何对接后端的AI功能