Spring Boot和Vue.js项目中实现文件压缩下载功能

在Spring Boot和Vue.js项目中实现文件压缩下载功能,主要思路是后端负责将多个文件压缩成ZIP包,前端负责触发下载并处理文件流。以下是具体的实现方案。

🔧 后端实现(Spring Boot)

后端需要完成的核心任务是接收文件ID列表,查询对应的文件路径,将它们压缩成ZIP文件,并通过HTTP响应返回给前端。

1. 添加依赖

首先,在pom.xml中添加处理ZIP文件的依赖,推荐使用功能强大的zip4j库。

xml 复制代码
<dependency>
    <groupId>net.lingala.zip4j</groupId>
    <artifactId>zip4j</artifactId>
    <version>2.11.5</version>
</dependency>

2. 核心服务层代码

创建一个Service方法,用于根据文件ID列表生成ZIP文件。此方法会查询文件实体,构建压缩包内的目录结构,并将文件添加到ZIP中。

java 复制代码
@Service
@Slf4j
public class FileDownloadService {

    @Autowired
    private SysDownloadCenterAttachmentMapper attachmentMapper;
    
    @Autowired
    private ScjtConfig scjtConfig; // 假设这个配置类包含文件上传的基础路径

    /**
     * 创建ZIP压缩文件
     * @param ids 文件ID列表,用逗号分隔
     * @return 生成的ZIP文件在服务器上的绝对路径
     */
    private String createZipFile(String ids) {
        // 将传入的ID字符串转换为整型数组
        Integer[] idArray = Convert.toIntArray(ids);
        List<String> allFilePaths = new ArrayList<>();

        // 1. 收集所有需要压缩的文件路径
        for (Integer id : idArray) {
            // 查询该ID对应的所有附件信息
            List<SysDownloadCenterAttachment> attachments = attachmentMapper.selectList(
                new LambdaQueryWrapper<SysDownloadCenterAttachment>()
                    .eq(SysDownloadCenterAttachment::getDownloadCenterId, id));
            
            if (!CollectionUtils.isEmpty(attachments)) {
                // 将附件信息转换为完整的服务器文件路径
                List<String> paths = attachments.stream().map(attachment -> {
                    String location = attachment.getLocation();
                    String fullPath = scjtConfig.getUploadPath() + location;
                    // 简单的路径补全逻辑,请根据你的实际存储结构调整
                    if (!fullPath.startsWith("D:")) {
                        fullPath = "D:" + fullPath; // 示例路径,请修改为你的实际存储路径
                    }
                    return fullPath;
                }).collect(Collectors.toList());
                allFilePaths.addAll(paths);
            }
        }

        // 2. 创建ZIP文件并添加内容
        ZipFile zipFile = new ZipFile("d:/temp_download_filename.zip"); // 指定临时ZIP文件路径
        for (String filePath : allFilePaths) {
            File fileToAdd = new File(filePath);
            if (fileToAdd.exists()) {
                try {
                    // 设置ZIP文件参数:压缩方法、级别等
                    ZipParameters parameters = new ZipParameters();
                    parameters.setCompressionMethod(CompressionMethod.DEFLATE);
                    parameters.setCompressionLevel(CompressionLevel.NORMAL);
                    
                    // 可选:在ZIP包内按原始目录结构组织文件,或自定义文件夹结构
                    // 例如,设置文件在ZIP包内的路径和名称
                    parameters.setFileNameInZip(fileToAdd.getName()); 
                    
                    // 将文件添加到ZIP包
                    zipFile.addFile(fileToAdd, parameters);
                    log.info("文件已成功添加到ZIP: {}", filePath);
                } catch (ZipException e) {
                    log.error("添加文件到ZIP时出错: {}", filePath, e);
                    throw new RuntimeException("压缩文件失败", e);
                }
            } else {
                log.warn("文件不存在,跳过: {}", filePath);
            }
        }
        return zipFile.getFile().getAbsolutePath();
    }

    /**
     * 供Controller调用的下载入口方法
     */
    public ResponseEntity<Resource> downloadZip(String ids, HttpServletResponse response) throws FileNotFoundException {
        String zipFilePath = createZipFile(ids);
        File zipFile = new File(zipFilePath);
        
        // 将文件包装成Spring的Resource对象(这里使用InputStreamResource)
        InputStreamResource resource = new InputStreamResource(new FileInputStream(zipFile));
        
        // 设置HTTP响应头,告诉浏览器这是一个需要下载的附件
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename="download.zip"")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .contentLength(zipFile.length())
                .body(resource);
    }
}

3. 控制器(Controller)

提供REST API接口给前端调用。

