springboot+vue实现SSE服务器发送事件

思路

一个基于订阅发布机制的SSE事件。客户端可以请求订阅api(携带客户端id),与服务器建立SSE链接;后续服务器需要推送消息到客户端时,再根据客户端id从已建立链接的会话中找到目标客户端,将消息推送出去。

后端

这个控制器类允许客户端订阅、接收消息和断开连接,通过 pool 存储 SseEmitter 并对其进行管理。

package com.example.q11e.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@RestController
public class SseController {
    // 存储已订阅的客户端的会话列表
    private final Map<String, SseEmitter> pool = new ConcurrentHashMap<>();

    // 向特定的 SseEmitter 发送消息
    public void publisher(String id_sid, int content) {
        // 根据 id_sid 从映射中获取 SseEmitter
        SseEmitter sseEmitter = pool.get(id_sid);
        if (Objects.isNull(sseEmitter)) {
            return;
        }
        try {
            sseEmitter.send(content); // 发送内容
        } catch (IOException e) {
            System.out.println("null " + e);
        }
    }


    // 处理客户端的订阅请求
    @GetMapping("/subscribe/{id}")
    public SseEmitter subscribe(@PathVariable("id") String id_sid) {
        // 根据 id_sid 从映射中获取 SseEmitter
        SseEmitter sseEmitter = pool.get(id_sid);
        if (Objects.isNull(sseEmitter)) {
            // 如果不存在,则创建一个新的 SseEmitter,设置超时时间为 130000 毫秒
            sseEmitter = new SseEmitter(130000L);
            // 设置发送完成事件:从映射中移除该 SseEmitter
            sseEmitter.onCompletion(() -> pool.remove(id_sid)); 
            // 设置超时事件:从映射中移除该 SseEmitter
            sseEmitter.onTimeout((() -> pool.remove(id_sid))); 
            // 将新创建的 SseEmitter 放入映射中
            pool.put(id_sid, sseEmitter);
        }
        // System.out.println(pool);
        // 返回 SseEmitter 给客户端
        return sseEmitter;
    }

    // 处理客户端的断开连接请求
    public void disconnect(String id_sid) {
        SseEmitter emitter = pool.remove(id_sid);
        if (emitter!= null) {
            emitter.complete();
        }
    }
}

发送消息

package com.example.q11e.service;

import com.example.q11e.controller.SseController;

@Service
public class BatchService {
    @Autowired
    public BatchService(SseController sseController) {
        this.sseController = sseController;
    }
    private final SseController sseController;

    @Async
    public void batchRequests(){
        // uid+"_"+sid 客户端标识符,sucCount为需要发送的信息
        sseController.publisher(uid + "_" + sid, sucCount);
        sseController.disconnect(uid + "_" + sid);
    }
}

前端

SSE状态管理  store.ts

// sse前端
import { defineStore } from 'pinia';
import { getUserBalance } from '@/request/api.ts'

export const useESStore = defineStore('EventSource', {
  state: () => ({
    uid: localStorage.getItem('uid'),
    balance: 1,
    eventSourceInstance: null as EventSource | null, // 新增状态属性
    currentSid: null as string | null,
    currentCount: 0,
    currentTotal: 0,
    connect: false
  }),
  actions: {
    setUid(uid:string) {
      this.uid = uid;
    },
    setConnect(connect: boolean) {
      this.connect = connect
    },
    initEventSource(sid:string) {
      if (this.uid) {
        const sseURL = import.meta.env.VITE_SSE_URL
        const evtSrcInstance = new EventSource(sseURL + "/" + this.uid + "_" + sid);
        evtSrcInstance.onmessage = (e) => {
          this.setCurrentCount(e.data) //普通函数时: this-->evtSrcInstance
        };
        evtSrcInstance.onopen = () => {
          this.setCurrentCount(0)
          this.setConnect(true)
        };
        evtSrcInstance.onerror = () => {
          this.setConnect(false)
          this.setCurrentTotal(0)
        };
        this.eventSourceInstance = evtSrcInstance; // 存储实例到状态
      }
    },
    closeEventSource() {
      if (this.eventSourceInstance) {
        this.eventSourceInstance.close();
        this.eventSourceInstance = null;
      }
    }
  }
});
<template>
  <span v-show="connect">
    <span class="sid">{{ sid }}</span>
    <span v-for="(char, index) in ['.', '.', '.']" :key="index" class="blink-effect sid"
      :style="{ animationDelay: `${index * 0.1}s` }">{{ char }}</span>
     <!----count是服务器推送的内容----->
    <span class="process">{{ count }}/{{ total }}</span>
  </span>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { useESStore } from '@/store/store.ts'
