一、引言
在 Web 应用开发中,实时显示任务进度是一个常见的需求。传统的轮询(Polling)和 WebSocket 是实现实时性的两种常用技术。然而,轮询方式存在资源浪费和延迟问题,而 WebSocket 则在某些场景下显得过于重量级。本文将介绍一种基于 Server-Sent Events(SSE)协议的实现方案,并结合 Vue2 和 Spring Boot 技术栈进行实战演示。
二、Server-Sent Events(SSE)简介
官网:www.w3cschool.cn/nwfchn/wpi3…
Server-Sent Events(SSE)是 HTML5 的一部分,允许服务器向客户端推送信息。与 WebSocket 相比,SSE 的优势在于:
- 服务器到客户端的单向通信,适用于只需要服务器推送数据的场景。
- 基于普通的 HTTP 请求,不需要额外的协议或库。
- 支持自动重连和事件流。
三、实战步骤
1、环境准备
本例使用 Vue2 + Spring Boot 技术栈。确保已安装以下环境:
2、实现服务器端(Spring Boot)
在 Spring Boot 项目中创建一个 Controller,用于发送 SSE 事件:
@RestController
public class ProgressController {
// 模拟进度条
@GetMapping("/progress")
public SseEmitter getProgress() {
SseEmitter emitter = new SseEmitter();
executorService.execute(() -> {
try {
for (int i = 0; i <= 100; i++) {
Thread.sleep(100); // 模拟任务执行时间
// 调用发送方法向浏览器发送 进度信息
emitter.send(i);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
/**
* 用于创建连接(将用户注册到server中)
*/
@GetMapping("/connect")
public SseEmitter connect() {
// 这里我的项目自己保存了登录时的用户信息,总之根据项目来看要将 sse链接和用户绑定
Long userId1 = LoginHelper.getUserId();
return SseEmitterServer.connect(userId1+"");
}
/**
* 实际任务处理场景
*/
@GetMapping("/taskHandle")
public String taskHandle() {
// 根据用户id 获取自己的连接
Long userId1 = LoginHelper.getUserId();
SseEmitter sseEmitter = SseEmitterServer.get(userId1+"");
// 任务处理代码 处理了 10%
.....
sseEmitter.send("10");
// 任务处理代码 处理了 20%
.....
sseEmitter.send("20");
// 任务处理代码 处理完成
.....
sseEmitter.send("100");
return "successed";
}
}
在实际业务中可以将 该对象保存在一个Map中、在业务中使用 用户的userid为key、从map中获取该sse对象再进行发送数据
public class SseEmitterServer {
/**
* 使用map对象,便于根据userId来获取对应的SseEmitter
*/
private static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
public static SseEmitter connect(String userId) {
// 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
SseEmitter sseEmitter = new SseEmitter(0L);
// 注册回调方法
sseEmitter.onCompletion(completionCallBack(userId));
sseEmitter.onError(errorCallBack(userId));
sseEmitter.onTimeout(timeoutCallBack(userId));
// 保存在map中
sseEmitterMap.put(userId, sseEmitter);
return sseEmitter;
}
// 结束链接
private static Runnable completionCallBack(String employeeCode) {
return () -> {
removeUser(employeeCode);
};
}
// 超时
private static Runnable timeoutCallBack(String employeeCode) {
return () -> {
removeUser(employeeCode);
};
}
// 发生异常
private static Consumer<Throwable> errorCallBack(String employeeCode) {
return throwable -> {
removeUser(employeeCode);
};
}
}
2、实现前端页面使用 vue2
在 Vue 项目中,创建一个组件,用于显示任务进度: 使用的是element-ui中的进度条组件
<!-- Progress.vue -->
<template>
<div>
<div>任务进度:{{ progress }}</div>
<el-progress :percentage="progress"></el-progress>
</div>
</template>
<script>
export default {
data() {
return {
progress: 0,
sse: null
};
},
mounted() {
this.initSse();
},
methods: {
initSse() {
// 进度条接口、根据后端接口来填写
this.sse = new EventSource('/progress');
this.sse.onmessage = event => {
this.progress = parseInt(event.data);
};
this.sse.onerror = error => {
console.error('SSE 连接异常', error);
this.sse.close();
};
}
},
beforeDestroy() {
if (this.sse) {
this.sse.close();
}
}
};
</script>