springboot+js实现SSE消息推送

news/2024/8/26 14:16:35 标签: spring boot, javascript, 后端

一、后端
1、新建工具类SseServiceTool

package com.example.system_manage.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * SSE消息推送工具
 */
@Slf4j
@Component
public class SseServiceTool {
    private final static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();

    //创建一个SSE对象
    public SseEmitter createSse(String messageId) {
        closeSseEmitterById(messageId);
        log.info("创建对象");
        log.info(sseEmitterMap.size()+"");
        SseEmitter sseEmitter = new SseEmitter(5_60_000L);
        // 设置前端的重试时间为1s
        sseEmitter.onCompletion(()->{
            log.info("完成传输");
        });
        sseEmitter.onError((throwable)->{
            log.info("出现错误");
        });
        sseEmitterMap.put(messageId, sseEmitter);
        return sseEmitter;
    }

    public SseEmitter getSseEmitterById(String id){
        if(!sseEmitterMap.containsKey(id)){
            throw new BusinessException("当前无连接对象");
        }
        return sseEmitterMap.get(id);
    }

    //根据id关闭连接
    public void closeSseEmitterById(String messageId){
        if(sseEmitterMap.containsKey(messageId)){
            sseEmitterMap.get(messageId).complete();
            sseEmitterMap.remove(messageId);
        }
    }
}

2、新建service接口

package com.example.system_manage.service;

import java.util.Map;

public interface ISseMessageService {
    //发送消息
    String pushMessage(Map<String,String> map);
    //关闭SSE服务
    String closeSseService(String messageId);
}

3、新建service实现类

package com.example.system_manage.service.impl;

import com.example.system_manage.service.ISseMessageService;
import com.example.system_manage.utils.SseServiceTool;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;

@Service
@Slf4j
public class SseMessageServiceImpl implements ISseMessageService {

    @Resource
    private SseServiceTool sseServiceTool;

    @Override
    public String pushMessage(Map<String, String> map) {

        try {
            SseEmitter sseEmitter = sseServiceTool.getSseEmitterById(map.get("id"));
            Map<String,String> sendMessage=Map.of("title",map.get("title"),"content",map.get("content"));
            SseEmitter.SseEventBuilder data = SseEmitter.event().name(map.get("eventName")).id(map.get("id")).data(sendMessage);
            sseEmitter.send(data);
        }catch (Exception ex){
            //移除连接
            sseServiceTool.closeSseEmitterById(map.get("id"));
            log.error(ex.getMessage());
            return "noSSE";
        }
        return "OK";
    }

    @Override
    public String closeSseService(String messageId) {
        sseServiceTool.closeSseEmitterById(messageId);
        return "OK";
    }
}

4、新建控制器

package com.example.system_manage.controller;

import com.example.system_manage.service.ISseMessageService;
import com.example.system_manage.utils.ResultMap;
import com.example.system_manage.utils.SseServiceTool;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping(path = "/sseMessage")
@Slf4j
public class SseMessageController {

    @Resource
    private ISseMessageService sseMessageService;

    @Resource
    private SseServiceTool sseServiceTool;

    //创建SSE连接对象
    @GetMapping(path = "/createSse", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter createSse(@RequestParam("messageId") String messageId) {
        return sseServiceTool.createSse(messageId);
    }
    //推送消息
    @PostMapping(path = "/pushMessage")
    public ResultMap pushMessage(@RequestBody Map<String,String> map) {
        return ResultMap.SUCCESS.setNewData(sseMessageService.pushMessage(map));
    }
    //关闭SSE连接
    @GetMapping("/closeSseService")
    public ResultMap closeSseService(@RequestParam("messageId") String messageId){
        return ResultMap.SUCCESS.setNewData(sseMessageService.closeSseService(messageId));
    }

}

二、前端部分
1、安装依赖

