如何在Java SpringMVC中实现单端点分批返回测试状态?
实现测试用例实时结果推送的几种方案
针对你在SpringMVC应用中需要实时展示每个测试用例执行结果的需求,下面提供三种可落地的实现方案:
一、Server-Sent Events(SSE)方案
适合后端单向推送结果的场景,无需额外依赖,实现成本低。
核心思路
- 定义
TestResult实体类,存储测试用例ID、状态(RUNNING/SUCCESS/FAILED)、执行详情、耗时等信息。 - 实现单例任务管理类,维护SSE连接会话,异步顺序执行测试用例,完成一个就推送一个结果。
- 前端通过SSE长连接接收实时更新,即时渲染UI。
代码示例
任务管理类
@Component public class TestTaskManager { private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>(); private final ExecutorService testExecutor = Executors.newSingleThreadExecutor(); // 保证测试顺序执行 public SseEmitter startTests() { SseEmitter emitter = new SseEmitter(0L); // 无超时设置 String clientId = UUID.randomUUID().toString(); emitters.put(clientId, emitter); testExecutor.submit(() -> { List<String> testCases = getTestCaseList(); // 替换为你的30个测试用例标识集合 for (String testId : testCases) { // 推送测试启动状态 sendUpdate(clientId, new TestResult(testId, "RUNNING", null, 0L)); try { long startTime = System.currentTimeMillis(); // 执行单个测试用例 boolean success = executeTestCase(testId); long cost = System.currentTimeMillis() - startTime; // 推送测试完成状态 sendUpdate(clientId, new TestResult(testId, success ? "SUCCESS" : "FAILED", "执行完成", cost)); } catch (Exception e) { sendUpdate(clientId, new TestResult(testId, "FAILED", e.getMessage(), 0L)); } } // 全部测试完成后关闭连接 try { emitter.send(SseEmitter.event().name("finish").data("所有测试执行完毕")); emitter.complete(); } catch (IOException e) { emitter.completeWithError(e); } emitters.remove(clientId); }); return emitter; } private void sendUpdate(String clientId, TestResult result) { SseEmitter emitter = emitters.get(clientId); if (emitter != null) { try { emitter.send(SseEmitter.event().name("testUpdate").data(result)); } catch (IOException e) { emitters.remove(clientId); } } } // 业务方法:替换为你的测试用例获取逻辑 private List<String> getTestCaseList() { return Arrays.asList("test01", "test02", ..., "test30"); } // 业务方法:替换为你的单个测试用例执行逻辑 private boolean executeTestCase(String testId) throws Exception { // 调用对应测试方法,返回执行结果 return true; } }
SpringMVC控制器
@RestController @RequestMapping("/test/api") public class TestController { @Autowired private TestTaskManager taskManager; @GetMapping("/start") public SseEmitter startTests() { return taskManager.startTests(); } }
前端逻辑
const eventSource = new EventSource('/test/api/start'); // 接收单个测试结果更新 eventSource.addEventListener('testUpdate', (event) => { const result = JSON.parse(event.data); // 更新UI:比如往结果列表中添加一行记录 const resultItem = document.createElement('div'); resultItem.textContent = `${result.testId} | ${result.status} | 耗时${result.cost}ms | ${result.detail}`; document.getElementById('test-result-container').appendChild(resultItem); }); // 接收全部测试完成通知 eventSource.addEventListener('finish', (event) => { alert(event.data); eventSource.close(); }); // 处理连接错误 eventSource.addEventListener('error', (err) => { console.error('SSE连接异常:', err); eventSource.close(); });
二、WebSocket双向通信方案
适合需要前端主动交互(比如中途暂停测试)的场景,支持双向消息传递。
核心思路
- 引入Spring WebSocket依赖,配置WebSocket端点。
- 实现WebSocket处理器,维护前端连接会话,测试用例完成后推送结果。
- 前端通过WebSocket建立连接,实时接收结果并可发送控制指令。
关键代码示例
WebSocket配置类
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(testResultHandler(), "/test/ws").setAllowedOrigins("*"); } @Bean public WebSocketHandler testResultHandler() { return new TestResultWebSocketHandler(); } }
WebSocket处理器
public class TestResultWebSocketHandler extends TextWebSocketHandler { private static final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet(); private final TestTaskManager taskManager; public TestResultWebSocketHandler(TestTaskManager taskManager) { this.taskManager = taskManager; // 注册测试结果回调 taskManager.setResultCallback(this::pushResultToFrontend); } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); // 前端连接成功后启动测试任务 taskManager.startTests(); } private void pushResultToFrontend(TestResult result) { String jsonResult = new ObjectMapper().writeValueAsString(result); for (WebSocketSession session : sessions) { if (session.isOpen()) { session.sendMessage(new TextMessage(jsonResult)); } } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); } }
三、轮询+状态存储方案
适合对实时性要求不高、不想维护长连接的场景,实现最简单。
核心思路
- 用内存Map(分布式场景用Redis)存储测试任务的实时状态,每个任务生成唯一ID。
- 启动测试的接口返回任务ID,前端定期调用查询接口获取已完成的测试结果。
- 异步线程执行测试用例,完成一个就更新状态存储。
关键代码示例
控制器方法
@RestController @RequestMapping("/test/api") public class TestController { @Autowired private TestTaskManager taskManager; @PostMapping("/start") public ResponseEntity<String> startTests() { String taskId = taskManager.startTests(); return ResponseEntity.ok(taskId); } @GetMapping("/results/{taskId}") public ResponseEntity<List<TestResult>> getTestResults(@PathVariable String taskId) { return ResponseEntity.ok(taskManager.getTaskResults(taskId)); } }
前端轮询逻辑
let taskId; // 启动测试 fetch('/test/api/start', { method: 'POST' }) .then(res => res.text()) .then(id => { taskId = id; // 每30秒查询一次结果 setInterval(() => { fetch(`/test/api/results/${taskId}`) .then(res => res.json()) .then(results => { // 更新UI:仅渲染新增的测试结果 console.log(results); }); }, 30000); });
内容的提问来源于stack exchange,提问作者Seriously
相关产品推荐
相关产品推荐

