VUE+Spring Flux实现SSE长连接

VUE代码

        // 初始化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 监听接口

 @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 提交数据服务

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

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现公告发布的基本思路是,前端页面提供一个表单,用户输入公告内容,点击发布按钮,然后将公告信息通过Ajax请求发送到后端,后端接收到请求后将公告信息保存到数据库中,再返回一个响应告诉前端保存成功。 下面是一个简单的实现过程: 1. 创建公告表 首先,在数据库中创建一个公告表,包含id、标题、内容、发布时间等字段。 2. 创建后端接口 使用Spring Boot创建一个后端接口,接收前端发送的公告信息,将公告信息保存到数据库中。可以使用Spring Data JPA来实现对数据库的操作。 例如,创建一个名为AnnouncementController的控制器类,包含一个POST方法用于接收并保存公告信息: ```java @RestController @RequestMapping("/announcement") public class AnnouncementController { @Autowired private AnnouncementRepository announcementRepository; @PostMapping("/add") public String addAnnouncement(@RequestBody Announcement announcement) { announcementRepository.save(announcement); return "success"; } } ``` 3. 创建前端页面 使用Vue.js创建一个前端页面,包含一个表单,用户输入公告信息并点击发布按钮后将公告信息发送到后端接口。 例如,创建一个名为AnnouncementForm的Vue组件,包含一个表单和一个发布按钮: ```html <template> <form> <div> <label for="title">标题:</label> <input type="text" id="title" v-model="title" /> </div> <div> <label for="content">内容:</label> <textarea id="content" v-model="content"></textarea> </div> <button type="button" @click="addAnnouncement">发布</button> </form> </template> ``` 在组件中定义一个addAnnouncement方法,用于将公告信息发送到后端接口: ```javascript <script> import axios from 'axios'; export default { data() { return { title: '', content: '' }; }, methods: { addAnnouncement() { const announcement = { title: this.title, content: this.content }; axios.post('/announcement/add', announcement) .then(response => { console.log(response.data); }) .catch(error => { console.log(error); }); } } }; </script> ``` 4. 集成前后端 将上面的前后端代码集成到一起,启动Spring Boot应用和Vue.js应用,访问Vue.js应用中的公告发布页面,输入公告信息并点击发布按钮,就可以将公告信息保存到数据库中了。 以上就是一个简单的Vue.jsSpring Boot实现公告发布的示例。当然,实际开发中还需要考虑一些安全性和可用性方面的问题,例如防止恶意攻击、数据校验、分页查询等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值