springboot+vue實現SSE服務器發(fā)送事件的示例
思路
一個基于訂閱發(fā)布機制的SSE事件。客戶端可以請求訂閱api(攜帶客戶端id),與服務器建立SSE鏈接;后續(xù)服務器需要推送消息到客戶端時,再根據客戶端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) {
// 根據 id_sid 從映射中獲取 SseEmitter
SseEmitter sseEmitter = pool.get(id_sid);
if (Objects.isNull(sseEmitter)) {
return;
}
try {
sseEmitter.send(content); // 發(fā)送內容
} 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)) {
// 如果不存在,則創(chuàng)建一個新的 SseEmitter,設置超時時間為 130000 毫秒
sseEmitter = new SseEmitter(130000L);
// 設置發(fā)送完成事件:從映射中移除該 SseEmitter
sseEmitter.onCompletion(() -> pool.remove(id_sid));
// 設置超時事件:從映射中移除該 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) //普通函數時: 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是服務器推送的內容----->
<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); /* 開始和結束狀態(tài)位置無變化 */
}
50% {
transform: translateY(-5px); /* 中間狀態(tài)位置向上移動5px */
}
}
.blink-effect {
display: inline-block;
animation: blink 1s infinite;
}
</style>
效果
后端執(zhí)行某耗時任務時,需要實時推送進度到客戶端,每完成一個階段,就向客戶端推送一個單位進度,做到客戶端實時顯示進度的效果。

到此這篇關于springboot+vue實現SSE服務器發(fā)送事件的示例的文章就介紹到這了,更多相關springboot vue SSE發(fā)送事件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot 將yyyy-MM-dd格式的文本字符串直接轉換為LocalDateTime出現的問題
這篇文章主要介紹了Spring Boot 將yyyy-MM-dd格式的文本字符串直接轉換為LocalDateTime出現的問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09
Java DriverManager.getConnection()獲取數據庫連接
這篇文章主要介紹了Java DriverManager.getConnection()獲取數據庫連接,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01

