springboot响应文件流文件给浏览器+前端下载

springboot响应文件流文件给浏览器+前端下载

在这里插入图片描述
在这里插入图片描述

1.controller:

@Api(tags = {"【样本提取系统】-api"})
@RestController("YbtqYstbtqController")
@RequiredArgsConstructor
@RequestMapping("/ybtq-ystbtq")
@Slf4j
public class YbtqYstbtqController {

private final YbService ybService;

@ApiOperation(value = "【样本集管理】-样本集导出", notes = "export", produces = "application/octet-stream")
    @PostMapping("/ybgl-set-cloud-download")
    @OpLog("【样本集管理】-样本集导出")
    public void ybGlYbSetDownload(@RequestBody List<String> pkIds, HttpServletResponse response) throws Exception {
        FileDownloadVo fileDownloadVo = tileSetService.ybGlYbSetDownload(pkIds);
        if (null != fileDownloadVo && fileDownloadVo.getFileStream() != null) {
            try (InputStream inputStream = fileDownloadVo.getFileStream();
                 OutputStream outputStream = response.getOutputStream()) {
                //先将字符串以UTF-8转换成字节数组,再将字节数组以ISO-8859-1转换字符串(标准)
                String fileName = new String(fileDownloadVo.getFileName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName,"utf-8"));
                response.setContentType(fileDownloadVo.getContentType());
                response.setCharacterEncoding("UTF-8");
                // 响应文件给浏览器
                IOUtils.copy(inputStream, outputStream);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else{
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(JSONUtil.toJsonStr(Result.error("所选样本集无有效文件!")));
        }
    }

}

文件下载对象FileDownloadVo:

/**
 * 文件下载对象
 */
@Data
public class FileDownloadVo {
    /**
     * 文件名称
     */
    @ApiModelProperty("文件名称")
    private String fileName;
    /**
     * 文件content-type
     */
    @ApiModelProperty("content-type")
    private String contentType;

    /**
     * 文件流
     */
    @ApiModelProperty("文件流")
    private InputStream fileStream;
}

2.serviceImpl:

@Override
    public FileDownloadVo ybGlYbSetDownload(List<String> pkIds) {
        // 文件根目录
        FileDownloadVo ret = new FileDownloadVo();
        // 获取样本集信息
        List<TileSet> ybSetList = tileSetMapper.selectList(new LambdaQueryWrapper<TileSet>().in(TileSet::getPkId, pkIds));
        // 查询到的样本集,影像压缩成一个zip
        if (ybSetList.size() > 0) {
            try {
                // 获取当前日期并格式化为 yyyyMMdd
                String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date());
                // 创建临时 ZIP 文件
                File tempZipFile = Files.createTempFile("样本集_" + currentDate + "_", ".zip").toFile();

                // 创建临时根文件夹用于存放样本文件夹
                File tempRootDir = Files.createTempDirectory("样本集_" + currentDate + "_").toFile();

                for (TileSet tileSet : ybSetList) {
                    // 为每个样本集创建一个文件夹
                    File sampleDir = new File(tempRootDir, tileSet.getTileName() + "_" + tileSet.getPkId());
                    sampleDir.mkdirs();

                    // 获取文件复制到临时目录下
                    setTitleSetFile(tileSet, sampleDir);
                }
                try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(tempZipFile))) {
                    // 遍历 tempRootDir 下的所有子文件夹并添加到 ZIP 文件中
                    File[] subDirs = tempRootDir.listFiles();
                    if (subDirs != null) {
                        for (File subDir : subDirs) {
                            UnZipUtils.zipFiles(subDir, subDir.getName(), zipOut); // 将每个子文件夹递归添加到 ZIP
                        }
                    }
                } finally {
                    // 删除临时文件夹及其内容
                    UnZipUtils.deleteDirectory(tempRootDir);
                }

