浏览器实现导出ZIP、TGZ格式压缩包的两种方式

首先导出各种格式的压缩包是常见的需求,以上总结记录一下常用的两种方式!!!!!
首先第一种常见的方式:代码实现共用浏览器域和请求头进行文件下载(从服务器上下载)

第一种实现方式如下:

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.file.FileReader;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yr.common.util.BeanUtils;
import com.yr.common.util.FileUtil;
import com.yr.common.util.FrameUtils;
import com.yr.common.util.K8SUtil;
import com.yr.dataAnnotations.config.DatasetImageProperties;
import com.yr.dataAnnotations.config.ResourceAccessProperties;
import com.yr.dataAnnotations.config.SpringBootPlusProperties;
import com.yr.dataAnnotations.entity.*;
import com.yr.dataAnnotations.mapper.*;
import com.yr.dataAnnotations.param.TrainingProjectTaskPageParam;
import com.yr.dataAnnotations.service.TrainingProjectTaskService;
import com.yr.dataAnnotations.util.DictValueUtil;
import com.yr.dataAnnotations.vo.TrainingProjectTaskQueryVo;
import com.yr.newframe.core.common.exception.BusinessException;
import com.yr.newframe.core.common.service.impl.BaseServiceImpl;
import com.yr.newframe.core.core.pagination.PageInfo;
import com.yr.newframe.core.core.pagination.Paging;
import com.yr.newframe.core.core.util.FrameUtil;
import io.kubernetes.client.custom.Quantity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;

