java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot多文件下载并打包为ZIP压缩包

基于SpringBoot实现多文件批量下载并打包为ZIP压缩包的完整解决方案

作者:qq_29757467

在日常的 Web 开发中,文件下载是非常常见的功能需求,而多文件批量下载并打包为 ZIP 压缩包 更是高频场景(比如批量下载合同、图片、报表等),本文将基于 SpringBoot 框架,手把手教你实现这一功能,从核心思路到完整代码,让你快速掌握,需要的朋友可以参考下

一、功能需求分析

我们要实现的核心功能:

  1. 前端传入多个文件 ID,后端根据 ID 查询文件的存储路径
  2. 将这些文件读取并打包成一个 ZIP 压缩包
  3. 通过 HTTP 响应将 ZIP 包直接下载到客户端
  4. 处理文件不存在、IO 异常等边界情况

二、核心技术点

三、代码实现

3.1 实体类:Attachment(文件附件实体)

首先定义文件附件实体类,对应数据库中存储的文件信息:

package com.itl.project.common.attachment.domain;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.itl.framework.aspectj.lang.annotation.Excel;
import com.itl.framework.web.domain.BaseEntity;

import java.util.Date;

/**
 * 文件信息对象 sys_attachment
 *
 * @author itl
 */
@Data
public class Attachment extends BaseEntity
{
    private static final long serialVersionUID = 1L;

    /** 文件主键 */
    private Long fileId;

    /** 文件业务id */
    @Excel(name = "文件业务id")
    @TableField(exist = false)
    private String fileBusinessId;

    /** 文件key */
    @Excel(name = "文件key")
    private String fileKey;

    /** 文件名称 */
    @Excel(name = "文件名称")
    private String fileName;

    /** mime类型 */
    @Excel(name = "mime类型")
    private String mimeType;

    /** 文件大小 */
    @Excel(name = "文件大小")
    private Long fileSize;

    /** 文件路径 */
    @Excel(name = "文件路径")
    private String filePath;

    /** 文件地址 */
    @Excel(name = "文件地址")
    private String fileUrl;

    /** 文件后缀 */
    @Excel(name = "文件后缀")
    private String suffix;

    /** 是否存在 */
	private boolean exists = false;

    /** 创建时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;


    /**
     * 版本号
     */
    private String version;


    /**
     * 文件备注
     */
    private String comment;

    @TableField(exist = false)
    private String fileIds;

}

3.2 核心工具类:CompressDownloadUtil(ZIP 压缩工具)

封装 ZIP 压缩的核心逻辑,负责将多个文件打包到输出流中:

package com.itl.common.utils;

import com.itl.project.common.attachment.domain.Attachment;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 文件压缩下载工具类
 * 功能:将多个文件压缩到指定输出流中,用于ZIP包下载
 */
public class CompressDownloadUtil {