                try {
                    // 将 ZIP 文件转为 FileInputStream
                    ret.setFileStream(new FileInputStream(tempZipFile));
                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 异步任务:300秒后删除文件
                UnZipUtils.deleteAsyncFiles(tempZipFile);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        ret.setContentType("application/zip");
        ret.setFileName(String.valueOf(new Date().getTime()));
        return ret;
    }


// 获取文件复制到临时目录下
    private void setTitleSetFile(TileSet tileSet, File sampleDir) {
        String tileSetPath = tileSet.getTilePath();

        boolean isTileSetPath = tileSetPath != null && !Objects.equals(tileSetPath, "");
        if (isTileSetPath) {
            // 获取样本切片file
            List<File> tileSetFileList = UnZipUtils.getFileByImagePaths(tileSetPath);
            for (File file : tileSetFileList) {
                try {
                    Files.copy(file.toPath(), new File(sampleDir, file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
                } catch (Exception e) {
                    System.out.print("前时项影像文件路径无效:" + file.getPath());
                }
            }
        }
    }

/**
     * 辅助方法,用于从ZIP输入流中提取文件
     *
     * @param zis ZIP输入流
     * @param filePath 文件的完整路径
     * @throws IOException 如果发生I/O错误
     */
    public static void extractFile(ZipInputStream zis, String filePath) throws IOException {
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        byte[] bytesIn = new byte[4096];
        int read = 0;
        while ((read = zis.read(bytesIn)) != -1) {
            bos.write(bytesIn, 0, read);
        }
        bos.close();
    }


public static void zipFiles(File fileToZip, String fileName, ZipOutputStream zipOut) throws IOException {
        if (fileToZip.isHidden()) return;

        if (fileToZip.isDirectory()) {
            if (fileName.endsWith("/")) {
                zipOut.putNextEntry(new ZipEntry(fileName));
                zipOut.closeEntry();
            } else {
                zipOut.putNextEntry(new ZipEntry(fileName + "/"));
                zipOut.closeEntry();
            }

            File[] children = fileToZip.listFiles();
            for (File childFile : children) {
                zipFiles(childFile, fileName + "/" + childFile.getName(), zipOut);
            }
            return;
        }

        try (FileInputStream fis = new FileInputStream(fileToZip)) {
            ZipEntry zipEntry = new ZipEntry(fileName);
            zipOut.putNextEntry(zipEntry);
            byte[] bytes = new byte[1024];
            int length;
            while ((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
        }
    }

public static void deleteDirectory(File file) {
        File[] contents = file.listFiles();
        if (contents != null) {
            for (File f : contents) {
                deleteDirectory(f);
            }
        }
        file.delete();
    }

// 异步任务-删除临时文件
    public static void deleteAsyncFiles(File tempZipFile) {
        executorService.submit(() -> {
            try {
                TimeUnit.SECONDS.sleep(300);
                Files.deleteIfExists(tempZipFile.toPath());
                System.out.println("临时压缩包已删除:" + tempZipFile.toPath());
            } catch (Exception e) {
                System.err.println("临时压缩包删除失败:" + e.getMessage());
            }
        });
    }

public static List<File> getFileByImagePaths(String imagePaths) {
        List<File> ret = new ArrayList<>();
        if (imagePaths != null && !imagePaths.equals("")) {
            List<String> preImagePathList = Arrays.asList(imagePaths.split(","));
            if (preImagePathList.size() > 0) {
                for (String preImagePath : preImagePathList) {
                    // 根据path获取文件
                    File file = new File(preImagePath);
                    ret.add(file);
                }
            }
        }
        return ret;
    }

3.前端下载:


/**
   * 【样本集管理】-样本集导出
   */
  ybGlYbSetDownload(context = undefined, params = undefined, fileName) {
    const url = `${this.interfaceUrl}/ybtq-ystbtq/ybgl-set-cloud-download`;
    return ajaxHelperInstance.downloadFilePost(url, fileName, {}, params);
  }

async downloadFilePost(url, fileName, urlParam, data = {}, requestMethod = 'POST', vueContext = undefined, spinName = 'spinning', useStreamSaver = false) {
        url = this.prepareUrl(url, {...urlParam, ...data});
        data = convertDateParam(data);
        setVueContextSpinState(vueContext, spinName);
        const requestOptions = this.setupRequestOptions(requestMethod, data);

        try {
            let response;
            if (useStreamSaver) {
                await this.downloadWithStream(url, fileName, requestOptions);
            } else {
                response = await axios({url, ...requestOptions});
                await this.handleResponse(response, fileName);
            }
        } catch (error) {
            this.handleError(error);
        } finally {
            resetVueContextSpinState(vueContext, spinName);
        }
    }


prepareUrl(url, params) {
        const reqData = _.map(params, (val, prop) => ({name: prop, value: val}));
        const queryString = abp.utils.buildQueryString(reqData);
        return url + queryString;
    }

/**
 * 转换时间参数,主要处理表单传入参数含有moment对象的问题
 * @param obj 传入对象
 * @constructor
 */
function convertDateParam(obj) {
    _.each(obj, (val, prop) => {
        if (val instanceof moment) {
            obj[prop] = (val as any).format('YYYY-MM-DD HH:mm:ss');
        }

        if (val instanceof Date) {
            obj[prop] = moment(val)
                .format('YYYY-MM-DD HH:mm:ss');
        }
    });
    return obj;
}


function setVueContextSpinState(vueContext, spinName: string) {
    if (vueContext && vueContext[spinName] != undefined) {
        vueContext[spinName] = true;
    }
}


setupRequestOptions(method, data = null) {
        const headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + window.abp.auth.getToken(),
        };
        return {
            method: method,
            headers: headers,
            responseType: 'blob',
            data: data ? JSON.stringify(data) : undefined,
        };
    }

/**
     * 通过fetch Api进行流式下载
     * @param url 下载地址
     * @param fileName 文件名称
     * @param options 下载选项
     */
    async downloadWithStream(url, fileName, options: any = {}) {
        const {timeout = 1000 * 60 * 180} = options;
        const response = await this.fetchWithTimeout(url, options, timeout);
        if (!response.ok) {
            notification.warning({
                message: '提示',
                description: '下载失败',
            });
            throw new Error('下载失败');
        }
        const fileStream = StreamSaver.createWriteStream(fileName);
        const writer = fileStream.getWriter();
        if (response.body.pipeTo) {
            writer.releaseLock();
            return response.body.pipeTo(fileStream);
        }

        const reader = response.body.getReader();
        const pump = () =>
            reader
                .read()
                .then(({value, done}) => (done ? writer.close() : writer.write(value).then(pump)));

        return pump();
    }

async handleResponse(response, fileName) {
        const contentType = response.headers['content-type'];
        console.log(contentType);
        if (contentType && contentType.includes('application/json')) {
            const reader = new FileReader();
            reader.onload = () => {
                try {
                    const jsonData = JSON.parse(reader.result as string);
                    if (jsonData && jsonData.message) {
                        notification.error({
                            message: '提示',
                            description: jsonData.message,
                        });
                    }
                } catch (error) {
                    console.error('解析json失败:', error);
                }
            };
            reader.readAsText(response.data);
        } else {
            const blob = new Blob([response.data]);
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);
        }
    }


handleError(error) {
        console.error('下载失败:', error);
    }


function resetVueContextSpinState(vueContext, spinName: string) {
    if (vueContext && vueContext[spinName] == true) {
        vueContext[spinName] = false;
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/905397.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DAY67WEB 攻防-Java 安全JNDIRMILDAP五大不安全组件RCE 执行不出网

知识点&#xff1a; 1、Java安全-RCE执行-5大类函数调用 2、Java安全-JNDI注入-RMI&LDAP&高版本 3、Java安全-不安全组件-Shiro&FastJson&JackJson&XStream&Log4j Java安全-RCE执行-5大类函数调用 Java中代码执行的类&#xff1a; Groovy Runti…

vue下载安装

目录 vue工具前置要求&#xff1a;安装node.js并配置好国内镜像源下载安装 vue 工具 系统&#xff1a;Windows 11 前置要求&#xff1a;安装node.js并配置好国内镜像源 参考&#xff1a;本人写的《node.js下载、安装、设置国内镜像源&#xff08;永久&#xff09;&#xff…

书生实战营第四期-第四关 玩转HF/魔搭/魔乐社区

一、任务1&#xff1a;模型下载 使用魔搭社区平台下载文档中提到的模型 1.创建开发机 2.环境配置 # 激活环境 conda activate /root/share/pre_envs/pytorch2.1.2cu12.1# 安装 modelscope pip install modelscope -t /root/env/maas pip install numpy1.26.0 -t /root/env/m…

【Blender】 学习笔记(一)

文章目录 参考概念原点 Origin游标 轴心点坐标操作默认快捷键两个比较好用的功能渲染器元素不可选&#xff08;防止误选&#xff09;关联材质 参考 参考b站视频&#xff1a;【Kurt】Blender零基础入门教程 | Blender中文区新手必刷教程(已完结) 概念 模型、灯光、摄像机 原点…

Java中的反射(Reflection)

先上两张图来系统的看一下反射的作用和具体的实现方法 接下来详细说一下反射的步骤以及之中使用的方法&#xff1a; 获取Class对象&#xff1a; 要使用反射&#xff0c;首先需要获得一个Class对象&#xff0c;该对象是反射的入口点。可以通过以下几种方式获取Class对象&#x…

号码认证是什么意思?有什么用?

随着通信环境越来越复杂&#xff0c;各种骚扰、推销电话层出不穷。许多企业为了取信于客户&#xff0c;提高电话的接听率&#xff0c;纷纷选择了申请号码认证&#xff0c;试图通过这种方法来与客户建立更加高效的沟通。 不可否认&#xff0c;这种方法是极其有效的。号码认证可…

Android 圆形进度条CircleProgressView 基础版

一个最基础的自定义View 圆形进度条&#xff0c;可设置背景色、进度条颜色&#xff08;渐变色&#xff09;下载进度控制&#xff1b;可二次定制度高&#xff1b; 核心代码&#xff1a; Overrideprotected void onDraw(NonNull Canvas canvas) {super.onDraw(canvas);int mW g…

Java基础0-Java概览

Java概览 一、Java的主要特性 Java 语言是简单的&#xff1a; Java 丢弃了 C 中很少使用的、很难理解的、令人迷惑的那些特性&#xff0c;如操作符重载、多继承、自动的强制类型转换。特别地&#xff0c;Java 语言不使用指针&#xff0c;而是引用。并提供了自动分配和回收内存…

信号(四)【信号处理与捕捉】

目录 1. 信号的处理1.1 内核态 && 用户态1.2 进程地址空间第三弹1.1 内核态 && 用户态 (续) 2. 信号捕捉 1. 信号的处理 我们一直在说&#xff0c;进程收到信号了&#xff0c;可能会因为各种原因无法即使处理信号&#xff0c;而后选择一个合适的时机去处理。所…

Kafka 与传统 MQ 消息系统之间有三个关键区别?

大家好&#xff0c;我是锋哥。今天分享关于【Kafka 与传统 MQ 消息系统之间有三个关键区别&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; Kafka 与传统 MQ 消息系统之间有三个关键区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 …

基于局部近似的模型解释方法

在机器学习领域中&#xff0c;模型解释性是一个越来越重要的议题&#xff0c;尤其是在复杂的深度学习模型和非线性模型广泛应用的今天。解释性不仅帮助我们理解模型的决策逻辑&#xff0c;还能提高模型在敏感领域&#xff08;如医疗诊断、金融分析&#xff09;中的可信度。基于…

img 标签的 object-fit 属性

设置图片固定尺寸后&#xff0c;可以通过 object-fit 属性调整图片展示的形式 object-fit: contain; 图片的长宽比不变&#xff0c;相应调整大小。 object-fit: cover; 当图片的长宽比与容器的长宽比不一致时&#xff0c;会被裁切。 object-fit: fill; 图片不再锁定长宽…

推荐一款功能强大的文字处理工具:Atlantis Word Processor

Atlantis word proCEssor是一款功能强大的文字处理工具。该软件可以让用户放心的去设计文档&#xff0c;并且软件的界面能够按用户的意愿去自定义&#xff0c;比如工具栏、字体选择、排版、打印栏等等&#xff0c;当然还有更多的功能&#xff0c;比如你还可以吧软件界面中的任何…

【算法】【优选算法】双指针(上)

目录 一、双指针简介1.1 对撞指针&#xff08;左右指针&#xff09;1.2 快慢指针 二、283.移动零三、1089.复写零3.1 双指针解题3.2 暴力解法 四、202.快乐数4.1 快慢指针4.2 暴力解法 五、11.盛最多⽔的容器5.1 左右指针5.2 暴力解法 一、双指针简介 常⻅的双指针有两种形式&…

Pandas DataFrame学习补充

1. 从字典创建&#xff1a;字典的键成为列名&#xff0c;值成为列数据。 import pandas as pd# 通过字典创建 DataFrame df pd.DataFrame({Column1: [1, 2, 3], Column2: [4, 5, 6]}) 2. 从列表的列表创建&#xff1a;外层列表代表行&#xff0c;内层列表代表列。 df pd.Da…

二、Go快速入门之数据类型

&#x1f4c5; 2024年4月27日 &#x1f4e6; 使用版本为1.21.5 Go的数据类型 &#x1f4d6;官方文档&#xff1a;https://go.dev/ref/spec#Types 1️⃣ 布尔类型 ⭐️ 布尔类型只有真和假,true和false ⭐️ 在Go中整数0不会代表假&#xff0c;非零整数也不能代替真&#…

Springboot整合原生ES依赖

前言 Springboot整合依赖大概有三种方式&#xff1a; es原生依赖&#xff1a;elasticsearch-rest-high-level-clientSpring Data ElasticsearchEasy-es 三者的区别 1. Elasticsearch Rest High Level Client 简介: 这是官方提供的 Elasticsearch 客户端&#xff0c;支持…

Spark,Anconda在虚拟机实现本地模式部署

如果想要了解模式的概念部分&#xff0c;以及作用请看&#xff1a; Spark学习-CSDN博客 一.在虚拟机安装spark cd /opt/modules 把Anconda和Spark安装包拖拽进去&#xff1a; 解压&#xff1a; tar -zxf spark-3.1.2-bin-hadoop3.2.tgz -C /opt/installs 重命名&#x…

Javaee:阻塞队列和生产者消费者模型

文章目录 什么是阻塞队列java中的主要阻塞队列生产者消费者模型阻塞队列发挥的作用解耦合削峰填谷 模拟实现阻塞队列put方法take方法生产者消费者模型 什么是阻塞队列 阻塞队列是一种支持阻塞操作的队列&#xff0c;在多线程中实现通线程之间的通信协调的特殊队列 java中的主…

[网络协议篇] UDP协议

文章目录 1. 简介2. 特点3. UDP数据报结构4. 基于UDP的应用层协议5. UDP安全性问题6. 使用udp传输数据的系统就一定不可靠吗&#xff1f;7. 基于UDP的主机探活 python实现 1. 简介 User Datagram Protocol&#xff0c;用户数据报协议&#xff0c;基于IP协议提供面向无连接的网…