Spring Boot 2.x / 3.x 中不需要单独引入依赖。
- 只需要引入web依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
后端代码
java
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/sse")
@CrossOrigin("*")
public class SseController {
// 保存每个客户端连接
private final Map<String, SseEmitter> clients = new ConcurrentHashMap<>();
/**
* 客户端建立连接
* 处理客户端的SSE连接请求,创建并保存连接对象,用于后续向客户端推送消息
*/
@GetMapping("/connect/{clientId}")
public SseEmitter connect(@PathVariable String clientId) {
// 创建SseEmitter对象,参数0L表示连接永不超时(默认有超时时间,0L禁用超时)
SseEmitter emitter = new SseEmitter(0L); // 永不超时
// 将客户端ID与对应的SseEmitter存入clients集合(clients应为线程安全的Map,如ConcurrentHashMap,用于管理所有连接)
clients.put(clientId, emitter);
// 注册连接完成回调:当连接正常关闭时,从clients中移除该客户端(避免内存泄漏)
emitter.onCompletion(() -> clients.remove(clientId));
// 注册超时回调:当连接超时时,从clients中移除该客户端
emitter.onTimeout(() -> clients.remove(clientId));
// 注册错误回调:当连接发生错误时,从clients中移除该客户端
emitter.onError(e -> clients.remove(clientId));
// 控制台打印客户端连接成功的信息,用于日志跟踪
System.out.println("Client connected: " + clientId);
// 返回SseEmitter对象,Spring会自动维护该连接,后续可通过此对象向客户端推送消息
return emitter;
}
/**
* 群发消息
* 向所有已连接的客户端推送相同的消息
*/
@GetMapping("/sendAll")
public String sendAll(@RequestParam String msg) {
// 遍历clients集合中所有已连接的客户端(key为clientId,value为对应的SseEmitter)
clients.forEach((id, emitter) -> {
try {
// 通过SseEmitter发送消息:使用event()构建事件,data()设置消息内容(这里添加"群发:"前缀标识消息类型)
emitter.send(SseEmitter.event().data("群发:" + msg));
} catch (IOException e) {
// 若发送失败(如客户端已断开连接),调用complete()关闭该连接
emitter.complete();
// 从clients中移除该客户端,避免下次继续尝试发送
clients.remove(id);
}
});
return "OK";
}
/**
* 单独推送给指定客户端
* 向指定clientId的客户端推送消息
*/
// 定义GET请求接口,路径为"/sendTo",用于接收目标客户端ID和消息内容
@GetMapping("/sendTo")
// 方法接收请求参数clientId(目标客户端ID)和msg(要发送的消息内容),返回"OK"表示处理成功
public String sendTo(@RequestParam String clientId, @RequestParam String msg) {
// 从clients集合中获取指定clientId对应的SseEmitter连接对象
SseEmitter emitter = clients.get(clientId);
// 判断连接对象是否存在(客户端是否在线)
if (emitter != null) {
try {
// 通过SseEmitter发送消息:添加"单发:"前缀标识消息类型
emitter.send(SseEmitter.event().data("单发:" + msg));
} catch (IOException e) {
// 发送失败时,从clients中移除该客户端
clients.remove(clientId);
// 关闭该连接
emitter.complete();
}
}
return "OK";
}
}
前端html
html
<!DOCTYPE html>
<html>
<body>
<h3>服务端推送测试 (SSE)</h3>
<h2 id="title"></h2>
<div id="msg"></div>
<script>
const clientId = "client_" + Math.floor(Math.random() * 10000);
document.getElementById("title").textContent=clientId;
<!-- 创建EventSource对象,建立与服务端的SSE(Server-Sent Events)连接
连接地址为本地服务的"/sse/connect/"+clientId,服务端可通过clientId识别当前客户端
SSE是一种服务端向客户端单向推送消息的技术,连接会长期保持 -->
const source = new EventSource("http://127.0.0.1:8080/sse/connect/"+clientId);
<!-- 给EventSource对象绑定onmessage事件处理函数:
当收到服务端发送的默认类型(无指定event字段)消息时触发
函数逻辑:获取id为"msg"的div,将收到的消息(event.data)用<p>标签包裹后追加到div中,在页面显示 -->
source.onmessage = function (event) {
document.getElementById("msg").innerHTML += "<p>" + event.data + "</p>";
};
<!-- 另一种方式监听"message"事件(与onmessage作用相同,都是处理默认类型消息)
这里的逻辑是在控制台打印收到的消息内容,用于调试 -->
source.addEventListener("message", (e) => {
console.log("Received:", e.data);
});
<!-- 绑定onerror事件处理函数:
当SSE连接发生错误(如连接中断、服务端异常)时触发
函数逻辑:在控制台打印错误信息,用于排查问题 -->
source.onerror = (err) => {
console.error("EventSource failed:", err);
};
</script>
</body>
</html>