美文网首页
记一次spring-boot使用ZipOutputStream压

记一次spring-boot使用ZipOutputStream压

作者: 最小栗子 | 来源:发表于2023-11-17 16:26 被阅读0次

由于网关的要求,控制器必须返回ResponseEntity对象进行文件输出,而且文件是存储在minio上,要求是对minio上的多个文件进行压缩下载,而且要求是不限制数量大小,但是开发时错估了生产环境下真正的文件大小,所以一开始的代码主要如下:

public ResponseEntity<InputStreamResource> downloadAsZip(List<Long> idList) throws Exception {  
    List<Media> mediaList = lambdaQuery().select(Media::getId, Media::getType, Media::getPath, Media::getObjectKey, Media::getName)  
            .in(Media::getId, idList)  
            .list();  
    // 压缩文件  
    ByteArrayOutputStream out = new ByteArrayOutputStream();  
    ZipOutputStream zipOut = new ZipOutputStream(out);  
    for (Media media: mediaList) {  
        InputStream inputStream = MinioUtils.getFileInputStream(media.getPath(), media.getObjectKey());  
        ZipEntry zipEntry = new ZipEntry(media.getName());  
        zipOut.putNextEntry(zipEntry);  
        byte[] buffer = new byte[1024];  
        int bytesRead;  
        while ((bytesRead = inputStream.read(buffer)) != -1) {  
            zipOut.write(buffer, 0, bytesRead);  
        }  
        zipOut.closeEntry();  
        inputStream.close();  
    }  
    zipOut.close();  
    out.close();  
    // 写出文件流  
    HttpHeaders headers = new HttpHeaders();  
    headers.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", URLEncoder.encode("下载文件.zip", "UTF-8")));  
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);  
    return ResponseEntity.ok()  
            .headers(headers)  
            .contentType(MediaType.APPLICATION_OCTET_STREAM)  
            .body(new InputStreamResource(new ByteArrayInputStream(out.toByteArray())));  
}

当然这些代码是我后面举例的,与实际我使用的代码还有很多逻辑差距,包括计算content-length等,这段代码主要的问题在于压缩文件时ZipOutputStream内传入的是一个ByteArrayOutputStream,而ByteArrayOutputStream实质上是在内部维护了一个byte数组用来存储字节到内存中,所以当压缩的文件越来越多时会导致堆内存溢出的这么一个现象;

找到问题根源后将代码修改如下,将压缩流直接输出到客户端,这样有一个弊端,就是前端的进度条不好算,因为文件是动态压缩的,不能一开始就给出总文件大小,但是可以通过提前计算压缩前的总文件大小,再配合前端做一下对偏差的修饰,来满足这个需求:

public ResponseEntity<StreamingResponseBody> downloadAsZip(List<Long> idList) throws Exception {  
    List<Media> mediaList = lambdaQuery().select(Media::getId, Media::getType, Media::getPath, Media::getObjectKey, Media::getName)  
            .in(Media::getId, idList)  
            .list();  
    // 写出文件流  
    HttpHeaders headers = new HttpHeaders();  
    headers.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=\"%s\"", URLEncoder.encode("下载文件.zip", "UTF-8")));  
    headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);  
    return ResponseEntity.ok()  
            .headers(headers)  
            .contentType(MediaType.APPLICATION_OCTET_STREAM)  
            .body(outputStream -> {  
                ZipOutputStream zipOut = new ZipOutputStream(outputStream);  
                for (Media media: mediaList) {  
                    InputStream inputStream = MinioUtils.getFileInputStream(media.getPath(), media.getObjectKey());  
                    ZipEntry zipEntry = new ZipEntry(media.getName());  
                    zipOut.putNextEntry(zipEntry);  
                    byte[] buffer = new byte[1024];  
                    int bytesRead;  
                    while ((bytesRead = inputStream.read(buffer)) != -1) {  
                        zipOut.write(buffer, 0, bytesRead);  
                    }  
                    zipOut.closeEntry();  
                    inputStream.close();  
                }  
                zipOut.close();  
            });  
}

相关文章

网友评论

      本文标题:记一次spring-boot使用ZipOutputStream压

      本文链接:https://www.haomeiwen.com/subject/cgxuwdtx.html