java使用压缩流把文件打成压缩包下载

需求:前台传过来一个url数组,其中是每个文件的路径,后台需要获取到每个文件然后将其打成压缩包返回流给前台下载

核心是:ZipOutputStream,废话不多说,上代码

  1. savePath 是我在yml配置文件里配置好的文件存储路径
    在这里插入图片描述
  2. 在使用谷歌(火狐可以)下载过程中遇到问题:文件名中含有半角符号无法下载,因为文件名是前台命名的,所以这个我就随便命名个1.zip解决一下
  3. 文件名有中文要用iso_8859_1转义一下,不然会乱码(使用swagger测试时一直都是乱码,是swagger的问题,使用浏览器直接下载没事)
  4. 接口写完之后使用swagger或者postMan等测试工具无法识别流,返回的都是乱码,这是正常的,前台处理一下就可以了
package com.hongseng.app.config.file;

import com.hongseng.app.mapper.task.TaskMapper;
import model.dto.dwonload.DownloadZipDto;
import model.entity.task.TaskList;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import result.Result;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

  @Value("${spring.servlet.multipart.location}")
   private String savePath;
  /**
     * 下载压缩包
     *
     * @param downloadZipDto
     * @param response
     * @throws IOException
     */
    public void downloadZipFromUrl(DownloadZipDto downloadZipDto, HttpServletResponse response) throws IOException {
        // 创建临时路径,存放压缩文件
        String zipFilePath = savePath + "\\11.zip";
        // 压缩输出流,包装流,将临时文件输出流包装成压缩流,将所有文件输出到这里,打成zip包
        ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath));
        // 循环调用压缩文件方法,将一个一个需要下载的文件打入压缩文件包
        @NotEmpty List<String> urls = downloadZipDto.getUrls();
        for (String url : urls) {
            int lastIndexOf = url.lastIndexOf('/');
            String name = url.substring(lastIndexOf + 1);
            String newPath = savePath + "\\" + name;
            // 该方法在下面定义
            fileToZip(newPath, zipOut);
        }
        zipOut.flush();
        // 压缩完成后,关闭压缩流
        zipOut.close();

        // 压缩包名称拼接
        @NotBlank String departmentName = downloadZipDto.getDepartmentName();

        TaskList taskList = taskMapper.selectById(downloadZipDto.getTaskListId());

        //拼接下载默认名称并转为ISO-8859-1格式
        String fileName = new String(("1.zip").getBytes(), StandardCharsets.ISO_8859_1);
        response.setHeader("Content-Disposition", "attchment;filename="+fileName);

        //该流不可以手动关闭,手动关闭下载会出问题,下载完成后会自动关闭
        ServletOutputStream outputStream = response.getOutputStream();
        FileInputStream inputStream = new FileInputStream(zipFilePath);
        // 如果是SpringBoot框架,在这个路径
        // org.apache.tomcat.util.http.fileupload.IOUtils产品
        // 否则需要自主引入apache的 commons-io依赖
        // copy方法为文件复制,在这里直接实现了下载效果
        IOUtils.copy(inputStream, outputStream);
        outputStream.close();
        // 关闭输入流
        inputStream.close();
        //下载完成之后,删掉这个zip包
        File fileTempZip = new File(zipFilePath);
        fileTempZip.delete();
    }

    private void fileToZip(String filePath,ZipOutputStream zipOut) throws IOException {
        // 需要压缩的文件
        File file = new File(filePath);
        // 获取文件名称,如果有特殊命名需求,可以将参数列表拓展,传fileName
        String fileName = file.getName();
        FileInputStream fileInput = new FileInputStream(filePath);
        // 缓冲
        byte[] bufferArea = new byte[1024 * 10];
        BufferedInputStream bufferStream = new BufferedInputStream(fileInput, 1024 * 10);
        // 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
        zipOut.putNextEntry(new ZipEntry(fileName));
        int length = 0;
        // 最常规IO操作,不必紧张
        while ((length = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
            zipOut.write(bufferArea, 0, length);
        }
        //关闭流
        fileInput.close();
        // 需要注意的是缓冲流必须要关闭流,否则输出无效
        bufferStream.close();
        // 压缩流不必关闭,使用完后再关
    }

补充:测试遇到问题
这个代码会有压缩文件错误,无法解压和解压后文件丢失的情况
关键代码:

    byte[] bufferArea = new byte[1024 * 10];
        BufferedInputStream bufferStream = new BufferedInputStream(fileInput, 1024 * 10);
        // 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
        zipOut.putNextEntry(new ZipEntry(fileName));
        int length = 0;
        // 最常规IO操作,不必紧张
        while ((length = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
            zipOut.write(bufferArea, 0, length);
        }

看了博友的博客说是因为有可能一次读取的byte数组太大了导致问题,修改后的代码可以使用

    byte[] bufferArea = new byte[1024];
        BufferedInputStream bufferStream = new BufferedInputStream(fileInput);
        // 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
        zipOut.putNextEntry(new ZipEntry(fileName));
        int length = 0;
        // 最常规IO操作,不必紧张
        while ((length = bufferStream.read(bufferArea)) != -1) {
            zipOut.write(bufferArea, 0, length);
        }
             // 解决剩余的
        int remain = bufferStream.available();
        byte[] last = new byte[remain];
        bufferStream.read(last);
        zipOut.write(last);

还增加了一点:关闭压缩流之前调用了flush(),刷新压缩流

zipOut.flush();

优化:使用了try-with-resource关闭流

新代码

 /**
     * 下载压缩包
     *
     * @param downloadZipDto
     * @param response
     * @throws IOException
     */
    public void downloadZipFromUrl(DownloadZipDto downloadZipDto, HttpServletResponse response) {
        // 创建临时路径,存放压缩文件
        String zipFilePath = savePath + "\\11.zip";
        try (ServletOutputStream outputStream = response.getOutputStream()){
            // 压缩输出流,包装流,将临时文件输出流包装成压缩流,将所有文件输出到这里,打成zip包
            ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath));
            // 循环调用压缩文件方法,将一个一个需要下载的文件打入压缩文件包
            @NotEmpty List<String> urls = downloadZipDto.getUrls();
            for (String url : urls) {
                int lastIndexOf = url.lastIndexOf('/');
                String name = url.substring(lastIndexOf + 1);
                String newPath = savePath + "\\" + name;
                // 该方法在下面定义
                fileToZip(newPath, zipOut);
            }
            zipOut.flush();
            // 压缩完成后,关闭压缩流
            zipOut.close();
            response.setHeader("Content-Disposition", "attchment;filename="+"1.zip");
            //该流不可以手动关闭,手动关闭下载会出问题,下载完成后会自动关闭
            FileInputStream inputStream = new FileInputStream(zipFilePath);
            // 如果是SpringBoot框架,在这个路径
            // org.apache.tomcat.util.http.fileupload.IOUtils产品
            // 否则需要自主引入apache的 commons-io依赖
            // copy方法为文件复制,在这里直接实现了下载效果
            IOUtils.copy(inputStream, outputStream);
            outputStream.close();
            // 关闭输入流
            inputStream.close();
            //下载完成之后,删掉这个zip包
            File fileTempZip = new File(zipFilePath);
            fileTempZip.delete();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    private void fileToZip(String filePath, ZipOutputStream zipOut) {
        // 需要压缩的文件
        File file = new File(filePath);
        // 获取文件名称,如果有特殊命名需求,可以将参数列表拓展,传fileName
        String fileName = file.getName();
        FileInputStream fileInput = null;
        BufferedInputStream bufferStream = null;
        try {
            fileInput = new FileInputStream(filePath);
            // 缓冲
            byte[] bufferArea = new byte[1024];
            bufferStream = new BufferedInputStream(fileInput);

            // 将当前文件作为一个zip实体写入压缩流,fileName代表压缩文件中的文件名称
            zipOut.putNextEntry(new ZipEntry(fileName));
            int length = 0;
            // 最常规IO操作,不必紧张
            while ((length = bufferStream.read(bufferArea)) != -1) {
                zipOut.write(bufferArea, 0, length);
            }
            // 解决剩余的
            int remain = bufferStream.available();
            byte[] last = new byte[remain];
            bufferStream.read(last);
            zipOut.write(last);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭流
                fileInput.close();
                // 需要注意的是缓冲流必须要关闭流,否则输出无效
                bufferStream.close();
                // 压缩流不必关闭,使用完后再关
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

不影响运行,但是看着糟心:

java.lang.IllegalStateException: Cannot call sendError() after the response has been committed
出现这个错误,应该是多次response导致的,可以这么理解,http server发送response后就关闭了socket,这个时候再次发送response给http client就会出现这个问题。
解决方法,不要返回值 使用void

    /**
     * 文件批量打包下载
     *
     * @param downloadZipDto 文件名称
     * @return result
     */
    @ApiOperation("文件批量打包下载")
    @AnonymousAccess
    @PostMapping(value = "/download-zip")
    public void downloadZipFromUrl(@RequestBody @Valid DownloadZipDto downloadZipDto, HttpServletResponse response) {
        downloadConfig.downloadZipFromUrl(downloadZipDto, response);
    }
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值