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));
    }

}
相关推荐
东东51627 分钟前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino31 分钟前
图片、文件的预览
前端·javascript
2501_920931702 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
AI老李3 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
方也_arkling3 小时前
Element Plus主题色定制
javascript·sass
晓晓莺歌3 小时前
vue3某一个路由切换,导致所有路由页面均变成空白页
前端·vue.js
一点程序3 小时前
基于SpringBoot的选课调查系统
java·spring boot·后端·选课调查系统
2601_949809593 小时前
flutter_for_openharmony家庭相册app实战+我的Tab实现
java·javascript·flutter
Up九五小庞3 小时前
开源埋点分析平台 ClkLog 本地部署 + Web JS 埋点测试实战--九五小庞
前端·javascript·开源
摘星编程4 小时前
React Native + OpenHarmony:UniversalLink通用链接
javascript·react native·react.js