思路
用文件流分别读取服务器硬盘上的文件,形成多个文件流,然后想办法压缩成个一个文件流写入到响应流即可。
方法
使用ZipOutputStream实现。
java.util.zip.ZipOutputStream是Java原生工具类,比较容易上手。
代码示例
先封装一个压缩工具类,代码如下所示。
package com.liuboss.common.utils.zipfile;
import com.liuboss.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Slf4j
@Component
public class ZipFileUtil {
/**
* 打包成zip下载
*/
public void downloadZipFile(HttpServletResponse response, ZipFileDTO fileDTO) {
List<String> names = fileDTO.getFileNms();
List<ByteArrayOutputStream> streams = fileDTO.getStreams();
//输出Excel文件
try (
OutputStream output = response.getOutputStream();
ZipOutputStream zipStream = new ZipOutputStream(output)
) {
String filename = URLEncoder.encode(fileDTO.getZipFileNm(), StandardCharsets.UTF_8.toString());
response.setHeader("Content-Disposition", "attachment;filename=" + filename + ";filename*=utf-8''" + filename);
response.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
//创建压缩文件,并进行打包
for (int i = 0; i < names.size(); i++) {
ZipEntry z = new ZipEntry(names.get(i));
zipStream.putNextEntry(z);
streams.get(i).writeTo(zipStream);
}
zipStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取一个文件,暂存入ByteArrayOutputStream
*
* @param fullPath
* @return
*/
public ByteArrayOutputStream getByteArrayOutputStream(String fullPath) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (
FileInputStream inputStream = new FileInputStream(fullPath)
) {
//小数组大法读取文件流,提高性能
byte[] buff = new byte[8 * 1024];
int len;
while (-1 != (len = inputStream.read(buff))) {
outputStream.write(buff, 0, len);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BusinessException("文件读取错误");
}
return outputStream;
}
}
这是定义的一个DTO对象,用于传递参数。
package com.liuboss.common.utils.zipfile;
import lombok.Getter;
import lombok.Setter;
import java.io.ByteArrayOutputStream;
import java.util.List;
@Setter
@Getter
public class ZipFileDTO {
/**
* 每个文件的文件名称
*/
private List<String> fileNms;
/**
* 每个文件的流
*/
private List<ByteArrayOutputStream> streams;
/**
* 定义的压缩文件的名称
*/
private String zipFileNm;
}
这里是我一般定义业务异常的风格,实现RuntimeException。然后把显示给用户看的信息和给程序员看的信息分离。
package com.liuboss.common.exception;
import lombok.Getter;
import lombok.Setter;
/**
* 可知的业务异常 等价于 BizException的含义,但是这里继承RuntimeException用于避免方法上加throws
*
* @author LYW
* @create 2022-01-19
*/
@Setter
@Getter
public class BusinessException extends RuntimeException {
/**
* 详细的错误信息,用于给程序开发人员进行错误分析用
*/
private String detailMsg;
/**
* 用户可看的信息,避免一大串的报错信息反馈给用户,userMsg要通俗易懂,言简意赅,一些用户不必要知道的信息统统用“系统异常,请联系管理员”代替
*/
private String userMsg;
public BusinessException(String msg) {
super(msg);
userMsg = msg;
}
public BusinessException(String msg, String detailMsg) {
super("userMsg:" + msg + ",detailMsg:" + detailMsg);
this.userMsg = msg;
this.detailMsg = detailMsg;
}
}
具体你的业务逻辑层ServiceImpl,可以这么调用,如下所示。
@Override
public void downloadAll(HttpServletResponse response) {
//TODO 你的逻辑,假设你这个时候已经整理好了一个File数组。
for (File one : files) {
streams.add(zipFileUtil.getByteArrayOutputStream(one.getPath()));
fileNms.add(one.getName());
}
ZipFileDTO zipFileDTO = new ZipFileDTO();
zipFileDTO.setZipFileNm("样例压缩文件.zip");
zipFileDTO.setStreams(streams);
zipFileDTO.setFileNms(fileNms);
zipFileUtil.downloadZipFile(response, zipFileDTO);
}
好了,这样就大功告成了,希望能够对你有用,不妨动动你勤劳的小手给我点个赞或者留个言,谢谢!