如何在Spring Boot Webflux SSE端点向Angular 7传递错误信息
解决Webflux SSE端点异常信息无法传递到Angular前端的问题
我之前也踩过这个坑!核心问题在于EventSource本身的设计限制:当服务端返回非2xx的HTTP状态码时,EventSource只会触发全局的error事件,但无法获取响应的状态码和响应体——它直接就断开连接了,根本不给你读取错误信息的机会。所以你不能像常规REST端点那样直接抛出带@ResponseStatus的异常,得换一种方式在SSE流里传递错误信息。
下面是两种可行的解决方案,你可以根据自己的架构选择:
方案1:用自定义事件传递错误信息
这种方式是在SSE流里发送一个专门的"错误事件",把状态码和异常详情打包进去,让前端通过监听这个事件来处理错误。
后端代码调整
你需要捕获服务抛出的异常,把它转换成一个带有自定义类型的ServerSentEvent,而不是直接抛出异常中断流:
@GetMapping(path = "{id}/async", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<Object>> getProcessedResultStream(@PathVariable("id") String id) { return Mono.fromCallable(() -> service.getProcessedResult(id)) // 处理正常结果,发送普通消息事件 .map(result -> ServerSentEvent.<Object>builder() .data(Optional.of(result)) .build()) // 捕获自定义异常,转换为错误事件 .onErrorResume(RequestNotFoundException.class, ex -> Flux.just(ServerSentEvent.<Object>builder() .event("error-event") // 用自定义事件名避免和EventSource全局error冲突 .data(Map.of( "status", HttpStatus.NOT_FOUND.value(), "message", ex.getMessage() )) .build()) ) // 最后发送断开连接事件 .concatWith(Flux.just(ServerSentEvent.builder() .event("disconnect") .build())); }
前端Angular处理
在前端,你需要监听这个自定义的error-event事件,解析里面的错误信息,同时保留原有的断开连接和全局错误处理:
const eventSource = new EventSource(`/your-api-path/${id}/async`); // 处理正常结果 eventSource.addEventListener('message', (event) => { const result = JSON.parse(event.data); console.log('处理结果:', result); // 这里更新你的UI逻辑 }); // 处理自定义错误事件 eventSource.addEventListener('error-event', (event) => { const errorData = JSON.parse(event.data); console.error(`请求失败 [${errorData.status}]: ${errorData.message}`); // 显示错误提示给用户 eventSource.close(); // 错误后主动关闭连接 }); // 处理连接级别的错误(比如网络中断) eventSource.addEventListener('error', (event) => { console.error('SSE连接异常:', event); eventSource.close(); }); // 处理断开连接事件 eventSource.addEventListener('disconnect', () => { eventSource.close(); console.log('数据流已断开'); });
方案2:用统一响应DTO包装结果和错误
如果你的项目有统一的响应格式,也可以创建一个通用的DTO来包装正常结果和错误信息,这样前端只需要解析同一个结构即可,不需要区分不同的事件类型。
第一步:创建统一响应DTO
public class SseResponse<T> { private boolean success; private T data; private Integer statusCode; private String errorMessage; // 构造器:正常结果 public SseResponse(T data) { this.success = true; this.data = data; this.statusCode = null; this.errorMessage = null; } // 构造器:错误结果 public SseResponse(Integer statusCode, String errorMessage) { this.success = false; this.data = null; this.statusCode = statusCode; this.errorMessage = errorMessage; } // Getter和Setter省略 }
第二步:后端返回包装后的响应
@GetMapping(path = "{id}/async", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<SseResponse<ProcessedResult>>> getProcessedResultStream(@PathVariable("id") String id) { return Mono.fromCallable(() -> service.getProcessedResult(id)) .map(SseResponse::new) // 正常结果包装 .map(response -> ServerSentEvent.builder(response).build()) .onErrorResume(RequestNotFoundException.class, ex -> Flux.just(ServerSentEvent.builder( new SseResponse<>(HttpStatus.NOT_FOUND.value(), ex.getMessage()) ).build()) ) .concatWith(Flux.just(ServerSentEvent.builder(new SseResponse<>(null)) .event("disconnect") .build())); }
第三步:前端统一解析
eventSource.addEventListener('message', (event) => { const response: SseResponse<ProcessedResult> = JSON.parse(event.data); if (response.success) { if (response.data) { // 处理正常结果 console.log('处理结果:', response.data); } } else { // 处理错误 console.error(`请求失败 [${response.statusCode}]: ${response.errorMessage}`); eventSource.close(); } }); // 断开连接和全局错误处理同上
为什么不能直接抛出异常?
再强调一下:SSE是基于持续HTTP流的协议,一旦服务端返回非2xx状态码,EventSource会立即终止连接,并且不会暴露响应体内容——这是EventSource的标准行为,不是Angular或Webflux的问题。所以必须在流的内部传递错误信息,而不是通过HTTP状态码。
如果你的场景对HTTP状态码有强需求,那可能需要考虑替换成WebSocket,它的双向通信模型能更好地处理错误状态传递。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

