原创

使用SSE从后端往前端推送数据

温馨提示:
本文最后更新于 2023年03月08日,已超过 415 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

最近在做项目的时候,有个需求是要实时验证设备的存在性,http不行啊,因为和设备之前通讯使用的是mqtt,只有收到订阅才你能返回给前端,如果说在springboot后台中进行睡眠等待或者线程池等待的话感觉也不现实,毕竟访问的人少可以,人一多就全部阻塞了,所以就想到21年用过的一个技术,SSE,这个东西呢,相比较websocket来说比较轻,怎么说呢,websocket是双向通讯,而这个是单向的,而且一般浏览器都支持,使用起来也比较简单,而且,springboot web已经集成了这个技术,我们不需要添加任何其他的依赖

1.后端代码编写

在后端这块呢,首先保证你是springboot项目,那项目启动肯定少不了web,那就加入web依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后就是编写你的controller
首先,这个controller和我们之前写的controller是一样的,之前怎么写,这个就怎么写,因为我写在demo里边了,所以就直接写在了启动类中。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * controller
 *
 * @author 王祁
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@RestController
@RequestMapping("/sse")
@CrossOrigin
public class App {

    private Map<String, SseEmitter> map = new ConcurrentHashMap<>();

    public static void main (String[] args) {
        SpringApplication.run(App.class, args);
    }

    @GetMapping(value = "check/{id}")
    public SseEmitter sub (@PathVariable String id) throws IOException {
        if (null != map.get(id)) {
            SseEmitter sseEmitter = new SseEmitter();
            sseEmitter.send(SseEmitter.event().name("disconnected").data("断开连接"));
            sseEmitter.complete();
            map.remove(id);
            return sseEmitter;
        }
        SseEmitter sseEmitter = new SseEmitter();
        sseEmitter.send(SseEmitter.event().name("info").data("连接成功"));
        map.put(id, sseEmitter);
        sseEmitter.onError((e) -> {
            System.out.println("错误" + e);
            map.remove(id);
        });
        sseEmitter.onTimeout(() -> {

        });
        sseEmitter.onCompletion(() -> {
            System.out.println("完成");
        });
        return sseEmitter;
    }

    @GetMapping("/push/{id}/{content}")
    public void push (@PathVariable String id, @PathVariable String content) {
        Map<String, Object> obj = new HashMap<>();
        obj.put("code", 200);
        obj.put("data", content);
        obj.put("msg", "成功");
        SseEmitter sseEmitter = map.get(id);
        if (null != sseEmitter) {
            try {
                sseEmitter.send(SseEmitter.event().name("message").data(obj, MediaType.APPLICATION_JSON));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

就这么简单,后端已经完成了,我给你们分析分析哈
这个呢,是存储已经创建了的sse实例,方便我们调用接口的时候拿出来发送
private Map<String, SseEmitter> map = new ConcurrentHashMap<>();
这个是创建一个实例,它有构造方法
SseEmitter sseEmitter = new SseEmitter();
这个构造方法可以设置过期时间,默认的过期时间是你web容器的过期时间,一般是30秒(tomcat),具体设置容器的过期时间,请自行百度,这里设置这个过期时间是Long类型,单位是秒,如果是10秒我可以输入 10L
file
这个是发送消息,为了让前端区分连接成功,断开以及正常消息,我们可以设置发送消息的eventName,就是 SseEmitter.event().name("info") 这个name,我这块的话info是连接成功,disconnected是连接失败,message是正常消息。发送消息呢,消息体在data里边,可以设置消息体的类型,可以是json格式的,就使用MediaType就行了
sseEmitter.send(SseEmitter.event().name("info").data("连接成功"));
这块是设置当执行完成也就是调用了completion、出现错误、连接超时的时候的处理方法

sseEmitter.onError((e) -> {
    System.out.println("错误" + e);
    map.remove(id);
});
sseEmitter.onTimeout(() -> {

});
sseEmitter.onCompletion(() -> {
    System.out.println("完成");
});

最后将实例return出去,前端需要接受SseEmitter类型

2.前端代码编写

前端呢,其实也很简单

var url = 'http://localhost:8080/sse/check/12'
var es = new EventSource(url)

es.addEventListener("info", e => {
    console.log("连接成功", e)
})
es.addEventListener('disconnected', e => {
    console.log("断开连接",e.data)
    es.close()
})
es.addEventListener("message", (e) => {
    console.log("消息", e)
})
es.addEventListener("error", e => {
    if (e.error) {
        console.log("错误,关闭连接", e.error)
        es.close()
    }
})
es.addEventListener("open", e => {
    console.log("打开连接")
})

就这就完了

正文到此结束
本文目录