现在很多的场景需要实现服务端向客户端发送消息的功能,比如AI对话,实时数据推送等等,这是我之前封装的SSE前后代码,支持断开重连,心跳机制,消息重发,前端订阅回传等功能。代码复制粘贴可用。
一、后端部分
SSEMessage 类SSE消息实体
@Data
public class SSEMessage {
private SseEnum event; // 事件类型(如"update")
private String data; // 消息内容
private String id; // 消息ID
}
SseEnum消息类型枚举类
public enum SseEnum {
HEARTBEAT("heartbeat"),
NOTIFICATION("notification"),
DATA("data"),
ERROR("error"),
SYSTEM("system"),
CUSTOM("custom"),
CHAT("chat")
private String val;
SseEnum(String val) {
this.val = val;
}
public String value(){
return val;
}
}
这里引入线程池,用来执行定时任务。
@Component
public class ThreadPoolManager {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolManager.class);
private static final int CORE_POOL_SIZE_MIN = 2;
private static volatile ScheduledExecutorService scheduledExecutorService;
public static ScheduledExecutorService getScheduled() {
if (ThreadPoolManager.scheduledExecutorService == null) {
synchronized (ThreadPoolManager.class) {
if (ThreadPoolManager.scheduledExecutorService == null) {
instant();
}
}
}
return ThreadPoolManager.scheduledExecutorService;
}
public static void instant() {
ThreadPoolManager.scheduledExecutorService = new ScheduledThreadPoolExecutor(CORE_POOL_SIZE_MIN,
new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
log.error("线程池异常",t);
}
};
}
}
SSEmitterManager管理类,用来管理和存储SSE对象
@Service
public class SSEmitterManager {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final ConcurrentHashMap<String, SSEmitterWrapper> emitters = new ConcurrentHashMap<>();
public SSEmitterManager() {
}
public SseEmitter createSseEmitter(String userId) {
if(StringUtils.isEmpty(userId)){
throw new RuntimeException("userId is null");
}
SSEmitterWrapper wrapper= emitters.computeIfAbsent(userId,
(k) -> {
SSEmitterWrapper emitter = new SSEmitterWrapper(loginUser, () -> emitters.remove(k));
emitter.start();
return emitter;
});
return wrapper.getEmitter();
}
/**
* 群发消息
*/
public void SendMessage(List<String> usrId, SSEMessage sseMessage) {
for (String id : usrId) {
SSEmitterWrapper wrapper = emitters.get(id);
if (wrapper != null && wrapper.isActive()) {
wrapper.sendMessage(sseMessage);
} else {
logger.warn("用户 {} 的SSE连接不存在或已断开", usrId);
}
}
}
/**
* 单发消息
*/
public void SendMessage(String usrId, SSEMessage sseMessage) {
SSEmitterWrapper wrapper = emitters.get(usrId);
if (wrapper != null && wrapper.isActive()) {
wrapper.sendMessage(sseMessage);
} else {
logger.warn("用户 {} 的SSE连接不存在或已断开", usrId);
}
}
/**
* 定时去轮询重发消息
*/
public void reSendMessagesTask() {
if (emitters.isEmpty()) return;
ThreadPoolManager.getScheduled().schedule( () ->
emitters.values().forEach(SSEmitterWrapper::retryFailedMessages),
5,TimeUnit.SECONDS
);
}
/**
* 定时检查SSE链接是否活跃
*/
public void checkActive() {
if (emitters.isEmpty()) return;
ThreadPoolManager.getScheduled().schedule(() -> {
List<String> inactiveKeys = new ArrayList<>();
emitters.forEach((key, emitter) -> {
if (!emitter.isActive()) {
inactiveKeys.add(key);
}
});
inactiveKeys.forEach(emitters::remove); // 批量删除
}, 1, TimeUnit.HOURS);
}
/**
* 判断这些用户是否处在链接状态
*/
public List<OnlineVo> getOnlineUser(List<String> usrId) {
if (usrId.isEmpty()) return new ArrayList<>();
List<OnlineVo> onlineUsers = new ArrayList<>();
for (String usr : usrId) {
if (usr == null || Objects.equals("null", usr)) continue;
OnlineVo onlineVo = new OnlineVo();
onlineVo.setId(Long.parseLong(usr));
if (emitters.containsKey(usr)) {
onlineVo.setOnline(1);
onlineUsers.add(onlineVo);
continue;
}
onlineVo.setOnline(0);
onlineUsers.add(onlineVo);
}
return onlineUsers;
}
}
SSEmitterWrapper消息包装类
public class SSEmitterWrapper {
private static final Logger log = LoggerFactory.getLogger(SSEmitterWrapper.class);
private static final long TIMEOUT = 21600000L;
private static final long HEARTBEAT_INTERVAL = 5L;
private static final int MAX_RETRY = 2;
private final LoginUser user;
private final Runnable closeCallback;
private final AtomicBoolean active = new AtomicBoolean(false);
private final AtomicBoolean closed = new AtomicBoolean(false);
@Getter
private SseEmitter emitter;
private ScheduledFuture<?> heartbeatTask;
/**
* 失败消息重试队列
*/
private final BlockingQueue<RetryMessage> retryQueue = new LinkedBlockingQueue<>(10);
public SSEmitterWrapper(LoginUser user, Runnable closeCallback) {
this.user = user;
this.closeCallback = closeCallback;
}
public SseEmitter start() {
this.emitter = new SseEmitter(TIMEOUT);
this.active.set(true);
registerListener();
startHeartbeat();
return emitter;
}
private void registerListener() {
emitter.onCompletion(this::close);
emitter.onTimeout(this::close);
emitter.onError(e -> {
log.warn("SSE error userId={}, msg={}", user.getUsrId(), e.getMessage());
close();
});
}
private void startHeartbeat() {
heartbeatTask = ThreadPoolManager.getScheduled().scheduleAtFixedRate(() -> {
try {
sendInternal(SseEmitter.event()
.name(SseEnum.HEARTBEAT.value())
.data("heart"));
} catch (Exception e) {
close();
log.warn("SSE thread error userId={}, msg={}", user.getUsrId(), e.getMessage());
}
}, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);
}
public boolean isActive() {
return active.get();
}
public void sendMessage(SSEMessage message) {
if (!isValid(message) || closed.get()) return;
if (!isActive() || closed.get()) {
log.info("SSE connection closed, skipping message send userId={}", user.getUsrId());
return;
}
try {
sendInternal(SseEmitter.event()
.id(message.getId())
.name(message.getEvent().value())
.data(message.getData()));
} catch (IOException e) {
enqueueRetry(message);
}
}
private void sendInternal(SseEmitter.SseEventBuilder event) throws IOException {
emitter.send(event);
}
private void enqueueRetry(SSEMessage message) {
retryQueue.offer(new RetryMessage(message));
}
/**
* 定时由 Manager 调用
*/
public void retryFailedMessages() {
int size = retryQueue.size();
for (int i = 0; i < size; i++) {
RetryMessage retry = retryQueue.poll();
if (retry == null) continue;
if (retry.retryCount >= MAX_RETRY) {
log.warn("SSE message dropped userId={}, msgId={}",
user.getUsrId(), retry.message.getId());
//todo可以放到日志里面去
continue;
}
try {
sendInternal(SseEmitter.event()
.id(retry.message.getId())
.name(retry.message.getEvent().value())
.data(retry.message.getData()));
} catch (IOException e) {
retry.retryCount++;
retryQueue.offer(retry);
}
}
}
private boolean isValid(SSEMessage message) {
return message != null
&& Objects.nonNull(message.getId())
&& !StringUtils.isEmpty(message.getData());
}
public void close() {
if (!closed.compareAndSet(false, true)) return;
active.set(false);
if (heartbeatTask != null) {
heartbeatTask.cancel(true);
heartbeatTask = null;
}
;
try {
emitter.complete();
} catch (Exception ignored) {
}
closeCallback.run();
log.info("SSE closed userId={}", user.getUsrId());
}
private static class RetryMessage {
private final SSEMessage message;
private int retryCount = 0;
RetryMessage(SSEMessage message) {
this.message = message;
}
}
}
至此后端封装完毕了只需要调用
SSEmitterManager的createSseEmitter方法就可以创建一个SSE对象
二、前端部分
这里是使用的Vite+Vue3的项目
SSEServer.ts
import { getToken } from "@/utils/auth.js";
import useUserStore from "@/store/modules/user";
/**
* SSE 服务器连接管理类
* 支持消息接收处理、事件回调、自动重连和心跳机制
*/
// 消息类型枚举
export enum SSEMessageType {
HEARTBEAT = 'heartbeat',
NOTIFICATION = 'notification',
DATA = 'data',
ERROR = 'error',
SYSTEM = 'system',
CUSTOM = 'custom',
CHAT="chat"
}
// SSE 消息接口
export interface SSEMessage {
type: SSEMessageType | string;
data: any;
id?: string;
timestamp?: number;
}
// 事件回调类型
export type SSECallback = (message: SSEMessage) => void;
// 连接状态枚举
export enum SSEConnectionStatus {
DISCONNECTED = 'disconnected',
CONNECTING = 'connecting',
CONNECTED = 'connected',
RECONNECTING = 'reconnecting',
ERROR = 'error'
}
// 重连配置接口
export interface ReconnectConfig {
enabled: boolean;
maxRetries: number;
retryInterval: number; // 毫秒
backoffMultiplier: number; // 退避倍数
}
// 心跳配置接口
export interface HeartbeatConfig {
enabled: boolean;
interval: number; // 心跳间隔(毫秒)
timeout: number; // 心跳超时(毫秒)
}
// SSE 服务器配置接口
export interface SSEServerConfig {
url: string;
reconnect?: Partial<ReconnectConfig>;
heartbeat?: Partial<HeartbeatConfig>;
headers?: Record<string, string>;
withCredentials?: boolean;
}
class SSEServer {
private eventSource: EventSource | null = null;
private status: SSEConnectionStatus = SSEConnectionStatus.DISCONNECTED;
// private config: Required<SSEServerConfig>;
private reconnectConfig: ReconnectConfig;
private heartbeatConfig: HeartbeatConfig;
// 事件回调映射
private callbacks: Map<string, Set<SSECallback>> = new Map();
// 通用回调(所有消息都会触发)
private generalCallbacks: Set<SSECallback> = new Set();
// 重连相关
private reconnectAttempts: number = 0;
private reconnectTimer: NodeJS.Timeout | null = null;
// 心跳相关
private heartbeatTimer: NodeJS.Timeout | null = null;
private lastHeartbeatTime: number = 0;
private heartbeatTimeoutTimer: NodeJS.Timeout | null = null;
// 消息队列(连接断开时暂存消息)
private messageQueue: SSEMessage[] = [];
private maxQueueSize: number = 100;
//当前链接的URL
private currentUrl: string | null = null;
constructor() {
// 默认配置
const defaultReconnectConfig: ReconnectConfig = {
enabled: true,
maxRetries: 10,
retryInterval: 3000,
backoffMultiplier: 1.5
};
const defaultHeartbeatConfig: HeartbeatConfig = {
enabled: true,
interval: 30000, // 30秒
timeout: 60000 // 60秒超时
};
this.reconnectConfig = defaultReconnectConfig;
this.heartbeatConfig = defaultHeartbeatConfig;
}
/**
* 连接到 SSE 服务器
*/
public connect(): void {
if (this.status === SSEConnectionStatus.CONNECTED ||
this.status === SSEConnectionStatus.CONNECTING) {
console.warn('SSE 连接已存在或正在连接中');
return;
}
this.status = SSEConnectionStatus.CONNECTING;
this.emitStatusChange();
const url: string = this.assembleUrl();
try {
// 创建 EventSource 连接
this.eventSource = new EventSource(url, {
withCredentials: true,
});
// 监听连接打开
this.eventSource.onopen = (event) => {
this.status = SSEConnectionStatus.CONNECTED;
this.reconnectAttempts = 0;
this.emitStatusChange();
this.startHeartbeat();
this.processMessageQueue();
};
for (const message of Object.values(SSEMessageType)) {
this.eventSource.addEventListener(message, (event: any) => {
this.handleMessage(message, event);
})
}
// 监听错误
this.eventSource.onerror = (error) => {
console.error('SSE 连接错误', error);
this.handleError(error);
};
} catch (error) {
console.error('创建 SSE 连接失败', error);
this.status = SSEConnectionStatus.ERROR;
this.emitStatusChange();
this.attemptReconnect();
}
}
/**
* 处理接收到的消息
*/
private handleMessage(type: string, event: MessageEvent): void {
try {
const message: SSEMessage = {
type: type as string,
data: event.data,
id: event.lastEventId,
timestamp: Date.now()
};
this.processMessage(message);
} catch (error) {
console.error('处理消息失败', error);
}
}
/**
* 处理消息
*/
private processMessage(message: SSEMessage): void {
// 更新心跳时间
if (message.type === SSEMessageType.HEARTBEAT) {
this.lastHeartbeatTime = Date.now();
return;
}
// 如果连接断开,将消息加入队列
if (this.status !== SSEConnectionStatus.CONNECTED) {
this.addToQueue(message);
return;
}
// 触发对应类型的回调
const typeCallbacks = this.callbacks.get(message.type);
if (typeCallbacks) {
typeCallbacks.forEach(callback => {
try {
callback(message);
} catch (error) {
console.error(`执行回调失败 [${message.type}]`, error);
}
});
}
// 触发通用回调
if (this.generalCallbacks.size > 0) {
this.generalCallbacks.forEach(callback => {
try {
callback(message);
} catch (error) {
console.error('执行通用回调失败', error);
}
});
}
}
/**
* 处理错误
*/
private handleError(error: Event): void {
this.status = SSEConnectionStatus.ERROR;
this.emitStatusChange();
// 发送错误消息
const errorMessage: SSEMessage = {
type: SSEMessageType.ERROR,
data: { error: '连接错误', originalEvent: error },
timestamp: Date.now()
};
this.processMessage(errorMessage);
// 如果连接关闭,尝试重连
if (this.eventSource?.readyState === EventSource.CLOSED) {
this.stopHeartbeat();
this.attemptReconnect();
}
}
/**
* 尝试重连
*/
private attemptReconnect(): void {
const userStore = useUserStore()
if (!userStore.checkIsLogin()) {
console.log("用户未登录")
return
}
if (!this.reconnectConfig.enabled) {
console.log('重连机制已禁用');
return;
}
if (this.reconnectAttempts >= this.reconnectConfig.maxRetries) {
console.error('达到最大重连次数,停止重连');
this.status = SSEConnectionStatus.ERROR;
this.emitStatusChange();
return;
}
this.status = SSEConnectionStatus.RECONNECTING;
this.emitStatusChange();
// 计算重连延迟(指数退避)
const delay = this.reconnectConfig.retryInterval *
Math.pow(this.reconnectConfig.backoffMultiplier, this.reconnectAttempts);
console.log(`将在 ${delay}ms 后尝试第 ${this.reconnectAttempts + 1} 次重连`);
this.reconnectTimer = setTimeout(() => {
this.reconnectAttempts++;
this.disconnect();
this.connect();
}, delay);
}
/**
* 启动心跳检测
*/
private startHeartbeat(): void {
if (!this.heartbeatConfig.enabled) return;
this.lastHeartbeatTime = Date.now();
// 定期检查心跳
this.heartbeatTimer = setInterval(() => {
const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeatTime;
if (timeSinceLastHeartbeat > this.heartbeatConfig.timeout) {
console.warn('心跳超时,连接可能已断开');
this.handleHeartbeatTimeout();
}
}, this.heartbeatConfig.interval);
}
/**
* 处理心跳超时
*/
private handleHeartbeatTimeout(): void {
this.stopHeartbeat();
this.disconnect();
this.attemptReconnect();
}
/**
* 停止心跳检测
*/
private stopHeartbeat(): void {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
if (this.heartbeatTimeoutTimer) {
clearTimeout(this.heartbeatTimeoutTimer);
this.heartbeatTimeoutTimer = null;
}
}
/**
* 断开连接
*/
public disconnect(): void {
this.stopHeartbeat();
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
// this.callbacks.clear()
// this.generalCallbacks.clear()
this.messageQueue = []
this.currentUrl = null
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
this.status = SSEConnectionStatus.DISCONNECTED;
this.emitStatusChange();
}
/**
* 注册消息类型回调
* @param type 消息类型
* @param callback 回调函数
*/
public on(type: string | SSEMessageType, callback: SSECallback): void {
if (!this.callbacks.has(type)) {
this.callbacks.set(type, new Set());
}
this.callbacks.get(type)!.add(callback);
}
/**
* 移除消息类型回调
*/
public off(type: string | SSEMessageType, callback?: SSECallback): void {
const callbacks = this.callbacks.get(type);
if (!callbacks) return;
if (callback) {
callbacks.delete(callback);
if (callbacks.size === 0) {
this.callbacks.delete(type);
}
} else {
this.callbacks.delete(type);
}
}
/**
* 注册通用回调(所有消息都会触发)
*/
public onMessage(callback: SSECallback): void {
this.generalCallbacks.add(callback);
}
/**
* 移除通用回调
*/
public offMessage(callback: SSECallback): void {
this.generalCallbacks.delete(callback);
}
/**
* 触发状态变化事件
*/
private emitStatusChange(): void {
const statusMessage: SSEMessage = {
type: SSEMessageType.SYSTEM,
data: {
event: 'statusChange',
status: this.status,
reconnectAttempts: this.reconnectAttempts
},
timestamp: Date.now()
};
this.processMessage(statusMessage);
}
/**
* 将消息添加到队列
*/
private addToQueue(message: SSEMessage): void {
this.messageQueue.push(message);
if (this.messageQueue.length > this.maxQueueSize) {
this.messageQueue.shift(); // 移除最旧的消息
}
}
/**
* 处理消息队列
*/
private processMessageQueue(): void {
while (this.messageQueue.length > 0) {
const message = this.messageQueue.shift();
if (message) {
this.processMessage(message);
}
}
}
/**
* 获取当前连接状态
*/
public getStatus(): SSEConnectionStatus {
return this.status;
}
/**
* 检查是否已连接
*/
public isConnected(): boolean {
return this.status === SSEConnectionStatus.CONNECTED;
}
/**
* 获取重连次数
*/
public getReconnectAttempts(): number {
return this.reconnectAttempts;
}
/**
* 清空消息队列
*/
public clearQueue(): void {
this.messageQueue = [];
}
private static SSEServer: SSEServer | null = null;
static getInstance(): SSEServer {
if (!this.SSEServer) {
this.SSEServer = new SSEServer();
}
return this.SSEServer;
}
private assembleUrl() {
return `${import.meta.env.VITE_APP_BASE_API}/sse?token=${getToken()}`;
}
}
export default SSEServer;