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项目中实现一个完整且健壮的文件压缩下载功能了。

相关推荐
To_OC5 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志6 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble8 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
戮漠summer9 小时前
Missing Semester 计算机教育中缺失的一课 Lecture 01 Shell
开发语言·后端·scala
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g10 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js