    /**
     * 将多个文件压缩到指定输出流中
     *
     * @param lists 需要压缩的文件列表
     * @param outputStream  压缩后的输出流(直接关联HTTP响应输出流)
     */
    public static void compressZip(List<Attachment> lists, OutputStream outputStream) {
        ZipOutputStream zipOutStream = null;
        try {
            // 包装成ZIP格式输出流,提升写入效率
            zipOutStream = new ZipOutputStream(new BufferedOutputStream(outputStream));
            // 设置压缩方法为DEFLATED(默认,有压缩效果)
            zipOutStream.setMethod(ZipOutputStream.DEFLATED);
            
            // 循环处理每个文件
            for (Attachment file : lists) {
                java.io.File localFile = new java.io.File(file.getFilePath());
                // 校验文件是否存在,避免空指针或文件找不到异常
                if (localFile.exists() && localFile.isFile()) {
                    try (FileInputStream fileInputStream = new FileInputStream(localFile)) {
                        // 读取文件字节数据
                        byte[] data = new byte[(int) localFile.length()];
                        fileInputStream.read(data);
                        
                        // 创建ZIP条目(压缩包内的文件名称)
                        ZipEntry zipEntry = new ZipEntry(file.getFileName());
                        zipEntry.setSize(localFile.length());
                        
                        // 将条目写入ZIP流
                        zipOutStream.putNextEntry(zipEntry);
                        zipOutStream.write(data);
                        // 关闭当前条目
                        zipOutStream.closeEntry();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("文件压缩失败:" + e.getMessage());
        } finally {
            // 关闭流(注意关闭顺序:先关ZIP流,再关基础输出流)
            try {
                if (Objects.nonNull(zipOutStream)) {
                    zipOutStream.flush();
                    zipOutStream.close();
                }
                if (Objects.nonNull(outputStream)) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.3 控制器:文件下载接口

import com.itl.common.utils.CompressDownloadUtil;
import com.itl.project.common.attachment.domain.Attachment;
import com.itl.project.common.attachment.service.AttachmentService;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.UUID;

/**
 * 文件下载控制器
 */
@RestController
@RequestMapping("/file")
public class FileDownloadController {

    // 注入文件附件服务(实际项目中通过@Autowired注入)
    private AttachmentService attachmentService;

    /**
     * 多文件下载为ZIP压缩包
     * @param fileid 多个文件ID,建议用逗号分隔(如:1,2,3)
     * @param request HTTP请求
     * @param response HTTP响应
     * @throws UnsupportedEncodingException 编码异常
     */
    @Log(title = "多文件下载", businessType = BusinessType.DOWNLOAD)
    @CrossOrigin // 解决跨域问题(前后端分离必加)
    @GetMapping("/downloadZip/{fileid}")
    public void downloadZip(@PathVariable("fileid") String fileid, 
                           HttpServletRequest request,
                           HttpServletResponse response) throws UnsupportedEncodingException {
        // 1. 根据文件ID查询文件列表
        Attachment attachment = new Attachment();
        attachment.setFileIds(fileid);
        List<Attachment> attachmentList = attachmentService.selectAttachmentList(attachment);
        
        // 2. 校验文件列表是否为空
        if (attachmentList.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }
        
        // 3. 设置响应头,告诉浏览器下载文件
        // 生成唯一的ZIP包名称(避免重名)
        String downloadName = UUID.randomUUID().toString().replaceAll("-", "") + ".zip";
        // 解决文件名中文乱码问题
        String fileName = new String(downloadName.getBytes("UTF-8"), "ISO8859-1");
        
        // 设置响应头
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        // 设置响应内容类型为二进制流(通用文件下载类型)
        response.setContentType("application/octet-stream");
        // 禁用缓存
        response.setHeader("Cache-Control", "no-cache");
        
        // 4. 调用工具类压缩文件并写入响应输出流
        try {
            CompressDownloadUtil.compressZip(attachmentList, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}

四、关键细节说明

4.1 解决文件名中文乱码

String fileName = new String(downloadName.getBytes("UTF-8"), "ISO8859-1");

4.2 流的关闭顺序

在工具类中,关闭流的顺序必须是:先关闭 ZipOutputStream,再关闭基础的 OutputStream。因为 ZipOutputStream 是包装流,关闭包装流时会自动刷新缓冲区,确保所有数据都写入基础流。

4.3 异常处理

4.4 跨域处理

五、前端调用示例(Axios)

// 前端下载ZIP包示例
function downloadZip(fileIds) {
    // 创建a标签,通过GET请求下载
    const a = document.createElement('a');
    a.href = `/file/downloadZip/${fileIds}`;
    a.download = '文件包.zip'; // 可选,优先使用后端返回的文件名
    a.click();
    // 移除a标签
    a.remove();
}

// 调用示例:下载ID为1,2,3的文件
downloadZip("1,2,3");

六、优化建议

  1. 大文件处理:本文代码适用于中小文件,若处理大文件,建议使用BufferedInputStream分块读取,避免一次性加载文件到内存导致 OOM。
  2. 文件名去重:如果压缩包内有重名文件,可在文件名后添加序号(如:test.txt → test (1).txt)。
  3. 进度条:大文件下载时,可结合 WebSocket 实现下载进度条。
  4. 权限控制:在接口中添加权限校验,确保只有授权用户才能下载文件。
  5. 日志完善:除了操作日志,可记录文件下载的大小、耗时、用户等信息,便于问题排查。

七、总结

本文基于 SpringBoot + JDK 原生 ZIP 工具类,实现了多文件批量下载并打包为 ZIP 压缩包的功能,核心思路是:
查询文件列表 → 2. 设置下载响应头 → 3. 读取文件并压缩 → 4. 写入响应输出流。
该方案无需引入额外的压缩依赖,基于 JDK 原生 API 实现,轻量且稳定,适用于大多数 Web 项目的文件下载场景。如果有更复杂的压缩需求(如加密、分卷压缩),可考虑使用 Apache Commons Compress 工具包。

以上就是基于SpringBoot实现多文件批量下载并打包为ZIP压缩包的完整解决方案的详细内容,更多关于SpringBoot多文件下载并打包为ZIP压缩包的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文