美文网首页
记一次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