编辑
2025-11-11
技术分享
00

目录

从0到1实现:Spring Boot + 自定义注解 + 异步任务 + Redis + SSE 实时进度推送
最终效果
设计思路
项目结构
核心注解设计
AOP 切面实现
Redis 存储进度状态
异步任务执行
控制器示例
前端监听(SSE 简化版)
进阶:任务进度 + Redis + SSE
优雅的点
写在最后

从0到1实现:Spring Boot + 自定义注解 + 异步任务 + Redis + SSE 实时进度推送

在日常的 Web 开发中,我们经常会遇到这种需求:

“前端上传一个文件到后端,后端需要先处理,然后上传到 OSS,整个过程可能需要几秒甚至几十秒,用户希望能看到实时的任务进度,而不是干等着。”

那该怎么做? 本文我就分享一下我最近在项目里实践的一个优雅方案

使用 Spring Boot + 自定义注解 + 异步任务 + Redis + SSE(Server-Sent Events) 实现任务进度的实时推送。


最终效果

上传文件 → 后端异步执行任务 → 前端实时显示进度,比如:

处理文件中... 35% 上传到OSS中... 70% 完成 ✅

设计思路

这个功能的核心在于「任务进度的可感知性」。 我们把整个流程拆成几个层次:

  1. 任务唯一标识(taskId)

    • 每个上传/任务都有唯一 taskId,用于追踪进度。
  2. 进度状态存储

    • 用 Redis 保存任务当前的进度状态(方便分布式扩展)。
  3. 异步执行任务

    • 防止阻塞主线程,让任务后台执行。
  4. 进度实时通知

    • 使用 SSE(Server-Sent Events)实现前端实时监听后端推送。
  5. 自定义注解 + AOP 封装

    • 通过一个注解 @TrackProgress,自动开启进度追踪逻辑,开发者不需要在业务里手动操作 Redis。

项目结构

plaintext
com.example.progress ├── annotation │ └── TrackProgress.java ├── aspect │ └── ProgressAspect.java ├── config │ └── RedisConfig.java ├── controller │ └── UploadController.java ├── service │ ├── AsyncTaskService.java │ ├── ProgressService.java │ └── SSEManager.java ├── model │ └── TaskProgress.java └── ProgressApplication.java

核心注解设计

java
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface TrackProgress { String value() default ""; }

标注在需要追踪进度的方法上,比如上传任务处理方法。


AOP 切面实现

java
@Aspect @Component @RequiredArgsConstructor public class ProgressAspect { private final ProgressService progressService; @Around("@annotation(trackProgress)") public Object around(ProceedingJoinPoint joinPoint, TrackProgress trackProgress) throws Throwable { String taskId = UUID.randomUUID().toString(); progressService.start(taskId); try { Object result = joinPoint.proceed(); progressService.complete(taskId); return result; } catch (Exception e) { progressService.fail(taskId); throw e; } } }

AOP 自动生成 taskId,在任务开始、完成、失败时更新 Redis 状态。


Redis 存储进度状态

java
@Service @RequiredArgsConstructor public class ProgressService { private final StringRedisTemplate redisTemplate; private static final String PREFIX = "task:progress:"; public void updateProgress(String taskId, int percent, String message) { Map<String, Object> progress = new HashMap<>(); progress.put("percent", percent); progress.put("message", message); redisTemplate.opsForValue().set(PREFIX + taskId, new ObjectMapper().writeValueAsString(progress)); } public String getProgress(String taskId) { return redisTemplate.opsForValue().get(PREFIX + taskId); } public void complete(String taskId) { updateProgress(taskId, 100, "任务完成 ✅"); } public void fail(String taskId) { updateProgress(taskId, -1, "任务失败 ❌"); } public void start(String taskId) { updateProgress(taskId, 0, "任务开始"); } }

异步任务执行

java
@Service @RequiredArgsConstructor public class AsyncTaskService { private final ProgressService progressService; @Async @TrackProgress public void processAndUpload(String taskId, MultipartFile file) throws Exception { progressService.updateProgress(taskId, 20, "正在处理文件..."); Thread.sleep(1000); progressService.updateProgress(taskId, 60, "正在上传到OSS..."); Thread.sleep(2000); progressService.updateProgress(taskId, 90, "OSS上传完成,正在收尾..."); Thread.sleep(500); progressService.complete(taskId); } }

控制器示例

java
@RestController @RequestMapping("/api/upload") @RequiredArgsConstructor public class UploadController { private final AsyncTaskService asyncTaskService; private final ProgressService progressService; @PostMapping public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) { String taskId = UUID.randomUUID().toString(); asyncTaskService.processAndUpload(taskId, file); return ResponseEntity.ok(Map.of("taskId", taskId)); } @GetMapping("/status/{taskId}") public ResponseEntity<?> getStatus(@PathVariable String taskId) { return ResponseEntity.ok(progressService.getProgress(taskId)); } }

前端监听(SSE 简化版)

javascript
const evtSource = new EventSource(`/api/progress/subscribe/${taskId}`); evtSource.onmessage = (e) => { const data = JSON.parse(e.data); document.getElementById('progress').innerText = `${data.message} (${data.percent}%)`; };

进阶:任务进度 + Redis + SSE

  • Redis 保存进度,支持集群。
  • SSE 订阅同 taskId 渠道,实时推送状态变化。
  • 如果服务重启,前端重新订阅即可恢复监听。

优雅的点

✅ 统一注解管理进度逻辑 ✅ 异步 + Redis 支撑分布式 ✅ SSE 实时、轻量无轮询 ✅ 业务层干净、低侵入


写在最后

这套设计的核心思路是「解耦」:

  • 开发者只关心业务逻辑;
  • AOP + 注解自动处理进度逻辑;
  • Redis + SSE负责状态共享和实时通信。

这不仅能解决「文件上传」的场景,也可以轻松扩展到大文件导入模型训练数据分析任务等长耗时操作。

如果想体验完整工程代码,我在文末贴了一个可运行版本: Spring Boot 进度追踪示例(Redis + SSE + 注解)

本文作者:zjx171

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!