Spring Boot中获取GZip编码前内容大小实现前端下载进度
解决方案
要在启用GZip压缩的Spring Boot应用中传递JSON响应的原始数据大小,核心思路是先序列化Java对象得到原始JSON的字节长度,再通过自定义响应头传递给前端,具体实现步骤如下:
1. 计算原始JSON的字节长度
利用Spring Boot自动配置的ObjectMapper(保证和框架默认序列化规则一致),将业务对象序列化为JSON字节数组,直接获取其长度。
2. 设置自定义响应头
在Controller方法中,通过HttpServletResponse或ResponseEntity的头设置方法,添加自定义头(比如X-Original-Content-Length)传递原始大小。
代码示例
Controller实现
@RestController @RequestMapping("/api") public class DataController { // 注入Spring Boot默认配置的ObjectMapper private final ObjectMapper objectMapper; public DataController(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @GetMapping("/large-data") public ResponseEntity<BusinessData> getLargeBusinessData(HttpServletResponse response) throws JsonProcessingException { // 生成业务数据对象 BusinessData data = buildLargeBusinessData(); // 序列化对象为JSON字节数组,计算原始大小 byte[] rawJsonBytes = objectMapper.writeValueAsBytes(data); long originalContentLength = rawJsonBytes.length; // 设置自定义响应头,传递原始数据大小 response.setHeader("X-Original-Content-Length", String.valueOf(originalContentLength)); return ResponseEntity.ok(data); } // 模拟生成大数据对象 private BusinessData buildLargeBusinessData() { BusinessData data = new BusinessData(); data.setRecords(IntStream.range(0, 10000) .mapToObj(i -> new Record("ID-" + i, "详情内容-" + i)) .collect(Collectors.toList())); return data; } // 业务数据类示例 static class BusinessData { private List<Record> records; // Getter & Setter public List<Record> getRecords() { return records; } public void setRecords(List<Record> records) { this.records = records; } } static class Record { private String id; private String detail; // Constructor, Getter & Setter public Record(String id, String detail) { this.id = id; this.detail = detail; } public String getId() { return id; } public String getDetail() { return detail; } } }
前端进度展示示例
前端通过读取自定义头获取原始大小,结合Content-Length(压缩后的传输大小)计算传输进度:
fetch('/api/large-data') .then(response => { const originalSize = parseInt(response.headers.get('X-Original-Content-Length'), 10); const compressedSize = parseInt(response.headers.get('Content-Length'), 10); const reader = response.body.getReader(); let receivedBytes = 0; return new ReadableStream({ start(controller) { function processChunk() { reader.read().then(({ done, value }) => { if (done) { controller.close(); console.log(`下载完成,原始数据大小:${(originalSize / 1024).toFixed(2)}KB`); return; } receivedBytes += value.length; // 计算传输进度(基于压缩后的字节数) const transferProgress = (receivedBytes / compressedSize) * 100; console.log(`传输进度:${transferProgress.toFixed(2)}%,原始数据总大小:${(originalSize / 1024).toFixed(2)}KB`); controller.enqueue(value); processChunk(); }); } processChunk(); } }); }) .then(stream => new Response(stream)) .then(response => response.json()) .then(data => console.log('数据解析完成', data)) .catch(err => console.error('请求异常', err));
注意事项
- 必须使用Spring Boot自动配置的
ObjectMapper,避免自定义序列化规则导致计算的原始大小与实际响应的原始大小不一致。 - 若处理超大对象(GB级),直接序列化计算大小会占用过多内存,此时可考虑流式序列化统计字节数,或者提前在业务逻辑中计算数据的预估大小。
- 自定义头名称可根据团队规范调整,只要前后端约定一致即可。
内容的提问来源于stack exchange,提问作者Nuwan Sameera
相关产品推荐
相关产品推荐

