springboot+vue實現(xiàn)SSE服務(wù)器發(fā)送事件的示例
思路
一個基于訂閱發(fā)布機制的SSE事件。客戶端可以請求訂閱api(攜帶客戶端id),與服務(wù)器建立SSE鏈接;后續(xù)服務(wù)器需要推送消息到客戶端時,再根據(jù)客戶端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 發(fā)送消息 public void publisher(String id_sid, int content) { // 根據(jù) id_sid 從映射中獲取 SseEmitter SseEmitter sseEmitter = pool.get(id_sid); if (Objects.isNull(sseEmitter)) { return; } try { sseEmitter.send(content); // 發(fā)送內(nèi)容 } catch (IOException e) { System.out.println("null " + e); } } // 處理客戶端的訂閱請求 @GetMapping("/subscribe/{id}") public SseEmitter subscribe(@PathVariable("id") String id_sid) { // 根據(jù) id_sid 從映射中獲取 SseEmitter SseEmitter sseEmitter = pool.get(id_sid); if (Objects.isNull(sseEmitter)) { // 如果不存在,則創(chuàng)建一個新的 SseEmitter,設(shè)置超時時間為 130000 毫秒 sseEmitter = new SseEmitter(130000L); // 設(shè)置發(fā)送完成事件:從映射中移除該 SseEmitter sseEmitter.onCompletion(() -> pool.remove(id_sid)); // 設(shè)置超時事件:從映射中移除該 SseEmitter sseEmitter.onTimeout((() -> pool.remove(id_sid))); // 將新創(chuàng)建的 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(); } } }
發(fā)送消息
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為需要發(fā)送的信息 sseController.publisher(uid + "_" + sid, sucCount); sseController.disconnect(uid + "_" + sid); } }
前端
SSE狀態(tài)管理 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, // 新增狀態(tài)屬性 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) //普通函數(shù)時: this-->evtSrcInstance }; evtSrcInstance.onopen = () => { this.setCurrentCount(0) this.setConnect(true) }; evtSrcInstance.onerror = () => { this.setConnect(false) this.setCurrentTotal(0) }; this.eventSourceInstance = evtSrcInstance; // 存儲實例到狀態(tài) } }, 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是服務(wù)器推送的內(nèi)容-----> <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); /* 開始和結(jié)束狀態(tài)位置無變化 */ } 50% { transform: translateY(-5px); /* 中間狀態(tài)位置向上移動5px */ } } .blink-effect { display: inline-block; animation: blink 1s infinite; } </style>
效果
后端執(zhí)行某耗時任務(wù)時,需要實時推送進度到客戶端,每完成一個階段,就向客戶端推送一個單位進度,做到客戶端實時顯示進度的效果。
到此這篇關(guān)于springboot+vue實現(xiàn)SSE服務(wù)器發(fā)送事件的示例的文章就介紹到這了,更多相關(guān)springboot vue SSE發(fā)送事件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot 將yyyy-MM-dd格式的文本字符串直接轉(zhuǎn)換為LocalDateTime出現(xiàn)的問題
這篇文章主要介紹了Spring Boot 將yyyy-MM-dd格式的文本字符串直接轉(zhuǎn)換為LocalDateTime出現(xiàn)的問題,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java DriverManager.getConnection()獲取數(shù)據(jù)庫連接
這篇文章主要介紹了Java DriverManager.getConnection()獲取數(shù)據(jù)庫連接,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01SpringSecurity實現(xiàn)圖形驗證碼功能的實例代碼
Spring Security 的前身是 Acegi Security ,是 Spring 項目組中用來提供安全認證服務(wù)的框架。這篇文章主要介紹了SpringSecurity實現(xiàn)圖形驗證碼功能,需要的朋友可以參考下2018-10-10SpringBoot整合mybatis常見問題(小結(jié))
這篇文章主要介紹了SpringBoot整合mybatis常見問題(小結(jié)),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12