less 复制代码
@RestController
@RequestMapping("/api/file")
public class FileDownloadController {

    @Autowired
    private FileDownloadService fileDownloadService;

    @PostMapping("/download-zip")
    public ResponseEntity<Resource> downloadZip(@RequestParam String ids, HttpServletResponse response) {
        try {
            return fileDownloadService.downloadZip(ids, response);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("ZIP文件未找到", e);
        }
    }
}

🎨 前端实现(Vue.js)

前端负责向后端接口发送请求,并将接收到的ZIP文件流保存为本地文件。

1. 安装依赖(可选)

如果前端也需要处理压缩(例如在浏览器端打包),可以安装jszipfile-saver。但对于本文描述的后端压缩方案,以下为最简实现。

复制代码
npm install file-saver

2. 发起下载请求

创建一个方法来调用后端的下载接口。使用axios(或你项目使用的HTTP客户端)时,关键是指定responseType: 'blob',以便正确处理二进制流响应。

xml 复制代码
<template>
  <div>
    <button @click="handleBatchDownload">下载选中文件压缩包</button>
  </div>
</template>

<script>
import axios from 'axios'; // 确保已安装axios

export default {
  data() {
    return {
      selectedFileIds: [1, 2, 3] // 假设这是用户选中的文件ID列表
    };
  },
  methods: {
    async handleBatchDownload() {
      try {
        // 将文件ID数组转换为后端接口需要的格式,例如逗号分隔的字符串
        const idsParam = this.selectedFileIds.join(',');
        
        const response = await axios.post('/api/file/download-zip', 
          { ids: idsParam }, // 请求参数
          {
            responseType: 'blob' // 必须设置,告知axios期望接收二进制数据
          }
        );

        // 处理下载的文件流
        if (response.data.size > 0) {
          // 创建一个指向Blob对象的URL
          const blobUrl = window.URL.createObjectURL(new Blob([response.data], { type: 'application/zip' }));
          
          // 创建一个隐藏的<a>标签并触发点击下载
          const link = document.createElement('a');
          link.style.display = 'none';
          link.href = blobUrl;
          link.download = 'files.zip'; // 设置下载的文件名
          document.body.appendChild(link);
          link.click();
          
          // 清理DOM和释放Blob URL,避免内存泄漏
          document.body.removeChild(link);
          window.URL.revokeObjectURL(blobUrl);
        } else {
          this.$message.error('没有找到可下载的文件');
        }
      } catch (error) {
        console.error('下载失败:', error);
        this.$message.error('文件下载失败,请重试');
      }
    }
  }
};
</script>

💡 性能与注意事项

  1. ​大文件与内存管理​ :对于超大文件或极多文件的压缩,建议使用流式压缩(如ZipOutputStream)而非一次性加载所有文件到内存,避免内存溢出(OOM)。后端示例中创建临时文件的方式在文件很大时也需注意磁盘空间和IO性能。
  2. ​临时文件清理​ :后端生成的临时ZIP文件应在下载完成后或定期进行清理,例如使用File.delete()或在项目启动时清理旧的临时文件,以免占用过多磁盘空间。
  3. ​错误处理​:增强代码的健壮性,对文件不存在、网络异常等情况进行妥善处理,并给前端返回明确的错误信息。
  4. ​安全考虑​:确保对文件ID进行权限校验,防止用户通过篡改ID下载未经授权的文件。

通过以上步骤,你就可以在Spring Boot和Vue.js项目中实现一个完整且健壮的文件压缩下载功能了。

相关推荐
Mr.45672 小时前
Spring Boot 全局鉴权认证简单实现方案
spring boot·后端
我是天龙_绍2 小时前
vue3 中,setup 函数 和 <script setup> 的区别
前端
krifyFan2 小时前
vue3+elementPlus el-date-picker 自定义禁用状态hook 实现结束时间不能小于开始时间
前端·vue.js·elementui
卷Java2 小时前
WXML 编译错误修复总结
xml·java·前端·微信小程序·uni-app·webview
SevgiliD2 小时前
解决使用 fixed固定列时el-table导致纵向滚动条问题
前端·vue.js·elementui
天蓝色的鱼鱼2 小时前
🚀 告别 Electron 的臃肿:用 Tauri 打造「轻如鸿毛」的桌面应用
前端
余大侠在劈柴2 小时前
go语言学习记录9.23
开发语言·前端·学习·golang·go
bitbitDown3 小时前
忍了一年多,我终于对i18n下手了
前端·javascript·架构
麦兜*3 小时前
Spring Boot 项目 Docker 化:从零到一的完整实战指南
数据库·spring boot·redis·后端·spring·缓存·docker