Spring Boot中使用Server-Sent Events (SSE) 實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送教程
一、簡(jiǎn)介
Server-Sent Events (SSE) 是HTML5引入的一種輕量級(jí)的服務(wù)器向?yàn)g覽器客戶端單向推送實(shí)時(shí)數(shù)據(jù)的技術(shù)。在Spring Boot框架中,我們可以很容易地集成并利用SSE來實(shí)現(xiàn)實(shí)時(shí)通信。
二、依賴添加
在Spring Boot項(xiàng)目中,無需額外引入特定的依賴,因?yàn)镾pring Web MVC模塊已經(jīng)內(nèi)置了對(duì)SSE的支持。
輔助Maven
<!-- 集成beetl --> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.2.30.RELEASE</version> </dependency> <!-- 集成hutool工具類簡(jiǎn)便操作 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.10</version> </dependency>
三、編寫核心SSE Client
@Slf4j @Component public class SseClient { private static final Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>(); /** * 創(chuàng)建連接 */ public SseEmitter createSse(String uid) { //默認(rèn)30秒超時(shí),設(shè)置為0L則永不超時(shí) SseEmitter sseEmitter = new SseEmitter(0l); //完成后回調(diào) sseEmitter.onCompletion(() -> { log.info("[{}]結(jié)束連接...................", uid); sseEmitterMap.remove(uid); }); //超時(shí)回調(diào) sseEmitter.onTimeout(() -> { log.info("[{}]連接超時(shí)...................", uid); }); //異?;卣{(diào) sseEmitter.onError( throwable -> { try { log.info("[{}]連接異常,{}", uid, throwable.toString()); sseEmitter.send(SseEmitter.event() .id(uid) .name("發(fā)生異常!") .data("發(fā)生異常請(qǐng)重試!") .reconnectTime(3000)); sseEmitterMap.put(uid, sseEmitter); } catch (IOException e) { e.printStackTrace(); } } ); try { sseEmitter.send(SseEmitter.event().reconnectTime(5000)); } catch (IOException e) { e.printStackTrace(); } sseEmitterMap.put(uid, sseEmitter); log.info("[{}]創(chuàng)建sse連接成功!", uid); return sseEmitter; } /** * 給指定用戶發(fā)送消息 * */ public boolean sendMessage(String uid,String messageId, String message) { if (StrUtil.isBlank(message)) { log.info("參數(shù)異常,msg為null", uid); return false; } SseEmitter sseEmitter = sseEmitterMap.get(uid); if (sseEmitter == null) { log.info("消息推送失敗uid:[{}],沒有創(chuàng)建連接,請(qǐng)重試。", uid); return false; } try { sseEmitter.send(SseEmitter.event().id(messageId).reconnectTime(1*60*1000L).data(message)); log.info("用戶{},消息id:{},推送成功:{}", uid,messageId, message); return true; }catch (Exception e) { sseEmitterMap.remove(uid); log.info("用戶{},消息id:{},推送異常:{}", uid,messageId, e.getMessage()); sseEmitter.complete(); return false; } } /** * 斷開 * @param uid */ public void closeSse(String uid){ if (sseEmitterMap.containsKey(uid)) { SseEmitter sseEmitter = sseEmitterMap.get(uid); sseEmitter.complete(); sseEmitterMap.remove(uid); }else { log.info("用戶{} 連接已關(guān)閉",uid); } } }
- 創(chuàng)建SSE 端點(diǎn):創(chuàng)建一個(gè)SseEmitter,用uid進(jìn)行標(biāo)識(shí),uid可以是用戶標(biāo)識(shí)符,也可以是業(yè)務(wù)標(biāo)識(shí)符??梢岳斫鉃橥ㄐ判诺罉?biāo)識(shí)。
- 通過端點(diǎn)發(fā)送事件:可以定時(shí)或在事件發(fā)生時(shí)調(diào)用sseEmitter.send()方法來發(fā)送事件。
- 關(guān)閉端點(diǎn)連接
四、編寫Controller
@Controller public class IndexAction { @Autowired private SseClient sseClient; @GetMapping("/") public String index(ModelMap model) { String uid = IdUtil.fastUUID(); model.put("uid",uid); return "index"; } @CrossOrigin @GetMapping("/createSse") public SseEmitter createConnect(String uid) { return sseClient.createSse(uid); } @CrossOrigin @GetMapping("/sendMsg") @ResponseBody public String sseChat(String uid) { for (int i = 0; i < 10; i++) { sseClient.sendMessage(uid, "no"+i,IdUtil.fastUUID()); } return "ok"; } /** * 關(guān)閉連接 */ @CrossOrigin @GetMapping("/closeSse") public void closeConnect(String uid ){ sseClient.closeSse(uid); } }
1,打開頁面默認(rèn)頁面,傳遞端點(diǎn)標(biāo)識(shí)。
2,連接端點(diǎn)(/createSse),頁面需要使用
3,通過ajax(/sendMsg),觸發(fā)后端業(yè)務(wù)(循環(huán)十條數(shù)據(jù)發(fā)往頁面),向頁面發(fā)送消息。
4,主動(dòng)關(guān)閉連接(/closeSse)
五、前端接收與處理
HTML & JavaScript
在前端頁面,使用EventSource API訂閱SSE endpoint:
Html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="con"></div> <script> let chat = document.getElementById("con"); if (window.EventSource) { //創(chuàng)建sse eventSource = new EventSource(`/createSse?uid=${uid}`); eventSource.onopen = function (event) { console.log('SSE鏈接成功'); } eventSource.onmessage = function (event) { if(event.data){ chat.innerHTML += event.data + '<br/>'; //console.log('后端返回的數(shù)據(jù):', data.value); } } eventSource.onerror = (error) => { console.log('SSE鏈接失敗'); }; } else { alert("你的瀏覽器不支持SSE"); } </script> </body> </html>
在這個(gè)例子中,前端每接收到一次SSE推送的事件,就會(huì)在id為"con"的元素中追加數(shù)據(jù)。
六、注意事項(xiàng)
- 當(dāng)客戶端斷開連接時(shí),SseEmitter會(huì)拋出IOException,所以務(wù)必捕獲并處理這種異常,通常情況下我們會(huì)調(diào)用
emitter.complete()
或emitter.completeWithError()
來關(guān)閉SseEmitter。 - SSE連接是持久性的,長(zhǎng)時(shí)間保持連接可能需要處理超時(shí)和重連問題。
- 考慮到資源消耗,對(duì)于大量的并發(fā)客戶端,可能需要采用連接池或者其他優(yōu)化策略。
總結(jié),Spring Boot中利用SSE實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送既簡(jiǎn)單又實(shí)用,特別適合實(shí)時(shí)更新頻率不高、實(shí)時(shí)性要求不嚴(yán)苛的場(chǎng)景。同時(shí),在高并發(fā)場(chǎng)景下需要注意資源管理和優(yōu)化策略的選擇。
到此這篇關(guān)于Spring Boot中使用Server-Sent Events (SSE) 實(shí)現(xiàn)實(shí)時(shí)數(shù)據(jù)推送教程的文章就介紹到這了,更多相關(guān)SpringBoot 實(shí)時(shí)數(shù)據(jù)推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis?超詳細(xì)講解動(dòng)態(tài)SQL的實(shí)現(xiàn)
動(dòng)態(tài)?SQL?是?MyBatis?的強(qiáng)大特性之一。如果你使用過?JDBC?或其它類似的框架,你應(yīng)該能理解根據(jù)不同條件拼接?SQL?語句有多痛苦,例如拼接時(shí)要確保不能忘記添加必要的空格,還要注意去掉列表最后一個(gè)列名的逗號(hào)。利用動(dòng)態(tài)?SQL,可以徹底擺脫這種痛苦2022-03-03詳解Spring Boot對(duì) Apache Pulsar的支持
Spring Boot通過提供spring-pulsar和spring-pulsar-reactive自動(dòng)配置支持Apache Pulsar,類路徑中這些依賴存在時(shí),Spring Boot自動(dòng)配置命令式和反應(yīng)式Pulsar組件,PulsarClient自動(dòng)注冊(cè),默認(rèn)連接本地Pulsar實(shí)例,感興趣的朋友一起看看吧2024-11-11Java中的Sentinel持久化規(guī)則啟動(dòng)
這篇文章主要介紹了Java中的Sentinel持久化規(guī)則啟動(dòng),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08Spring Boot中使用Actuator的/info端點(diǎn)輸出Git版本信息
這篇文章主要介紹了Spring Boot中使用Actuator的/info端點(diǎn)輸出Git版本信息,需要的朋友可以參考下2017-06-06Java 14 發(fā)布了,你還會(huì)使用Lombok?
2020年3月17日發(fā)布,Java正式發(fā)布了JDK 14 ,目前已經(jīng)可以開放下載。在JDK 14中,共有16個(gè)新特性,本文主要來介紹其中的一個(gè)特性:JEP 359: Records,需要的朋友可以參考下2020-04-04