需求概述
支持按勾选的成果申请记录批量下载附件。按"年份"+“成果名称"将对应的成果附件放入文件夹,最后将所有文件夹打包成一个压缩包,名称未"成果附件”+“下载时间(精确到秒)”。例如:
工具类
在网上找了个Zip压缩工具类,基本满足了我的需求,代码如下:
/**
* Zip压缩工具类:支持压缩文件列表(包括压缩包)和文件夹
*
* @author chuenhung
* @createTime 2022/06/08
*/
public class ZipUtil {
private static final int BUFFER_SIZE = 2 * 1024;
/**
* @param srcFiles 需要压缩的文件列表
* @param out 输出流
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<File> srcFiles ,OutputStream out) throws RuntimeException, FileNotFoundException {
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @param srcFiles 需要压缩的文件列表
* @param outDir 输出文件目录
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<File> srcFiles ,String outDir) throws RuntimeException, FileNotFoundException {
OutputStream out = new FileOutputStream(new File(outDir));
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
for (File srcFile : srcFiles) {
byte[] buf = new byte[BUFFER_SIZE];
zos.putNextEntry(new ZipEntry(srcFile.getName()));
int len;
FileInputStream in = new FileInputStream(srcFile);
while ((len = in.read(buf)) != -1){
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
}
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil",e);
}finally{
if(zos != null){
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* @param outDirList 压缩文件夹路径
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws RuntimeException 压缩失败会抛出运行时异常
*/
public static void toZip(List<String> outDirList, OutputStream out,
boolean KeepDirStructure) throws RuntimeException, Exception {
ZipOutputStream zos = null;
try {
zos = new ZipOutputStream(out);
List<File> sourceFileList = new ArrayList<>();
for (String dir : outDirList) {
File sourceFile = new File(dir);
sourceFileList.add(sourceFile);
}
compress(sourceFileList, zos, KeepDirStructure);
} catch (Exception e) {
throw new RuntimeException("zip error from ZipUtil", e);
} finally {
if (zos != null) {
try {
zos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 递归压缩方法
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param KeepDirStructure 是否保留原来的目录结构,
* true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
* @throws Exception
*/
private static void compress(File sourceFile, ZipOutputStream zos,
String name, boolean KeepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
if (sourceFile.isFile()) {
zos.putNextEntry(new ZipEntry(name));
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
// Complete the entry
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
if (KeepDirStructure) {
zos.putNextEntry(new ZipEntry(name + "/"));
zos.closeEntry();
}
} else {
for (File file : listFiles) {
if (KeepDirStructure) {
compress(file, zos, name + "/" + file.getName(),
KeepDirStructure);
} else {
compress(file, zos, file.getName(), KeepDirStructure);
}
}
}
}
}
private static void compress(List<File> sourceFileList,
ZipOutputStream zos, boolean KeepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
for (File sourceFile : sourceFileList) {
String name = sourceFile.getName();
if (sourceFile.isFile()) {
zos.putNextEntry(new ZipEntry(name));
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
if (KeepDirStructure) {
zos.putNextEntry(new ZipEntry(name + "/"));
zos.closeEntry();
}
} else {
for (File file : listFiles) {
if (KeepDirStructure) {
compress(file, zos, name + "/" + file.getName(),
KeepDirStructure);
} else {
compress(file, zos, file.getName(),
KeepDirStructure);
}
}
}
}
}
}
}
业务中需要删除临时文件夹以及下面的所有文件,用到的工具类代码如下所示:
/**
* 文件工具类
* @author chuenhung
* @createTime 2022/06/09
*/
public class FileUtils {
/**
* 删除文件或文件夹
*
* @param: file 如果是文件删除该文件,如果是文件夹删除该文件夹以及下面的所有文件
* @return void
* @author chuenhung
* @date 2022/6/9/
*/
public static void deleteFile(File file) {
if (file.isFile()) {
file.delete();
} else {
String[] childFilePaths = file.list();
for (String childFilePath : childFilePaths) {
File childFile = new File(file.getAbsolutePath() + "/" + childFilePath);
deleteFile(childFile);
}
file.delete();
}
}
}
业务代码
核心逻辑就是要把每个成果申请的所有文件放入到成果申请临时文件夹中,成果申请临时文件夹名:“年份”+“成果名称”,然后对每个成果申请文件夹压缩,最后删除临时文件夹。我在代码中也有详细地写注释,业务代码如下所示:
@Override
public void batchDownload(HttpServletResponse response, HttpServletRequest request, List<Long> idList) throws Exception {
// 1、参数校验
if(CollectionUtil.isEmpty(idList)){
throw new CommonsException(MessageCode.PARAM_NULL);
}
// 2、设置response相关内容(包括文件名)
StringBuilder builder = new StringBuilder();
builder.append("成果附件");
builder.append(DateUtil.getNowDate("yyyyMMddHHmmss"));
builder.append(".zip");
String fileName = HttpHelper.createDownloadFileName(builder.toString());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
// 3、根据成果主键批量查询成果信息
Map<String,Object> map = new HashMap<>();
map.put("idList",idList);
List<AchievementApplication> applicationList = achievementApplicationMapper.queryAchievementApplicationList(map);
if(CollectionUtil.isEmpty(applicationList)){
throw new CommonsException(MessageCode.DATA_NOT_FOUND);
}
// 4、把每个成果申请的所有文件放入到成果申请临时文件夹中,成果申请临时文件夹名:年份+成果名称
// 每一个压缩的成果附件路径
List<String> outDirList = new ArrayList<>();
for(AchievementApplication application: applicationList){
if(StringUtil.isNotEmpty(application.getAttachment())){
List<AttachmentFileBo> attachmentFileBoList = attachmentService.listFormalAttachmentFile(application.getAttachment());
if(CollectionUtil.isNotEmpty(attachmentFileBoList)){
// 成果申请临时文件夹名:年份+成果名称
builder.delete(0,builder.length());
builder.append(application.getYear());
builder.append("-");
builder.append(application.getName());
// 循环对每个成果申请的所有文件压缩
List<String> fileNameList = new ArrayList<>();
// 每个成果的临时复制后存放的文件夹,之后对这个文件夹压缩
String outDir = attachementUploadUrl+builder.toString();
File outDirFile = new File(outDir);
outDirFile.mkdirs();
for(AttachmentFileBo fileBo: attachmentFileBoList){
// 每个成果如有重复附件名则不处理
if(!fileNameList.contains(fileBo.getFileName())){
File sourceFile = new File(attachementUploadUrl+fileBo.getFileSaveName());
if (!sourceFile.exists()) {
log.error("AchievementApplicationServiceImpl.batchDownload附件{}丢失",fileBo.getFileName());
throw new CommonsException(MessageCode.ATTACHMENT_LOST, "附件:" + fileBo.getFileName() + "丢失");
}
// 复制到临时文件夹
File destinctFile = new File(outDir+"/"+fileBo.getFileName());
Files.copy(sourceFile.toPath(), destinctFile.toPath());
}
fileNameList.add(fileBo.getFileName());
}
outDirList.add(outDir);
}
}
}
// 4、对成果申请文件夹压缩
if(CollectionUtil.isNotEmpty(outDirList)){
ZipUtil.toZip(outDirList, response.getOutputStream(),true);
}
// 5、删除临时文件夹
for(String outDir: outDirList){
File file = new File(outDir);
FileUtils.deleteFile(file);
}
}