VUE+Spring Flux实现SSE长连接

VUE代码

javascript 复制代码
        // 初始化EventSource
        initEventSource(url) {
          const token = getAccessToken();
          const eventSource = new EventSourcePolyfill(url, {
            headers: {
              'Authorization': `Bearer ${token}`,
              'tenant-id': getTenantId(),
            }
          });

          eventSource.onerror = (e) => {
            console.log("SSE连接错误", e);
            if (e.readyState === EventSource.CLOSED || eventSource.readyState === EventSource.CONNECTING) {
              console.log("SSE连接已关闭或正在重连");
            } else {
              // 当发生错误时,尝试重新连接
              if (reconnectAttempts < maxReconnectAttempts) {
                console.log(`尝试第${reconnectAttempts + 1}次重连`);
                reconnectAttempts++;
                setTimeout(() => {
                  eventSource.close(); // 关闭当前连接
                  this.initEventSource(url); // 重新初始化EventSource
                }, reconnectDelay * reconnectAttempts);
              } else {
                console.error("达到最大重连次数,不再尝试重连");
              }
            }
          };

          eventSource.addEventListener("message", res => {
            const data = JSON.parse(res.data)
            if (data.type == 2) {
              this.unreadCount = data.content;
            }
            if (data.type == 1) {
              this.createNotify(data)
            }
          })
        },

后端采用redis做管道,能够兼容分布式服务

JAVA 监听接口

java 复制代码
 @GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamEvents() {
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        USER_IDS.add(loginUserId);
        SseMessageVO heartbeat = new SseMessageVO()
                .setType(SseNotifyTypeEnum.HEARTBEAT.getType())
                .setUserId(loginUserId)
                .setContent("Heartbeat");
        return  reactiveRedisOperations.listenToChannel(SseService.getDestination(loginUserId))
                .map(data -> sendMsg(loginUserId,data.getMessage(),heartbeat))
                .publishOn(Schedulers.boundedElastic())
                .doOnSubscribe(subscription -> {
                    // 订阅时发送一次心跳,确认连接
                    heartbeat(heartbeat);
                });
    }

    private String sendMsg(Long loginUserId,String sseMessage,SseMessageVO heartbeat){
        SseMessageVO sseMessageVO = JSONUtil.toBean(sseMessage, SseMessageVO.class);
        if (null != sseMessageVO && Objects.equals(sseMessageVO.getUserId(), loginUserId)){
            return JSONUtil.toJsonStr(sseMessageVO);
        }
        return JSONUtil.toJsonStr(heartbeat);
    }

    /**
     * 登录时心跳
     */
    private void heartbeat(SseMessageVO heartbeat) {
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.schedule(() -> {
            sseService.publishEventToChannel(heartbeat).subscribe();
        }, 1, TimeUnit.SECONDS);
        executorService.shutdown();
    }

    /**
     * 保活
     */
    @PostConstruct
    public void heartbeatTimer() {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.scheduleAtFixedRate(() -> {
            if (CollectionUtil.isNotEmpty(USER_IDS)){
                for (Long userId : USER_IDS) {
                    String message = "Heartbeat at " + LocalDateTime.now();
                    SseMessageVO heartbeat = new SseMessageVO()
                            .setType(SseNotifyTypeEnum.HEARTBEAT.getType())
                            .setUserId(userId)
                            .setContent(message);
                    sseService.publishEventToChannel(heartbeat).subscribe();
                }
            }
        }, 0, 10, TimeUnit.SECONDS);
    }

JAVA 提交数据服务

java 复制代码
@Component
public class SseService {

    @Resource
    private ReactiveRedisOperations<String, String> reactiveRedisOperations;

    private static final String DESTINATION = "event-channel-user:";

    /**
     * 获取指定通道
     * @param userId
     * @return
     */
    public static String getDestination(Long userId){
        return DESTINATION+userId;
    }

    /**
     * 推送事件到通道
     * @param sseMessageVO
     * @return
     */
    public Mono<Long> publishEventToChannel(SseMessageVO sseMessageVO) {
        return reactiveRedisOperations.convertAndSend(getDestination(sseMessageVO.getUserId()), JSONUtil.toJsonStr(sseMessageVO));
    }

}
相关推荐
小钻风33664 分钟前
Spring Boot WebSocket 两种集成方式深度解析
spring boot·后端·websocket
海兰8 分钟前
【实用程序】图片处理服务,前端应用 (Vue 3 + Pinia + Vite)后端服务 (Node.js + Express + Sharp)
前端·javascript·vue.js·node.js·pinia
SuniaWang21 分钟前
AgentX 专栏-00前言:一个Java开发者的Agent实践之路
java·人工智能·spring boot·langchain·系统架构
RPGMZ27 分钟前
RPGMZ游戏引擎事件技巧大全
javascript·游戏引擎·事件·rpgmz·rpgmakermz
贵州晓智信息科技32 分钟前
曼德勃罗集的 Three.js 实现
开发语言·javascript·ecmascript
逍遥德1 小时前
Java编程高频的“踩坑点”-01:fastjson.JSON 转换时泛型擦除问题
java·spring boot·spring·系统架构·json
m0_502724951 小时前
vue3生成pdf
前端·javascript·vue.js·pdf
Reload.1 小时前
CZ航司,shopping JS逆向 acw_sc__v2
开发语言·javascript·python·网络爬虫·ecmascript
闪电悠米1 小时前
黑马点评短信登录01_session_sms_login
java·spring boot·redis·git·spring·面试