Spring?Boot?使用?SSE?方式向前端推送數(shù)據(jù)詳解
前言
SSE簡(jiǎn)單的來(lái)說(shuō)就是服務(wù)器主動(dòng)向前端推送數(shù)據(jù)的一種技術(shù),它是單向的,也就是說(shuō)前端是不能向服務(wù)器發(fā)送數(shù)據(jù)的。SSE適用于消息推送,監(jiān)控等只需要服務(wù)器推送數(shù)據(jù)的場(chǎng)景中,下面是使用Spring Boot 來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的模擬向前端推動(dòng)進(jìn)度數(shù)據(jù),前端頁(yè)面接受后展示進(jìn)度條。
服務(wù)端
在Spring Boot中使用時(shí)需要注意,最好使用Spring Web 提供的SseEmitter這個(gè)類來(lái)進(jìn)行操作,我在剛開始時(shí)使用網(wǎng)上說(shuō)的將Content-Type設(shè)置為text-stream這種方式發(fā)現(xiàn)每次前端每次都會(huì)重新創(chuàng)建接。最后參考該文實(shí)現(xiàn)了最終想要的效果:
SSE工具類
SSEServer.java
package vip.huhailong.catchat.sse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; /** * @author Huhailong */ @Slf4j public class SSEServer { /** * 當(dāng)前連接數(shù) */ private static AtomicInteger count = new AtomicInteger(0); private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>(); public static SseEmitter connect(String userId){ //設(shè)置超時(shí)時(shí)間,0表示不過(guò)期,默認(rèn)是30秒,超過(guò)時(shí)間未完成會(huì)拋出異常 SseEmitter sseEmitter = new SseEmitter(0L); //注冊(cè)回調(diào) sseEmitter.onCompletion(completionCallBack(userId)); sseEmitter.onError(errorCallBack(userId)); sseEmitter.onTimeout(timeOutCallBack(userId)); sseEmitterMap.put(userId,sseEmitter); //數(shù)量+1 count.getAndIncrement(); log.info("create new sse connect ,current user:{}",userId); return sseEmitter; } /** * 給指定用戶發(fā)消息 */ public static void sendMessage(String userId, String message){ if(sseEmitterMap.containsKey(userId)){ try{ sseEmitterMap.get(userId).send(message); }catch (IOException e){ log.error("user id:{}, send message error:{}",userId,e.getMessage()); e.printStackTrace(); } } } /** * 想多人發(fā)送消息,組播 */ public static void groupSendMessage(String groupId, String message){ if(sseEmitterMap!=null&&!sseEmitterMap.isEmpty()){ sseEmitterMap.forEach((k,v) -> { try{ if(k.startsWith(groupId)){ v.send(message, MediaType.APPLICATION_JSON); } }catch (IOException e){ log.error("user id:{}, send message error:{}",groupId,message); removeUser(k); } }); } } public static void batchSendMessage(String message) { sseEmitterMap.forEach((k,v)->{ try{ v.send(message,MediaType.APPLICATION_JSON); }catch (IOException e){ log.error("user id:{}, send message error:{}",k,e.getMessage()); removeUser(k); } }); } /** * 群發(fā)消息 */ public static void batchSendMessage(String message, Set<String> userIds){ userIds.forEach(userId->sendMessage(userId,message)); } public static void removeUser(String userId){ sseEmitterMap.remove(userId); //數(shù)量-1 count.getAndDecrement(); log.info("remove user id:{}",userId); } public static List<String> getIds(){ return new ArrayList<>(sseEmitterMap.keySet()); } public static int getUserCount(){ return count.intValue(); } private static Runnable completionCallBack(String userId) { return () -> { log.info("結(jié)束連接,{}",userId); removeUser(userId); }; } private static Runnable timeOutCallBack(String userId){ return ()->{ log.info("連接超時(shí),{}",userId); removeUser(userId); }; } private static Consumer<Throwable> errorCallBack(String userId){ return throwable -> { log.error("連接異常,{}",userId); removeUser(userId); }; } }
上面這個(gè)類可以把它當(dāng)作一個(gè)SSE的工具類,下面我們使用一下它
在Controller層創(chuàng)建 SSEController.java
package vip.huhailong.catchat.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import vip.huhailong.catchat.sse.SSEServer; /** * @author Huhailong */ @Slf4j @RestController @CrossOrigin @RequestMapping("/sse") public class SSEController { @GetMapping("/connect/{userId}") public SseEmitter connect(@PathVariable String userId){ return SSEServer.connect(userId); } @GetMapping("/process") public void sendMessage() throws InterruptedException { for(int i=0; i<=100; i++){ if(i>50&&i<70){ Thread.sleep(500L); }else{ Thread.sleep(100L); } SSEServer.batchSendMessage(String.valueOf(i)); } } }
上面的connect是用來(lái)連接sse的,它返回一個(gè)SseEmitter實(shí)例,這時(shí)候連接就已經(jīng)創(chuàng)建了,然后下面的process接口是用來(lái)推送數(shù)據(jù)的,我這里是準(zhǔn)備讓前端實(shí)現(xiàn)一個(gè)進(jìn)度條的效果,所以推送的是數(shù)字,為了效果明顯,我在推送到50到70的時(shí)候速度放慢,其余都是100ms
前端代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home</title> <script> let data = new EventSource("/cat-chat/sse/connect/huhailong") data.onmessage = function(event){ console.log("test=>",event) document.getElementById("result").innerText = event.data+'%'; document.getElementById("my-progress").value = event.data; } </script> </head> <body> <div id="result"></div> <progress style="width: 300px" id="my-progress" value="0" max="100"></progress> </body> </html>
最終效果:
到此這篇關(guān)于Spring Boot 使用 SSE 方式向前端推送數(shù)據(jù)詳解的文章就介紹到這了,更多相關(guān)Spring Boot SSE內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息的示例
- SpringBoot2.0集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息
- SpringBoot整合WebSocket實(shí)現(xiàn)后端向前端主動(dòng)推送消息方式
- SpringBoot集成WebSocket實(shí)現(xiàn)后臺(tái)向前端推送信息
- SpringBoot使用WebSocket實(shí)現(xiàn)向前端推送消息功能
- SpringBoot+WebSocket向前端推送消息的實(shí)現(xiàn)示例
- 利用Netty+SpringBoot實(shí)現(xiàn)定時(shí)后端向前端推送數(shù)據(jù)
相關(guān)文章
認(rèn)識(shí)Java底層操作系統(tǒng)與并發(fā)基礎(chǔ)
這篇文章主要介紹了認(rèn)識(shí)Java底層操作系統(tǒng)與并發(fā)基礎(chǔ),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-07-07利用反射獲取Java類中的靜態(tài)變量名及變量值的簡(jiǎn)單實(shí)例
下面小編就為大家?guī)?lái)一篇利用反射獲取Java類中的靜態(tài)變量名及變量值的簡(jiǎn)單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12Spring @Configuration和@Component的區(qū)別
今天小編就為大家分享一篇關(guān)于Spring @Configuration和@Component的區(qū)別,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12使用springboot對(duì)linux進(jìn)行操控的方法示例
這篇文章主要介紹了使用springboot對(duì)linux進(jìn)行操控的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11詳解SSM框架下結(jié)合log4j、slf4j打印日志
本篇文章主要介紹了詳解SSM框架下結(jié)合log4j、slf4j打印日志,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11SpringBoot實(shí)現(xiàn)字段自動(dòng)填充的兩種方式
每個(gè)字段在插入數(shù)據(jù)庫(kù),或者更新時(shí)都要在serviceimpl層對(duì)creatby,updateby等字段進(jìn)行填充,這個(gè)太繁瑣了,所以本文給大家介紹了SpringBoot實(shí)現(xiàn)字段自動(dòng)填充的兩種方式,需要的朋友可以參考下2024-11-11解決Maven的pom.xml中設(shè)置repository不起作用問(wèn)題
這篇文章主要介紹了解決Maven的pom.xml中設(shè)置repository不起作用問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03SpringBoot詳解自定義Stater的應(yīng)用
Springboot的出現(xiàn)極大的簡(jiǎn)化了開發(fā)人員的配置,而這之中的一大利器便是springboot的starter,starter是springboot的核心組成部分,springboot官方同時(shí)也為開發(fā)人員封裝了各種各樣方便好用的starter模塊2022-07-07