在日常的 Web 开发中,我们经常会遇到这种需求:
“前端上传一个文件到后端,后端需要先处理,然后上传到 OSS,整个过程可能需要几秒甚至几十秒,用户希望能看到实时的任务进度,而不是干等着。”
那该怎么做? 本文我就分享一下我最近在项目里实践的一个优雅方案:
使用 Spring Boot + 自定义注解 + 异步任务 + Redis + SSE(Server-Sent Events) 实现任务进度的实时推送。
上传文件 → 后端异步执行任务 → 前端实时显示进度,比如:
处理文件中... 35% 上传到OSS中... 70% 完成 ✅
这个功能的核心在于「任务进度的可感知性」。 我们把整个流程拆成几个层次:
任务唯一标识(taskId)
进度状态存储
异步执行任务
进度实时通知
自定义注解 + AOP 封装
@TrackProgress,自动开启进度追踪逻辑,开发者不需要在业务里手动操作 Redis。plaintextcom.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 "";
}
标注在需要追踪进度的方法上,比如上传任务处理方法。
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 状态。
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));
}
}
javascriptconst 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 实时、轻量无轮询 ✅ 业务层干净、低侵入
这套设计的核心思路是「解耦」:
这不仅能解决「文件上传」的场景,也可以轻松扩展到大文件导入、模型训练、数据分析任务等长耗时操作。
如果想体验完整工程代码,我在文末贴了一个可运行版本: Spring Boot 进度追踪示例(Redis + SSE + 注解)
本文作者:zjx171
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!