public void download(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
    //获取数据目录ID,给下面当目录使用
    TrainingProjectTask trainingProjectTask = trainingProjectTaskMapper.selectById(id);
    //要压缩的模型文件目录路径:"D:\\Desktop\\testModel\\0e328d1b517643bc82ff9fa1f98614d6\\5994db1e98154505ae79825a8d547ec0";
    String sourceDirPath = model_dev_path + trainingProjectTask.getModelId() + File.separator + trainingProjectTask.getVersionId();

    //成功文件路径
    String logPath = trainingProjectTask.getLogPath();
    //生成文件类型后缀(zip文件则换成.zip,其他压缩文件同理)
    String tarGzFilePath = sourceDirPath + ".tar.gz";

    // 创建TarArchiveOutputStream并关联到GzipCompressorOutputStream,进而关联到文件输出流
    try (FileOutputStream fos = new FileOutputStream(tarGzFilePath);
         GzipCompressorOutputStream gos = new GzipCompressorOutputStream(fos);
         TarArchiveOutputStream taos = new TarArchiveOutputStream(gos)) {

        // 如果sourceDirPath不为空,则添加模型文件到versionfiles目录
        if (StringUtils.isNotBlank(sourceDirPath)) {
            addFilesToTarWithPrefix(sourceDirPath, "versionFiles/", taos);
        } else {
            // sourceDirPath为空,创建空的versionFiles目录
            TarArchiveEntry emptyDirEntry = new TarArchiveEntry("versionFiles/");
            taos.putArchiveEntry(emptyDirEntry);
            taos.closeArchiveEntry();
        }

        // 如果logPath不为空,则添加日志文件到winfiles目录
        if (StringUtils.isNotBlank(logPath)) {
            addFilesToTarWithPrefix(logPath, "winFiles/", taos);
        } else {
            // logPath为空,创建空的winFiles目录
            TarArchiveEntry emptyDirEntry = new TarArchiveEntry("winFiles/");
            taos.putArchiveEntry(emptyDirEntry);
            taos.closeArchiveEntry();
        }
        taos.finish();
    }

    /**
     * 分割线:上面是获取目录生成指定的tgz压缩包文件,下面是拿到生成好的文件,通过浏览器下载下来
     */

    //压缩包名称
    String fileName = trainingProjectTask.getModelName() + "_" + trainingProjectTask.getVersionName() + ".tar.gz";

    // 读取TGZ文件准备下载
//    BufferedInputStream inputStream = cn.hutool.core.io.FileUtil.getInputStream(tarGzFilePath);

    // 使用FileInputStream打开文件流,然后通过BufferedInputStream装饰以提升读取效率
    try (FileInputStream fis = new FileInputStream(tarGzFilePath);
         BufferedInputStream inputStream = new BufferedInputStream(fis)) {

        /* 清空缓存 */
        response.reset();
        try {

            /* 获取响应流对象 */
            OutputStream output = response.getOutputStream();

            String userAgent = request.getHeader("USER-AGENT").toLowerCase();
            String codedFileName;

            if (userAgent != null && (userAgent.indexOf("Firefox") >= 0 || userAgent.indexOf("Chrome") >= 0 || userAgent.indexOf("Safari") >= 0)) {
                codedFileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            } else {
                codedFileName = java.net.URLEncoder.encode(fileName, "UTF-8");
            }

            // 设置浏览器跨域
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Cache-Control", "no-cache");

            /**
             * 调整为二进制的格式
             */
            response.setContentType("application/octet-stream");

            // 定义浏览器响应表头,顺带定义下载名
            response.setHeader("Content-Disposition", "attachment;filename=" + codedFileName);

            // 开放权限,让前端获取到
            response.setHeader("filename", codedFileName);
            response.setHeader("Access-Control-Expose-Headers", "filename");

            /**
             * 替换为循环读取和写入的代码,避免存在下载压缩包打开异常问题
             */
            int len;
            byte[] buffer = new byte[1024];
            while ((len = inputStream.read(buffer)) != -1) {
                output.write(buffer, 0, len);
                output.flush(); // 确保所有数据都被写入到输出流中
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 这里不再需要任何操作,因为所有的Closeable资源都在try-with-resources中声明了
        }
    } catch (FileNotFoundException e) {
        // 处理找不到文件的异常
        e.printStackTrace();
    }
}
第二种则在前端中直接使用 openWindows 方法进行文件下载

具体实现方式如下:

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.file.FileReader;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yr.common.util.BeanUtils;
import com.yr.common.util.FileUtil;
import com.yr.common.util.FrameUtils;
import com.yr.common.util.K8SUtil;
import com.yr.dataAnnotations.config.DatasetImageProperties;
import com.yr.dataAnnotations.config.ResourceAccessProperties;
import com.yr.dataAnnotations.config.SpringBootPlusProperties;
import com.yr.dataAnnotations.entity.*;
import com.yr.dataAnnotations.mapper.*;
import com.yr.dataAnnotations.param.TrainingProjectTaskPageParam;
import com.yr.dataAnnotations.service.TrainingProjectTaskService;
import com.yr.dataAnnotations.util.DictValueUtil;
import com.yr.dataAnnotations.vo.TrainingProjectTaskQueryVo;
import com.yr.newframe.core.common.exception.BusinessException;
import com.yr.newframe.core.common.service.impl.BaseServiceImpl;
import com.yr.newframe.core.core.pagination.PageInfo;
import com.yr.newframe.core.core.pagination.Paging;
import com.yr.newframe.core.core.util.FrameUtil;
import io.kubernetes.client.custom.Quantity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.*;

public String download(String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
    TrainingProjectTask trainingProjectTask = trainingProjectTaskMapper.selectById(id);

    //要压缩的模型文件目录路径: "D:\\Desktop\\testModel\\0e328d1b517643bc82ff9fa1f98614d6\\5994db1e98154505ae79825a8d547ec0";
    String sourceDirPath = model_dev_path + trainingProjectTask.getModelId() + File.separator + trainingProjectTask.getVersionId();

    //成功文件路径
    String logPath = trainingProjectTask.getLogPath();
    //生成文件格式后缀(zip: .zip)
    String tarGzFilePath = sourceDirPath + ".tar.gz";


    // 创建TarArchiveOutputStream并关联到GzipCompressorOutputStream,进而关联到文件输出流
    try (FileOutputStream fos = new FileOutputStream(tarGzFilePath);
         GzipCompressorOutputStream gos = new GzipCompressorOutputStream(fos);
         TarArchiveOutputStream taos = new TarArchiveOutputStream(gos)) {

        // 如果sourceDirPath不为空,则添加模型文件到versionfiles目录
        if (StringUtils.isNotBlank(sourceDirPath)) {
            addFilesToTarWithPrefix(sourceDirPath, "versionFiles/", taos);
        } else {
            // sourceDirPath为空,创建空的versionFiles目录
            TarArchiveEntry emptyDirEntry = new TarArchiveEntry("versionFiles/");
            taos.putArchiveEntry(emptyDirEntry);
            taos.closeArchiveEntry();
        }

        // 如果logPath不为空,则添加日志文件到winfiles目录
        if (StringUtils.isNotBlank(logPath)) {
            addFilesToTarWithPrefix(logPath, "winFiles/", taos);
        } else {
            // logPath为空,创建空的winFiles目录
            TarArchiveEntry emptyDirEntry = new TarArchiveEntry("winFiles/");
            taos.putArchiveEntry(emptyDirEntry);
            taos.closeArchiveEntry();
        }
        taos.finish();
    }

    //复制生成好的tgz文件,重新复制一个给项目下载
    File tarGzFile = new File(tarGzFilePath);
    String newTarGzFileUrl = "";
    // 检查TGZ文件是否已存在
    if (tarGzFile.exists()) {
        //新的压缩包名称
        String newFileName = trainingProjectTask.getModelName() + "_" + trainingProjectTask.getVersionName() + ".tar.gz";
        
        // 服务器容器固定配置映射路径:hui_data/hui_soft/data/kiues/file/images/upload/
        String baseUploadPath = springBootPlusProperties.getUploadPath();
        
        // modeldev目录路径
        String modelDevPath = baseUploadPath  + "model_dev";
        
        // 根据ModelId创建的子目录路径
        String modelSubDirPath = modelDevPath + File.separator + trainingProjectTask.getModelId();

        // 检查并创建modeldev目录(如果不存在)
        File modelDevDir = new File(modelDevPath);
        if (!modelDevDir.exists()) {
            modelDevDir.mkdir(); // 创建modeldev目录
            System.out.println("Directory 'model_dev' created successfully.");
        }

        // 检查并创建ModelId对应的子目录(如果不存在)
        File modelSubDir = new File(modelSubDirPath);
        if (!modelSubDir.exists()) {
            modelSubDir.mkdir(); // 创建ModelId子目录
            System.out.println("Directory for ModelId '" + trainingProjectTask.getModelId() + "' created successfully.");
        }

        // 构建新的文件路径
        String newTarGzFilePath = modelSubDirPath + File.separator + newFileName;
        //返回可读取下载地址示例:/hui_data/hui_soft/data/kiues/file/images/upload/model_dev/XXXXXX/XXX.tgz
        newTarGzFileUrl = newTarGzFilePath.replace(springBootPlusProperties.getUploadPath(), resourceAccessProperties.getResourceAccessUrl());
        try {
            // 复制并重命名文件
            Files.copy(tarGzFile.toPath(), new File(newTarGzFilePath).toPath(), StandardCopyOption.REPLACE_EXISTING);
            System.out.println("复制生成提供下载连接tgz文件完成: " + newTarGzFilePath);
        } catch (IOException e) {
            System.err.println("Error occurred while copying or renaming the TGZ file: " + e.getMessage());
        }
    } else {
        System.out.println("TGZ file does not exist.");
    }
    return newTarGzFileUrl;
总结:

以上两种方式均为下载功能的实现,具体可参考自己项目需求进行灵活选择。

如果以上代码实现存在问题,处理BUG思路如下:

  1. 代码断点先排错确保代码逻辑正常,生成文件在服务器指定目录中
  2. 上到服务器文件,在服务器上解压该文件,确保压缩生成文件内容正常
  3. 以上生成压缩文件、解压压缩文件在服务器上都没问题,则要考虑是否浏览器域和请求头进行文件下载存在问题,有前端的兄弟可以让前端看看

本人在使用第一种方法中碰到以上问题:正常在本地postMan进行测试可以生成对应压缩包文件且可以解压,发布到线上下载出来压缩包,解压提示文件内容损坏,经过以上处理逻辑发现可能是浏览器请求下载部分存在问题,尝试很多种方式均无法解决该问题,(由于需求比较赶就没有深入研究具体原因) 后面换成第二种方式来实现该效果(如果有兄弟也是同样问题且解决了可以回复告诉怎么处理的这个问题 感谢!!!!!)

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值