Spring?Boot?使用?SSE?方式向前端推送數(shù)據(jù)詳解
前言
SSE簡單的來說就是服務器主動向前端推送數(shù)據(jù)的一種技術(shù),它是單向的,也就是說前端是不能向服務器發(fā)送數(shù)據(jù)的。SSE適用于消息推送,監(jiān)控等只需要服務器推送數(shù)據(jù)的場景中,下面是使用Spring Boot 來實現(xiàn)一個簡單的模擬向前端推動進度數(shù)據(jù),前端頁面接受后展示進度條。
服務端
在Spring Boot中使用時需要注意,最好使用Spring Web 提供的SseEmitter這個類來進行操作,我在剛開始時使用網(wǎng)上說的將Content-Type設置為text-stream這種方式發(fā)現(xiàn)每次前端每次都會重新創(chuàng)建接。最后參考該文實現(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 {
/**
* 當前連接數(shù)
*/
private static AtomicInteger count = new AtomicInteger(0);
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public static SseEmitter connect(String userId){
//設置超時時間,0表示不過期,默認是30秒,超過時間未完成會拋出異常
SseEmitter sseEmitter = new SseEmitter(0L);
//注冊回調(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("連接超時,{}",userId);
removeUser(userId);
};
}
private static Consumer<Throwable> errorCallBack(String userId){
return throwable -> {
log.error("連接異常,{}",userId);
removeUser(userId);
};
}
}上面這個類可以把它當作一個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是用來連接sse的,它返回一個SseEmitter實例,這時候連接就已經(jīng)創(chuàng)建了,然后下面的process接口是用來推送數(shù)據(jù)的,我這里是準備讓前端實現(xiàn)一個進度條的效果,所以推送的是數(shù)字,為了效果明顯,我在推送到50到70的時候速度放慢,其余都是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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
認識Java底層操作系統(tǒng)與并發(fā)基礎(chǔ)
這篇文章主要介紹了認識Java底層操作系統(tǒng)與并發(fā)基礎(chǔ),文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-07-07
利用反射獲取Java類中的靜態(tài)變量名及變量值的簡單實例
下面小編就為大家?guī)硪黄梅瓷浍@取Java類中的靜態(tài)變量名及變量值的簡單實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12
Spring @Configuration和@Component的區(qū)別
今天小編就為大家分享一篇關(guān)于Spring @Configuration和@Component的區(qū)別,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12
詳解SSM框架下結(jié)合log4j、slf4j打印日志
本篇文章主要介紹了詳解SSM框架下結(jié)合log4j、slf4j打印日志,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
解決Maven的pom.xml中設置repository不起作用問題
這篇文章主要介紹了解決Maven的pom.xml中設置repository不起作用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

