使用Spring Boot、VUE实现SSE长连接:跟踪文件上传和任务进度

news2024/12/16 0:42:52

使用Spring Boot实现SSE长连接:跟踪文件上传和任务进度

文章目录

  • 使用Spring Boot实现SSE长连接:跟踪文件上传和任务进度
    • 什么是SSE?
    • 使用场景
    • 前端库选择
      • 安装`event-source-polyfill`
        • 1. 创建SSE连接
        • 2. 关闭SSE连接
        • 3. 结合Vue.js使用
    • 使用Spring Boot实现SSE
      • 1. 创建SSE工具类
      • 2. 实现文件上传进度通知
      • 3. 实现任务执行进度跟踪

在现代Web应用中,服务器实时向客户端推送数据是一项非常常见的需求。在多种实现技术中,Server-Sent Events(SSE)是一个轻量级的解决方案,适用于对实时性要求不高、数据量不大的场景。本文将介绍如何在Spring Boot中使用SSE,结合实际案例展示在文件上传和任务执行中的应用。

什么是SSE?

Server-Sent Events(SSE)是HTML5标准的一部分,允许服务器单向推送消息到客户端。它与WebSocket不同,SSE只支持服务器向客户端推送数据,而不支持客户端向服务器发送数据。SSE的优点在于其实现简单、兼容性好,非常适合不需要双向通讯的场景。

使用场景

  • 文件上传进度通知:当用户上传文件时,服务器可以通过SSE实时告知客户端上传进度。
  • 任务执行进度跟踪:对于耗时的任务(如数据处理、批量导入等),可以通过SSE向客户端实时推送任务进度。

前端库选择

由于原生的EventSource在某些浏览器中可能不支持自定义请求头,因此选择event-source-polyfill库来建立SSE连接。这一库允许在初始化时设置请求头,如身份验证所需的token等。

安装event-source-polyfill

首先安装event-source-polyfill库:

npm install event-source-polyfill

前端实现步骤

1. 创建SSE连接

通过封装一个createSseConnection函数,建立与服务端的SSE连接,并定义如何处理不同消息事件:

import { EventSourcePolyfill } from "event-source-polyfill";
import config from "../../../../config";

export function createSseConnection(context, topic, callbacks, showMessage, onError) {
  const url = config.sse_host[process.env.NODE_ENV] + "/techik/sse/subscribe?topic=" + topic;
  const headers = { "token": localStorage.getItem("token") };
  
  const source = new EventSourcePolyfill(url, {
    headers,
    heartbeatTimeout: 30 * 60 * 1000,
  });

  source.onopen = () => {
    console.log("SSE connection established.");
  };

  source.onmessage = (e) => {
    const message = e.data;
    if (callbacks.onMessage) {
      callbacks.onMessage(message, context);
    }
    if (showMessage && message.includes('success')) {
      context.$message({
        type: "success",
        duration: 3000,
        message: "提交成功!",
      });
    }
  };

  source.onerror = (e) => {
    console.error("SSE error:", e);
    if (callbacks.onError) {
      callbacks.onError(e, context);
    }
    if (e.readyState === EventSource.CLOSED) {
      console.log("SSE connection closed.");
      if (callbacks.onClose) {
        callbacks.onClose(context);
      }
    } else if (onError) {
      onError(e);
    }
  };

  return source;
}
2. 关闭SSE连接

提供一个closeSseConnection函数,当不再需要接收消息时,手动关闭SSE连接:

export function closeSseConnection(source, context, afterClose) {
  if (source) {
    source.close();
    console.log("SSE connection closed.");
    if (afterClose) {
      afterClose(context);
    }
  }
}
3. 结合Vue.js使用

在Vue组件中使用createSseConnection和closeSseConnection管理SSE连接:

export default {
  data() {
    return {
      sseSource: null,
    };
  },
  methods: {
    startListening() {
      const topic = "uploadProgress"; // 根据需求选择不同的topic
      this.sseSource = createSseConnection(this, topic, {
        onMessage: this.handleMessage,
        onError: this.handleError,
        onClose: this.handleClose,
      }, true);
    },
    handleMessage(message, context) {
      console.log("Received message:", message);
      // 处理接收到的消息
    },
    handleError(error, context) {
      console.error("Error received:", error);
    },
    handleClose(context) {
      console.log("Connection closed.");
    },
    stopListening() {
      closeSseConnection(this.sseSource, this, () => {
        this.sseSource = null;
      });
    }
  },
  beforeDestroy() {
    this.stopListening();
  }
};