 npm install event-source-polyfill  -S

2、封装requestEventSource.js用于创建SSE对象

import { EventSourcePolyfill } from 'event-source-polyfill';
import cookie from 'js-cookie';


/**
 * 创建一个sse对象
 * @param {*} data
 * @returns
 */
export const getEventSource = (data) => {
  return new EventSourcePolyfill(`${data.httpRequest}${data.url}`, {
    headers: {
      'xxx': cookie.get("xxxxx"),
    },
  });
};

用于获取请求SSE对象

export function getSseMessageObj(data) {
  return getEventSource({
    httpRequest: baseUrl,
    url: `/sseMessage/createSse?messageId=${data}`,
  });
}

3、页面组件

<!--
 * @Author: zhangming zhangming@sinoma-tianjin.cn
 * @Date: 2024-07-15 15:48:08
 * @LastEditors: zhangming zhangming@sinoma-tianjin.cn
 * @LastEditTime: 2024-07-16 15:35:23
 * @FilePath: \zjcgg_system_manage\src\views\demos\OrganizationSelectTest.vue
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
  SSE测试
  <el-button type="primary" @click="pushSseMessageTopLeft">左上角推送消息</el-button>
  <el-button type="primary" @click="pushSseMessageTopRight">右上角推送消息</el-button>
</template>

<script setup>
import { ref, onMounted, onBeforeMount, onBeforeUnmount, nextTick, reactive, watch } from 'vue';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { ElMessage, ElNotification } from 'element-plus';
import { generateUUID } from '@/utils/index';
import { closeSseService, pushSseMessage, getSseMessageObj } from '@/api/sseClient';

const source = ref(null); //sse连接对象
const messageKey = ref('');

window.addEventListener('beforeunload', async (event) => {
  //浏览器窗口事件
  // 设置returnValue属性可以显示一个提示信息,询问用户是否真的要离开页面
  await closeSsse();
});

window.addEventListener('unload', async (event) => {
  //浏览器窗口事件
  // 设置returnValue属性可以显示一个提示信息,询问用户是否真的要离开页面
  await closeSsse();
});

onBeforeMount(async () => {
  //初始加载
  messageKey.value = generateUUID();
  source.value = getSseMessageObj(messageKey.value);
  source.value.addEventListener(
    'message-top-left',
    (event) => {
      let response = JSON.parse(event.data);
      ElNotification({
        title: response.title,
        message: response.content,
        position: 'top-left',
      });
    },
    false
  );
  source.value.addEventListener(
    'message-top-right',
    (event) => {
      let response = JSON.parse(event.data);
      ElNotification({
        title: response.title,
        message: response.content,
        position: 'top-right',
      });
    },
    false
  );
});

onBeforeUnmount(async () => {
  await closeSsse();
});

const closeSsse = async () => {
  //获取到选择的人
  source.value.close();
  await closeSseService(messageKey.value);
};

const pushSseMessageTopRight = async () => {
  let postData = {
    id: messageKey.value,
    content: '报警通知',
    title: '消息主题',
    eventName: 'message-top-right',
  };
  await pushSseMessage(postData);
};

const pushSseMessageTopLeft = async () => {
  let postData = {
    id: messageKey.value,
    content: '报警通知',
    title: '消息主题',
    eventName: 'message-top-left',
  };
  await pushSseMessage(postData);
};
</script>

<style lang="scss" scoped></style>

注:里面有部分接口是请求后端断开连接用的


http://www.niftyadmin.cn/n/5558910.html

相关文章

GIS前端react组件之GeoStyler(一)初识

简介及使用记录 0、背景1、初识2、使用记录2.1、sld线样式2.1.1、不兼容的标签1、PerpendicularOffset2、VendorOption 2.1.2、意外收获 0、背景 公司想调用GeoServer接口&#xff0c;重写一套前端的东西。其中在线样式编辑功能使用了GeoStyler这个组件 1、初识 项目比较正规…

域名解析记录与服务器源IP的探索

在互联网中&#xff0c;域名和IP地址是进行网络通信的基础。用户通常通过域名来访问网站或服务&#xff0c;而实际的数据交换则发生在IP地址之间。域名解析&#xff08;DNS解析&#xff09;是将易于记忆的域名转换为计算机可识别的IP地址的过程。本文将探讨如何通过域名解析记录…

示例:在WPF ListBox中,ScrollViewer.CanContentScroll=“False“破坏虚拟化如何解决

一、目的&#xff1a;分享一个解决ListBox设置了ScrollViewer.CanContentScroll"False"破坏虚拟坏的解决方法 ScrollViewer.CanContentScroll 是 WPF 中 ScrollViewer 控件的一个属性&#xff0c;它决定了滚动内容时是按逻辑单位&#xff08;如项&#xff09;还是按物…

Matlab|基于蒙特卡洛法的电动汽车充电负荷计算

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序方法复现《V2G 模式下含分布式能源的配电网优化运行研究》第二章电动汽车无序充电模型&#xff0c;按照文章《V2G 模式下基于复杂网络的电动汽车有序充电策略》分析思路研究了不同数量电动汽车接入情况…

license系统模型设计使用django models

User (用户)License (许可证)Product (产品)LicenseAssignment (许可证分配) 简单的模型定义&#xff1a; from django.db import models from django.contrib.auth.models import Userclass Product(models.Model):name models.CharField(max_length255)description model…

Linux文件编程(标准IO与文件IO)学习日记1

文件类型 b 块设备文件 硬盘 c 字符设备 鼠标、键盘 d 目录文件 文件夹 - 常规文件 l 软连接文件 类似windows的快捷方式 s socket 套接字…

prompt第五讲-fewshot-selector

文章目录 前提回顾few-shot selectorsSemanticSimilarityExampleSelector 前提回顾 前面以一种优雅的形式实现了一个翻译助手。但是有一个非常大的问题&#xff0c;里面的案例都是中文到英文的案例&#xff0c;如果用户定义的翻译助手是日文到英文&#xff0c;那这个案例就不起…

鹈鹕优化算法(POA)及其Python和MATLAB实现

鹈鹕优化算法&#xff08;Pelican Optimization Algorithm&#xff0c;简称POA&#xff09;是一种基于仿生学原理的优化算法&#xff0c;灵感来源于大自然中鹈鹕的觅食行为。POA被设计用于解决优化问题&#xff0c;尤其在连续型和离散型的优化问题中展现出了较好的性能。 ### …