const SSE = useESStore()
let count = computed(() => SSE.currentCount)
let total = computed(() => SSE.currentTotal)
let sid = computed(() => SSE.currentSid)
let connect = computed(() => SSE.connect)
</script>

<style scoped>
.process {
  background: red;
  color: white;
  padding: 2px 4px;
}
.sid { color: #333; }
@keyframes blink {
  0%, 100% {
    transform: translateY(0); /* 开始和结束状态位置无变化 */
  }
  50% {
    transform: translateY(-5px); /* 中间状态位置向上移动5px */
  }
}
.blink-effect {
  display: inline-block;
  animation: blink 1s infinite;
}
</style>

效果

后端执行某耗时任务时,需要实时推送进度到客户端,每完成一个阶段,就向客户端推送一个单位进度,做到客户端实时显示进度的效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/946909.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用XGBoost算法进行机器学习任务:从理论到实践

目录 使用XGBoost算法进行机器学习任务&#xff1a;从理论到实践引言1. XGBoost算法简介2. XGBoost的数学原理3. 环境准备与数据集介绍3.1 环境准备3.2 数据集介绍 4. XGBoost的PyTorch实现4.1 数据预处理4.2 XGBoost模型定义4.3 模型训练与评估 5. 结果分析与可视化5.1 绘制损…

NeurIPS 2024 | 像素级LLM实现图像视频理解、生成、分割和编辑大统一(昆仑万维等)

Accepted by NeurIPS 2024 文章链接&#xff1a;https://arxiv.org/pdf/2412.19806 项目链接&#xff1a;https://vitron-llm.github.io/ Github链接&#xff1a;https://github.com/SkyworkAI/Vitron 亮点直击 首次提出了一种通用的视觉多模态大语言模型&#xff08;MLLM&…

P8打卡——YOLOv5-C3模块实现天气识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 1.检查GPU import torch import torch.nn as nn import torchvision.transforms as transforms import torchvision from torchvision import transforms, dat…

OpenCV-Python实战(16)——单/多模板匹配

一、模板匹配函数 cv2.matchTemplate() result cv2.matchTemplate(image*,templ*,method*,mask*) result&#xff1a;函数返回值&#xff0c;比较结果的数组。 image&#xff1a;原始图像。 templ&#xff1a;模板图像。templ.shape<image.shape。 method&#xff1a;…

分布式 L2 网关下的 OVS 未知单播泛洪

大家读完觉得有意义和帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 目录 1 问题描述 2 基础设施和环境信息 3 故障排除 3.1 确认&#xff1a;单播泛洪 3.2 确认&#xff1a;所有泛洪流量都以 L2 GW 为目标 3.3 验证&#xff1a;容器 ARP 处于活动状态时&…

天猫推荐数据集实践

参考自 https://github.com/xufengtt/recom_teach_code&#xff0c;学习记录。 环境配置&#xff08;maxcomputedataworks&#xff09; 下载天猫推荐数据集&#xff1b;开启 aliyun 的 maxcompute&#xff0c;dataworks&#xff0c;pai&#xff1b;使用 odpscmd 上传本地数据…

库的概念:动态库与静态库

在软件开发中&#xff0c;库是代码复用的核心工具&#xff0c;它帮助开发者避免重复造轮子&#xff0c;提升开发效率。库可以分为动态库和静态库&#xff0c;这两者在程序开发中的使用方式、链接过程和性能上存在显著区别。本文将详细讲解动态库与静态库的定义、区别、链接过程…

Flink源码解析之:如何根据JobGraph生成ExecutionGraph

Flink源码解析之&#xff1a;如何根据JobGraph生成ExecutionGraph 在上一篇Flink源码解析中&#xff0c;我们介绍了Flink如何根据StreamGraph生成JobGraph的流程&#xff0c;并着重分析了其算子链的合并过程和JobGraph的构造流程。 对于StreamGraph和JobGraph的生成来说&…

风力涡轮机缺陷检测数据集,91.4%准确识别率,18912张图片,支持yolo,PASICAL VOC XML,COCO JSON格式的标注

风力涡轮机缺陷检测数据集&#xff0c;91.4&#xff05;准确识别率&#xff0c;18912张图片&#xff0c;支持yolo&#xff0c;PASICAL VOC XML&#xff0c;COCO JSON格式的标注 数据集下载&#xff1a; &#xff59;&#xff4f;&#xff4c;&#xff4f; &#xff56;&#…

系统设计——大文件传输方案设计

摘要 大文件传输是指通过网络将体积较大的文件从一个位置发送到另一个位置的过程。这些文件可能包括高清视频、大型数据库、复杂的软件安装包等&#xff0c;它们的大小通常超过几百兆字节&#xff08;MB&#xff09;甚至达到几个吉字节&#xff08;GB&#xff09;或更大。大文…

linux中执行命令

1.1 命令格式 命令格式&#xff1a; 主命令 选项 参数&#xff08;操作对象&#xff09; 命令分为两类&#xff1a; 内置命令&#xff08; builtin &#xff09;&#xff1a;由 shell 程序自带的命令 外部命令&#xff1a;有独立的可执行程序文件&#xff0c;文件名即命令…

Elasticsearch:当混合搜索真正发挥作用时

作者&#xff1a;来自 Elastic Gustavo Llermaly 展示混合搜索何时优于单独的词汇或语义搜索。 在本文中&#xff0c;我们将通过示例探讨混合搜索&#xff0c;并展示它与单独使用词汇或语义搜索技术相比的真正优势。 什么是混合搜索&#xff1f; 混合搜索是一种结合了不同搜索…

Python pyside6 设置的一个《广告图片生成器》

一、图&#xff1a; 二、说明书&#xff1a; 广告图片生成器使用说明 软件功能 这是一个用于生成广告图片的工具&#xff0c;可以快速制作包含产品图片和文字的广告图片。 主要特点 自定义广告尺寸&#xff08;默认620420像素&#xff09; 智能去除产品图片背景 自动排版&…

Spark基本介绍

一&#xff0c;Spark是什么 1.定义&#xff1a;Aache Spark是用于大规模数据处理的统一分析引擎。 二&#xff0c;Spark的发展 三&#xff0c;Spark的特点 高效性 计算速度快 提供了一个全新的数据结构RDD&#xff08;弹性分布式数据集&#xff09;。整个计算操作&#xff0c;…

Elasticsearch操作笔记版

文章目录 1.ES索引库操作(CRUD)1.mapping常见属性(前提)2.创建索引库3.查询&#xff0c;删除索引库4.修改索引库 2.ES文档操作(CRUD)1.新增文档2.查询、删除文档查询返回的数据解读&#xff1a; 3.修改文档 3.RestClient操作(索引库/文档)(CRUD)1.什么是RestClient2.需要考虑前…

EFEVD: Enhanced Feature Extraction for Smart Contract Vulnerability Detection

假设&#xff0c;攻击者在合约 Dao 内存放有 1 Ether 攻击者调用 withdraw 函数&#xff0c;提取 1 Ether&#xff1b; 函数执行到 require 之后&#xff0c; balances 之前时&#xff0c;6789-6789-6789- contract Dao {function withdraw() public {require(balances[msg.…

我的线代观-秩(向量,矩阵)

都说秩是线代中不可避免的一环&#xff0c;当然&#xff0c;它其中最重要的一环。 我在学习线代之后&#xff0c;也有这种感受&#xff0c;它有着一种很绕的感受。 1.矩阵中 在矩阵中&#xff0c;它的秩是怎么定义的呢。它常常与行列式扯上关系&#xff0c;我们拿三阶矩阵为例…

ES IK分词字典热更新

前言 在使用IK分词器的时候&#xff0c;发现官方默认的分词不满足我们的需求&#xff0c;那么有没有方法可以自定义字典呢&#xff1f; 官方提供了三种方式 一、ik本地文件读取方式 k插件本来已为用户提供自定义词典扩展功能&#xff0c;只要修改配给文件即可&#xff1a; …

基于Spring Boot的电影网站系统

一、技术架构 后端框架&#xff1a;Spring Boot&#xff0c;它提供了自动配置、简化依赖管理、内嵌式容器等特性&#xff0c;使得开发者可以快速搭建起一个功能完备的Web应用。 前端技术&#xff1a;可能采用Vue.js、JS、jQuery、Ajax等技术&#xff0c;结合Element UI等组件库…

C#运动控制系统:雷赛控制卡实用完整例子 C#雷赛开发快速入门 C#雷赛运动控制系统实战例子 C#快速开发雷赛控制卡

雷赛控制技术 DMC系列运动控制卡是一款新型的 PCI/PCIe 总线运动控制卡。可以控制多个步进电机或数字式伺服电机&#xff1b;适合于多轴点位运动、插补运动、轨迹规划、手轮控制、编码器位置检测、IO 控制、位置比较、位置锁存等功能的应用。 DMC3000 系列卡的运动控制函数库功…