使用Spring Boot实现SSE

1. 创建SSE工具类

首先,创建一个工具类SseUtils来管理SSE连接:

package com.techik.Util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;

@Slf4j
public class SseUtils {

    // 原子计数器,用于跟踪活跃的连接数
    private static final AtomicInteger COUNT = new AtomicInteger(0);

    // 存储主题和对应的SseEmitter映射关系,确保线程安全
    private static final Map<String, SseEmitter> SSE_EMITTER_MAP = new ConcurrentHashMap<>();

    /**
     * 建立新的SSE连接,并设置相关的回调函数
     * 
     * @param topic 要连接的主题
     * @return 新创建的SseEmitter对象
     */
    public static SseEmitter connect(String topic) {
        // 设置超时时间为30分钟
        SseEmitter sseemitter = new SseEmitter(30 * 60 * 1000L);

        // 设置连接完成后的回调
        sseemitter.onCompletion(completionCallBack(topic));

        // 设置连接出错时的回调
        sseemitter.onError(errorCallBack(topic));

        // 设置连接超时时的回调
        sseemitter.onTimeout(timeoutCallBack(topic));

        // 将新的SseEmitter存储到Map中
        SSE_EMITTER_MAP.put(topic, sseemitter);

        // 增加活跃连接数
        COUNT.incrementAndGet();

        log.info("创建新的sse连接,当前的主题:{}", topic);

        return sseemitter;
    }

    /**
     * 发送消息到指定的主题
     * 
     * @param topic   目标主题
     * @param message 要发送的消息内容
     */
    public static void sendMessage(String topic, String message) {
        if (SSE_EMITTER_MAP.containsKey(topic)) {
            try {
                // 发送消息
                SSE_EMITTER_MAP.get(topic).send(message);
            } catch (IOException e) {
                log.error("当前的主题:{},发送消息-错误:{}", topic, e.getMessage());
            }
        }
    }

    /**
     * 移除指定主题的连接
     * 
     * @param topic 要移除的主题
     */
    public static void removeTopic(String topic) {
        // 从Map中移除SseEmitter
        SSE_EMITTER_MAP.remove(topic);

        // 减少活跃连接数
        COUNT.decrementAndGet();

        log.info("删除主题:{}", topic);
    }

    // 创建连接完成的回调函数
    private static Runnable completionCallBack(String topic) {
        return () -> {
            log.info("结束连接,{}", topic);
            removeTopic(topic);
        };
    }

    // 创建连接超时的回调函数
    private static Runnable timeoutCallBack(String topic) {
        return () -> {
            log.info("连接超时,{}", topic);
            removeTopic(topic);
        };
    }

    // 创建连接出错的回调函数
    private static Consumer<Throwable> errorCallBack(String topic) {
        return throwable -> {
            log.error("连接异常,{}", topic);
            removeTopic(topic);
        };
    }
}

2. 实现文件上传进度通知

在上传文件的过程中,可以使用SseEmitter向客户端实时推送上传进度:

@PostMapping("/upload")
public ResponseEntity<String> uploadFile(MultipartFile file) {
    String topic = "uploadProgress";
    SseEmitter emitter = SseUtils.connect(topic);
    // 模拟上传文件并推送进度
    for (int i = 0; i <= 100; i += 10) {
        SseUtils.sendMessage(topic, "上传进度: " + i + "%");
        Thread.sleep(500); // 模拟耗时操作
    }
    SseUtils.sendMessage(topic, "上传完成!");
    return ResponseEntity.ok("文件上传成功");
}

3. 实现任务执行进度跟踪

类似文件上传,当需要执行耗时任务时,可以使用SSE推送任务进度:

