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

 更新时间:2025年01月02日 09:06:02   作者:black^sugar  
本文介绍了使用Spring Boot和Vue实现服务器发送事件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

思路

一个基于订阅发布机制的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>

效果

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

到此这篇关于springboot+vue实现SSE服务器发送事件的示例的文章就介绍到这了,更多相关springboot vue SSE发送事件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java final关键字用法、场景、面试题全解析

    Java final关键字用法、场景、面试题全解析

    本文全程围绕final的核心用法展开,从“修饰变量、修饰方法、修饰类”三个核心场景,搭配实战代码、避坑细节,再补充高频面试题,帮你彻底吃透final,既适合新手入门,也适合巩固基础、备战面试,感兴趣的朋友一起看看吧
    2026-04-04
  • java实现表格tr拖动的实例(分享)

    java实现表格tr拖动的实例(分享)

    下面小编就为大家分享一篇java实现表格tr拖动的实例。具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • java排查进程占用系统内存高方法

    java排查进程占用系统内存高方法

    这篇文章主要为大家介绍了java进程占用系统内存高排查方法,
    2023-06-06
  • Java实现局域网聊天小程序

    Java实现局域网聊天小程序

    这篇文章主要为大家详细介绍了Java实现局域网聊天小程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • Java并发系列之AbstractQueuedSynchronizer源码分析(条件队列)

    Java并发系列之AbstractQueuedSynchronizer源码分析(条件队列)

    这篇文章主要为大家详细介绍了Java并发系列之AbstractQueuedSynchronizer源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-02-02
  • 如何解决使用restTemplate进行feign调用new HttpEntity<>报错问题

    如何解决使用restTemplate进行feign调用new HttpEntity<>报错问题

    这篇文章主要介绍了如何解决使用restTemplate进行feign调用new HttpEntity<>报错问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • java实现租车系统

    java实现租车系统

    这篇文章主要为大家详细介绍了java实现租车系统,以及遇到的两个问题解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • windows上nacos自启动的三种方法小结

    windows上nacos自启动的三种方法小结

    本文主要给大家介绍了windows上nacos自启动的三种方法,借助WinSW.exe添加到服务列表,修改nacos启动配置以及以开机"启动"方式——启动Nacos的startup.cmd这三种方法,文中通过图文讲解的非常详细,需要的朋友可以参考下
    2023-12-12
  • Java.toCharArray()和charAt()的效率对比分析

    Java.toCharArray()和charAt()的效率对比分析

    这篇文章主要介绍了Java.toCharArray()和charAt()的效率对比分析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • 基于SpringBoot打造一个通用CLI命令系统

    基于SpringBoot打造一个通用CLI命令系统

    在日常开发中,某些情况下可能需要为服务提供一个命令行工具(CLI),方便运维、调试或者远程调用业务接口,下面我们就来使用SpringBoot打造一个通用的CLI命令系统吧
    2025-12-12

最新评论