如何从Spring Boot后端向Angular12前端传输就绪的ExecutionDto列表?
问题描述
我有大量的Execution实体数据,需要处理后转换为ExecutionDto,Dto定义如下:
@Data @AllArgsConstructor @NoArgsConstructor public class ExecutionDto { private long OrgUnitId; private long countsDone; private long countsShouldBe; private float progressPercentage; private double countsIndicator; private String OrgUnitDisplayName; }
目前处理方法是一次性生成完整的Dto列表:
public List<ExecutionDTO> processExecutions(){ // 处理逻辑,生成完整的ExecutionDto列表 return listOfExecutions; }
对应的REST接口一次性返回整个列表:
@GetMapping("/executions/process") public ResponseEntity<List<ExecutionDTO>> processedExecutions(){ List<ExecutionDTO> executionDTOs = executionService.processExecutions(); return ResponseEntity.ok().body(executionDTOs); }
现在前端需要等待后端生成完所有Dto才能拿到数据,耗时极长。我不想用分页,希望将生成好的Dto实时传输到前端,而非等待整个列表处理完成,请问该怎么实现?
解决方案
要实现实时流式传输,核心是让后端边处理边返回数据,而非等待全量数据生成完毕。Spring框架提供两种主流实现方式:
1. 使用Spring MVC的StreamingResponseBody(Servlet栈)
适合传统Spring MVC项目,通过分块传输编码(Chunked Transfer Encoding)分批发送数据。
改造Service方法
将批量处理改为流式迭代处理,避免一次性加载所有数据到内存:
public Stream<ExecutionDTO> processExecutionsStream(){ // 示例:从数据库流式查询Execution,边查边转换Dto return executionRepository.streamAll() // 假设Repository支持流式查询 .map(this::convertToDto); } private ExecutionDTO convertToDto(Execution execution){ ExecutionDTO dto = new ExecutionDTO(); dto.setOrgUnitId(execution.getOrgUnitId()); dto.setCountsDone(execution.getCountsDone()); dto.setCountsShouldBe(execution.getCountsShouldBe()); dto.setProgressPercentage(execution.getProgressPercentage()); dto.setCountsIndicator(execution.getCountsIndicator()); dto.setOrgUnitDisplayName(execution.getOrgUnitDisplayName()); return dto; }
改造REST接口
使用StreamingResponseBody逐个输出Dto,采用NDJSON(换行分隔JSON)格式:
@GetMapping(value = "/executions/stream", produces = MediaType.APPLICATION_NDJSON_VALUE) public ResponseEntity<StreamingResponseBody> streamExecutions(){ Stream<ExecutionDTO> dtoStream = executionService.processExecutionsStream(); StreamingResponseBody responseBody = outputStream -> { ObjectMapper mapper = new ObjectMapper(); try (dtoStream) { // 自动关闭流 dtoStream.forEach(dto -> { try { mapper.writeValue(outputStream, dto); outputStream.write('\n'); // 用换行分隔单个Dto outputStream.flush(); // 立即推送数据到前端 } catch (IOException e) { throw new RuntimeException("写入Dto到流失败", e); } }); } }; return ResponseEntity.ok() .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_NDJSON_VALUE) .body(responseBody); }
2. 使用Spring WebFlux的Flux(响应式栈)
如果项目基于响应式编程,直接返回Flux<ExecutionDTO>即可,框架自动处理流式传输。
改造Service方法
返回Flux类型实现异步流式处理:
public Flux<ExecutionDTO> processExecutionsFlux(){ // 响应式Repository流式查询,例如MongoDB的findAll()返回Flux<Execution> return executionRepository.findAll() .map(this::convertToDto); }
改造REST接口
直接返回Flux<ExecutionDTO>:
@GetMapping(value = "/executions/flux", produces = MediaType.APPLICATION_NDJSON_VALUE) public Flux<ExecutionDTO> fluxExecutions(){ return executionService.processExecutionsFlux(); }
前端处理示例
前端需监听响应数据流,逐行解析处理单个Dto:
fetch('/executions/stream') .then(response => { const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); let buffer = ''; function processChunk() { return reader.read().then(({ done, value }) => { if (done) { console.log('数据流传输完成'); return; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); // 保留未完成的行 lines.forEach(line => { if (line.trim()) { const dto = JSON.parse(line); // 处理单个Dto:比如渲染到页面、添加到列表 console.log('收到Dto:', dto); } }); return processChunk(); }); } return processChunk(); }) .catch(error => console.error('数据流接收失败:', error));
内容的提问来源于stack exchange,提问作者amir raad
相关产品推荐
相关产品推荐