@GetMapping("/executeTask")
public ResponseEntity<String> executeTask() {
    String topic = "taskProgress";
    SseEmitter emitter = SseUtils.connect(topic);
    // 模拟任务执行并推送进度
    for (int i = 0; i <= 100; i += 20) {
        SseUtils.sendMessage(topic, "任务进度: " + i + "%");
        Thread.sleep(1000); // 模拟耗时操作
    }
    SseUtils.sendMessage(topic, "任务完成!");
    return ResponseEntity.ok("任务执行成功");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2260207.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASP.NET Core API + MySql

环境 数据库&#xff1a; mysql8.0 后端&#xff1a; vs2022 ASP.NET Core API .net 8 前端&#xff1a; Hbuilderx bootstrap 5.3.0 jquery v3.7.1 bootstrap-table 1.23.5 创建项目 添加资源包 AutoMapper Microsoft.EntityFrameworkCore.Tools 8.0.0 Pomelo.EntityFramew…

小程序维护外包流程和费用

由于某些原因很多老板想要跟换掉小程序原来合作的开发公司&#xff0c;重新把小程序系统维护外包新的公司。小程序系统外包维护是一个涉及多个方面的过程&#xff0c;需要从需求明确、选择团队到持续优化等多个环节进行细致管理。以下就是小程序系统外包维护主要包括几个关键步…

代码随想录算法训练营第三十二天|动态规划理论基础|LC509.肥波那些数|LC70.爬楼梯|LC746.使用最小花费爬楼梯

动态规划理论基础 解释&#xff1a;动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff1b;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 动态规划五部曲&#xff1a; 1、确定dp数组&#xff08;dp table&#xff09;…

亮相AICon,火山引擎边缘云揭秘边缘AI Agent探索与实践

12月13-14日&#xff0c;AICon 全球人工智能开发与应用大会在北京成功举办。火山引擎边缘智能技术负责人谢皓受邀出席大会&#xff0c;以《AI Agent 在边缘云的探索与实践》为主题&#xff0c;与全球 AI 领域的资深专家&#xff0c;共同深入探讨大模型落地、具身智能、多模态大…

找出1000以内的所有回文数

找出1000以内的所有回文数 方法概述检查回文数的方法伪代码C代码实现代码解析运行结果在计算机科学中,回文数是一种具有对称性质的数,即从左向右读和从右向左读都是相同的。例如,121、1331、12321都是回文数。本文将利用数据结构、C语言和算法的知识来编写一个程序,找出100…

数据保护策略:如何保障重要信息的安全

一、什么是数据安全&#xff1f; 数据安全是保护数字信息免遭盗窃、未经授权的访问和恶意修改的过程。这是一个持续的过程&#xff0c;负责监督信息的收集、存储和传输。 机密性&#xff1a;保护数据免遭未授权方访问。 完整性&#xff1a;保护数据免遭未经授权的修改、损坏…

SpringBoot【八】mybatis-plus条件构造器使用手册!

一、前言&#x1f525; 环境说明&#xff1a;Windows10 Idea2021.3.2 Jdk1.8 SpringBoot 2.3.1.RELEASE 经过上一期的mybatis-plus 入门教学&#xff0c;想必大家对它不是非常陌生了吧&#xff0c;这期呢&#xff0c;我主要是围绕以下几点展开&#xff0c;重点给大家介绍 里…

基于springboot+vue的高校校园交友交流平台设计和实现

文章目录 系统功能部分实现截图 前台模块实现管理员模块实现 项目相关文件架构设计 MVC的设计模式基于B/S的架构技术栈 具体功能模块设计系统需求分析 可行性分析 系统测试为什么我&#xff1f; 关于我项目开发案例我自己的网站 源码获取&#xff1a; 系统功能 校园交友平台…

33.攻防世界upload1

进入场景 看看让上传什么类型的文件 传个木马 把txt后缀改为png 在bp里把png改为php 上传成功 用蚁剑连接 在里面找flag 得到

基于Python+Sqlite3实现的搜索和推荐系统

基于Python实现的搜索和推荐系统 一、引言 伴随着科技的不断进步&#xff0c;互联网&#xff0c;万维网的不断发展。我们越来越热爱万维网&#xff0c;也欣赏他的发展方式。20世纪90年代初&#xff0c;万维网还只是一个将文档联系起来的简单网络。如今&#xff0c;他已经成为…

使用idea创建一个JAVA WEB项目

文章目录 1. javaweb项目简介2. 创建2.1 idea新建项目2.2 选择&#xff0c;命名2.3 打开2.4 选择tomcat运行2.5 结果 3. 总结 1. javaweb项目简介 JavaWeb项目是一种基于Java技术的Web应用程序&#xff0c;主要用于开发动态网页和Web服务。这种项目能够构建在Java技术栈之上&a…

【潜意识Java】Java基础教程:从零开始的学习之旅

目录 1. Java 简介 2. Java 程序结构 2.1 包声明&#xff08;Package Declaration&#xff09; 2.2 导入语句&#xff08;Import Statement&#xff09; 2.3 类声明&#xff08;Class Declaration&#xff09; 2.4 main 方法&#xff08;Main Method&#xff09; 3. Jav…

算法题(2):三步问题

审题&#xff1a;需要输出小孩上楼梯的方式的数量&#xff08;需要取模&#xff09; 思路&#xff1a; 如果正面来思考这个问题会无从下手&#xff0c;因为我们的分类太多了&#xff0c;没有办法把大问题缩小。 但是如果反过来思考&#xff0c;小孩最后一步有几种情况&#xff…

Quad Remesher使用教程

为什么要拓扑&#xff1f; 我们知道&#xff0c;模型在三维软件中的表现&#xff0c;是由一系列的面通过不同角度组合而成的。3D模型制作层面上的拓扑&#xff0c;按我的理解来说&#xff0c;就是一个模型的面的结构分布——布线。想表现和制作一个三维模型&#xff0c;有无限…

Android 使用Overlay现实主题切换

最近项目上&#xff0c;想做一个主题切换的功能&#xff0c;整理了一下发布出来&#xff0c;主要使用的是IOverlayManager&#xff0c;大体思路如下&#xff1a; 1、想切换的应用&#xff0c;各自做overlay apk&#xff08;简称皮肤包&#xff09; 2、将overlay apk push 到v…

apk反编译修改教程系列-----超简单修改apk中名称 包名 布局文本以及其中的文字选项 手机设置中apk对应修改演示【三十三】

💝💝💝在反编译apk中,每个初学者可能最感兴趣入门的就是修改包名 去更新以及其中选项文本的修改。这样循序渐进来激发学习的兴趣。了解一些apk中常见的修改方法。对于修改手机rom中的 系统类等等的apk原理都是一样的。这篇是应粉丝需要的修改apk基础教程. 通过博文了解…

02HBuilder工具准备

一、下载网址&#xff1a; HBuilder 使用教程 | 菜鸟教程 DCloud - 数字天堂官网、HBuilderX、HBuilder、uni-app、uniapp、5、5plus、mui、wap2app、流应用、HTML5、小程序开发、跨平台App、多端框架 二、下载 三、解压 四、启动程序

记一个framebuffer显示混乱的低级错误

记一个framebuffer显示混乱的低级错误 由于framebuffer的基础知识不扎实&#xff0c;这个任务上我多卡了两天&#xff0c;差点把我搞死&#xff0c;于此记录为后鉴。 打算用awtk做一个多进程项目&#xff0c;计划把framebuffer的内容通过websocket输出到浏览器上去显示画面, …

C++---入门

C补充了上的不足&#xff0c;使用各方面更加便捷&#xff0c;在C的基础上面向对象进行编译&#xff0c;学过C之后&#xff0c;对学习C的一定的帮助。 命名空间&#xff1a; namesp的定义&#xff1a;定义命名空间&#xff0c;需要使⽤到namespace关键字&#xff0c;后⾯跟命…

boost电路的同步和异步模式 及CCM、DCM模式 介绍

一、同步 异步 1.一般区别 电路结构 异步升压 IC&#xff1a;采用传统的 Boost 电路结构&#xff0c;主要由电感、开关管、二极管和输出电容等组成。同步升压 IC&#xff1a;与异步升压 IC 的 Boost 电路相比&#xff0c;其将二极管替换成了一个同步整流 MOS 管&#xff0c;…