服务端通过SSE向前端推送消息

0.068字数 453阅读 1486

SSE(Server-Sent Events)相比Websocket来说更加轻量,在代码书写时也更加的方便,所以今天来讲一下SSE的简单使用。

当时接到一个需求是上传word进行解析题目存储,并且前端要显示解析过程,当时首先想到的就是Websocket,后来仔细一想,有没有更轻量级的方法呢,果然还是有的

下面来讲一下具体的使用方法

  1. 我们使用Spring项目来进行讲解,所以需要引入的Spring依赖就不再说明了,然后我们可以惊奇的发现引入完Spring的一些依赖,我们竟然就可以使用SSE了。果然够轻量

  2. 通过以下简单的代码我们就完成了SSE消息的发送,一个方法通过sse监控解析进度,一个方法进行文件解析

/**
     * sse
     */
    @GetMapping("/sse")
    public SseEmitter getSSE(
            HttpServletRequest request) {
        SseEmitter emitter;
        String userId = UserUtils.getUserId(request);
        if (userMap.containsKey(userId)) {
            emitter = userMap.get(userId);
        } else {
            emitter = new SseEmitter();
            userMap.put(userId, emitter);
            emitter.onTimeout(() -> userMap.remove(userId));
        }
        return emitter;
    }
    @PostMapping("/submit")
    public ResponseMessage submit(
            HttpServletRequest request,
            @RequestParam MultipartFile file,
            @RequestParam String category,
            @RequestParam String type,
            @RequestParam(required = false, defaultValue = "") String year) {
        String editor = UserUtils.getUserId(request);
        return ResponseMessage.successMessage(uploadService.upload(file, category, type, year, editor));
    }

返回数据

data:"文件开始解析"
event:upload_start
  1. 接下来说一下我们该如何进行解析操作
/**
     * 存储解析进度key:userId,value:schedule
     */
    private final Map<String, Integer> scheduleMap = Maps.newConcurrentMap();

    /**
     * 是否已经完成解析key:userId,value:是否已经完成
     */
    private final Map<String, Boolean> isFinishedMap = Maps.newConcurrentMap();

    /**
     * 解析完成后返回的数据
     */
    private final Map<String, Map<String, Object>> returnMap = Maps.newConcurrentMap();
    @Scheduled(fixedRate = 500)
    public void uploadSchedule() {
        for (Map.Entry<String, SseEmitter> entry : UploadCtrl.userMap.entrySet()) {
            try {
                String userId = entry.getKey();
                Boolean isFinished = isFinishedMap.get(userId);
                if (isFinished == null) {
                    return;
                }
                if (!isFinished) {
                    SseEmitter.SseEventBuilder builder = SseEmitter
                            .event()
                            .data(scheduleMap.get(userId))
                            .name("upload_schedule");
                    entry.getValue().send(builder);
                    log.info("sse send" + userId + " schedule: " + scheduleMap.get(userId));
                } else {
                    // 代表解析完毕
                    SseEmitter.SseEventBuilder builder = SseEmitter
                            .event()
                            .data(returnMap.get(userId))
                            .name("upload_end");
                    entry.getValue().send(builder);
                    log.info("sse send" + userId + " end: " + gson.toJson(returnMap.get(userId)));
                    // 解析完毕后从Map中移除
                    UploadCtrl.userMap.remove(userId);
                    isFinishedMap.remove(userId);
                    scheduleMap.remove(userId);
                    returnMap.remove(userId);
                    // 关闭emitter
                    entry.getValue().complete();
                }
            } catch (Exception e) {
                log.warn("upload Error of " + entry.getKey(), e);
                UploadCtrl.userMap.remove(entry.getKey());
            }
        }
    }

可以看出我通过是否完成map,解析结果map,解析进度map来进行解析进度的存储,然后通过定时任务,每0.5s向前端发送一次进度。

我们可以发现,使用SSE推送消息是十分的方便快捷的,并且自身封装有一些失败重试,自动重连的机制,相比websocket来说,使用更加方便。

今天前端同事对接接口,才发现sse是没法使用post方法的,这一点大家要注意,所以对博客进行了更改

每当我们想要松懈时,就想想之前的汗水,那是我们今天所拥有的一切的源泉