SpringBoot多文件压缩包下载(多附件zip格式)_springboot下载多个文件-程序员宅基地

技术标签: spring boot  java  后端  


前言 : 此 Demo 为 Windows 环境下演示,部署到服务器的话路径需改成服务器的路径。

此文基于SpringBoot,实现多文件的打包下载,废话不多说直接上代码。

最近注意到这篇打包下载频繁的被大家访问,觉得大家应该做的项目大部分都有这个需求,今天把它完善一下更加通俗易懂,如果有用请点赞收藏!!


一、自定义工具类DownLoadZipUtil

import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author 码不多
 * @version 1.0
 * @description: 下载打包Zip工具类
 * @date 2021/8/20 13:47
 */
public class DownLoadZipUtil {
    
    /**
     * 功能描述:  将文件打包的方法,需要传一个压缩路径,和一个文件,一次只将一个文件写入压缩包
     * @author 码不多
     * @date 2021/8/21
     * @param filePath 文件路径
       @param zipOut 压缩流
       @param realFileName 真实的文件名
      * @return void
     */
    public static void fileToZip(String filePath,ZipOutputStream zipOut,String realFileName) throws IOException {
    
        // 需要压缩的文件
        File file = new File(filePath);
        //创建文件输入流
        FileInputStream fileInput = new FileInputStream(filePath);
        // 缓冲
        byte[] bufferArea = new byte[1024 * 10];
        BufferedInputStream bufferStream = new BufferedInputStream(fileInput, 1024 * 10);
        // 将当前文件作为一个zip实体写入压缩流,realFileName代表压缩文件中的文件名称
        zipOut.putNextEntry(new ZipEntry(realFileName));
        int length = 0;
        // 写操作
        while ((length = bufferStream.read(bufferArea, 0, 1024 * 10)) != -1) {
    
            zipOut.write(bufferArea, 0, length);
        }
        //关闭流
        fileInput.close();
        // 需要注意的是缓冲流必须要关闭流,否则输出无效
        bufferStream.close();
        // 压缩流不必关闭,使用完后再关
    }
}

二、Dao层分析与sql

注意 : 打包下载文件,一般都是下载多个文件,而这多个文件一定有一个唯一标识与之对应,我们在前端要拿到这个唯一标识传递到后端,如下面的公告名称或者id。

在这里插入图片描述在这里插入图片描述
如图这是我存储公告的数据库表 id为主键列为一对多的一,也就是发布的公告 id 为 17
在这里插入图片描述
这张为存储上传文件路径的数据表这里为一对多的多,fid对应的是公告表的主键列id 17
在这里插入图片描述

了解了字段对应关系来看mapper.xml的映射sql,对应上面的例子就是查询出所有fid为17的附件,返回文件路径等信息。

    <!--模板批量下载根据id和外键id,查询路径-->
    <select id="findFileTempById" parameterType="int" resultType="com.mabuduo.entity.FileTemp">
        SELECT xc_xc_filetemp.id,xc_xc_filetemp.dir_path,xc_xc_filetemp.file_newName,
        xc_xc_filetemp.file_name,xc_xc_message.about
        FROM xc_xc_filetemp,xc_xc_message
        WHERE xc_xc_filetemp.fid = #{mid}
        AND xc_xc_filetemp.fid = xc_xc_message.id;
    </select>

这里是用的实体类来接收返回的列表数据

mapper层代码(DAO)
    /**
     * 功能描述: 批量下载模板接口,查询模板名称显示前端页面
     * @author 码不多
     * @date 2021/8/20
     * @param mid message的id值
     * @return java.util.List<com.mabuduo.entity.FileTemp>
     */
    List<FileTemp>  findFileTempById(@Param("mid") int mid);

三、Service层代码

service

    /**
     * 功能描述: 批量下载模板接口,查询公告信息显示前端
     * @author 码不多
     * @date 2021/8/20
     * @param mid message的id值
     * @return java.util.List<com.mabuduo.entity.FileTemp>
     */
    List<FileTemp> findFileTempById(int mid);

serviceimpl

    //DI注入mapper层
    @Autowired
    private FileTempMapper fileTempMapper;
    /**
     * 功能描述: 批量下载模板接口,查询公告信息显示前端
     * @author 码不多
     * @date 2021/8/20
     * @param mid
     * @return java.util.List<com.mabuduo.entity.FileTemp>
     */
    @Override
    public List<FileTemp> findFileTempById(int mid) {
    
        return fileTempMapper.findFileTempById(mid);
    }

三、Controller层代码

注意 : 文件的打包下载这里用到了临时路径,下面只需要关注方法ZipTempDownLoad即可,下面的代码实际需根据自己的逻辑需求去写。

/**
 * @author 码不多
 * @version 1.0
 * @description: 打包下载模板控制器
 * @date 2021/8/19 13:53
 */
@CrossOrigin
@RestController
@RequestMapping("/sys")
@Slf4j
@Transactional
public class FileTempDownLoadController {
    
    //定义存储压缩包的临时路径,这里我写在了配置文件中方便修改
    @Value("${zipFilePath.path}")
    private String zipFilePath;

    //DI
    @Autowired
    private FileTempDownLoadServiceImpl fileTempDownLoadService;
    /**
     * 功能描述: 压缩包下载模板
     * @author 码不多
     * @date 2021/8/20
     * @param id 模板公告id 
     * @param response 响应
     * @return void
     */
    @Transactional
    //@LogAnnotationMethod(module = "下载模板",operator = "打压缩包下载模板")
    @RequestMapping(path = "/downloadTempZip",method={
    RequestMethod.POST,RequestMethod.GET})
    public void ZipTempDownLoad(@RequestParam("id") int id, HttpServletResponse response){
    
        //通过id获取路径
        List<FileTemp> fileTempById = fileTempDownLoadService.findFileTempById(id);

        //创建集合存储路径
        ArrayList<String> pathList = new ArrayList<String>();

        //遍历集合取出路径
        for (FileTemp fileTemp:fileTempById){
    
            //获取到真实的文件名
            String realFileName = fileTemp.getFile_name();
            //拿到文件目录路径
            String dir_path = fileTemp.getDir_path();
            //拿到文件名
            String file_newName = fileTemp.getFile_newName();
            //拼接成全路径| 将真实的文件名也拼接进去,通过/进行分割,下面进行拆分,否则文件的真实名称将获取不到
            String realPath = dir_path+file_newName+"/"+realFileName;
            //向集合中添加路径
            pathList.add(realPath);
        }

	    //这里我是需要公告名称给压缩包命名的所以获取公告名称。	
        //获取公告名称,用于给压缩包命名,因为它们一个共用一个公告名所以随便取集合中的一个就可以
        String packageName = fileTempById.get(0).getAbout()+".zip";

        //判断集合是否有路径
        if (pathList.size() != 0) {
    
            // 压缩输出流,包装流,将临时文件输出流包装成压缩流,将所有文件输出到这里,打成zip包
            ZipOutputStream zipOut = null;
            try {
    
                zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath+packageName));
            } catch (FileNotFoundException e) {
    
                e.printStackTrace();
            }
            // 循环调用压缩文件方法,将一个一个需要下载的文件打入压缩文件包
            for (String path : pathList) {
    
                //将路径进行拆分,将上面拼接的真实文件名拆分出来作为参数传递进去
                int lastIndexOf = path.lastIndexOf("/")+1;
                String realFileName = path.substring(lastIndexOf, path.length());
                //将路径进行拆分,获取存储路径
                String realPath = path.substring(0, lastIndexOf - 1);
                try {
    
                    //调用工具类方法,传递路径和压缩流,压缩包文件的名字
                    DownLoadZipUtil.fileToZip(realPath,zipOut,realFileName);
                } catch (IOException e) {
    
                    e.printStackTrace();
                }
            }

            try {
    
                // 压缩完成后,关闭压缩流
                zipOut.close();
            } catch (IOException e) {
    
                e.printStackTrace();
            }


            //提高作用域
            String fileName = null;
            try {
    
                //下载名称并转为ISO-8859-1格式,解决中文乱码
                fileName = new String(packageName.getBytes("UTF-8"), "ISO8859-1");
                //设置内容内容型应用下载,设置字符集
                response.setContentType("application/x-download;charset=utf-8");
                //告诉客户端该文件不是直接解析而是以附件的形式打开(下载)
                response.setHeader("Content-Disposition", "attachment;filename="+fileName);
            } catch (UnsupportedEncodingException e) {
    
                e.printStackTrace();
            }

            //提高作用域
            ServletOutputStream outputStream = null;
            FileInputStream inputStream = null;
            try {
    
                //该流不可以手动关闭,手动关闭下载会出问题,下载完成后会自动关闭
                outputStream = response.getOutputStream();
                inputStream = new FileInputStream(zipFilePath+packageName);
            } catch (IOException e) {
    
                e.printStackTrace();
            }

            try {
    
                //文件下载,复制
                IOUtils.copy(inputStream, outputStream);
            } catch (IOException e) {
    
                e.printStackTrace();
            }

            // 关闭输入流
            try {
    
                if (inputStream!=null){
    
                    inputStream.close();
                }
            } catch (IOException e) {
    
                e.printStackTrace();
            }

            //下载完成,删掉zip包
            File fileTempZip = new File(zipFilePath+packageName);
            fileTempZip.delete();
        }
    }
}

四、前端部分代码,此Demo前端用的vue,根据你自己使用的去写就可以了,此处仅供参考。

<template slot-scope="scope">                                                         	
<!--传递行信息-->
  <el-button type="primary"  size="mini" @click="ZipDownLoad(scope.row)">打包下载</el-button>
                      
</template>

js

       /**
         * 功能描述: 打包下载模板
         * @author 码不多
         * @return
         */
        ZipDownLoad(row){
    
            axios({
    
                method: 'GET',
                url: 'http://localhost:8081/sys/downloadTempZip?',
                params: {
    
                    id:row.id
                },
                responseType: 'blob'
            }).then(response => {
    
                //创建文件流对象
                let blob = new Blob([response.data], {
     type: 'application/zip' })
                //判断后端传递过来的流是否为不为空
                if (blob.size!==0){
    
                    //获取heads中的filename文件名,这种方式在跨域的情况下获取不到默认响应头外的信息。
                    /*let fileName = response.headers["Content-Disposition"].split(";")[1].split("filename=")[1];*/
                    let url = window.URL.createObjectURL(blob) // 创建新的URL表示指定的blob对象
                    //设置打包的名字
                    let fileName = row.about
                    //创建a标签元素节点
                    let a = document.createElement('a')
                    a.style.display = 'none'
                    a.href = url // 指定下载链接
                    a.download = fileName // 指定下载文件名
                    a.click()
                    URL.revokeObjectURL(a.href) // 释放URL对象
                    /*这样下载文件名为乱码所以不再采用 window.location.href = url*/
                }else{
    
                    this.$message.warning("当前公告下暂无上传的附件!")
                }
            }).catch(error => this.$message.error(error) )
        }

五、效果展示

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


# 总结 因为比较忙的关系,代码不再进行拆分讲解,不懂可以评论解答或者直接加微信 ldq122522一起交流技术分享资料。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_46417933/article/details/121156712

智能推荐

MQ的概念和RabbitMQ知识点(无代码)-程序员宅基地

文章浏览阅读1.2w次,点赞7次,收藏76次。MQ全称是MessageQueue(消息队列),是保存消息在传输过程中的一种容器,既是存储消息的一种中间件。多是应用在分布式系统中进行通信的第三方中间件,如下图所示,发送方成为生产者,接收方称为消费者。............_mq

如何做好Bug分析-程序员宅基地

文章浏览阅读1.5k次,点赞47次,收藏18次。Bug分析是QA的一项主要技能,需要针对项目中遇到的经典问题进行分类分析, 直达问题本质。 并且能够给团队其他项目或者成员起到典型的借鉴作用。 当然也有一些非常经典的问题可以进行技术深挖, 以供参考。 个人认为比较典型的「Bug分析」是stackoverflow, 当然, 一个完善的bug分析库, 可以进行问题分类总结。 对于测试新人是有很大的帮助的。本质上, 在测试领域很多问题是可重现可整理可规避的。另外, bug分析本身是为了拓宽每个人的认知边界, 缩小团队间的乔哈里窗以达到最佳的合作状态。一个「好的B

H5020NL PULSE 50PIN千兆四口网络变压器 HQST H85001S建议IC配置型号_4口网络变压器-程序员宅基地

文章浏览阅读800次。HQST导读:PULSE普思是网络通讯行业中龙头企业之一,其中网络变压器产品大都由国内代工厂代为生产,H5020NLHX5020NL千兆四口网络变压器是普思公司经典老牌产品,相对整个市场用量不是很大,集中生产约一月20万颗左右……PULSE普思是网络通讯行业中龙头企业之一,其中网络变压器产品大都由国内代工厂代为生产,H5020NLHX5020NL千兆四口网络变压器是普思公司经典老牌产品,相对整个市场用量不是很大,集中生产约一月20万颗左右,……PULSE H5020NL千兆网络变压器对应HQS._4口网络变压器

D20 EME 支持2k MAC地址表-程序员宅基地

文章浏览阅读242次,点赞3次,收藏9次。交换机,壳体采用镀锌钢板,结构紧凑,支持八个百兆端口,可配置一至四个百兆光纤端口。两路冗余电源设计,支持4pin可插拔端子,交直流通用,同时提供电源防接保护及过压、欠压保护,极大提升产品工作的稳定性。2.支持两路冗余电源设计,4pin可插拔端子,支持12~36V宽电压输入,交直流通用,同时提供电源防反接保护及过压、欠压保护,极大提升产品工作的稳定性。4.-40℃~75℃工作温度,-40~85℃存储温度,在极端气象条件下也能安全运行。8.支持IEEE802.3,IEEE802.3u,IEEE802.3x。

阿昌教你如何使用通义灵码-程序员宅基地

文章浏览阅读946次。Hi,我是阿昌,今天教你如何使用通义灵码。_通义灵码

老版本NDK下载列表(Android官网)_ndk 老颁布-程序员宅基地

文章浏览阅读2.3w次。我们在开发或编译旧版本NDK项目时,需要使用一些老版本的NDK,在这里提供了旧版NDK的列表及下载链接_ndk 老颁布

随便推点

网关、安全网关?与防火墙的区别(2),网络安全多线程断点续传-程序员宅基地

文章浏览阅读640次,点赞6次,收藏18次。网关是一个大的概念,没有特指是什么设备,很多设备都可以做网关,普通的PC机也能做,常用的网关设备是路由器。网关的作用主要是用来连接两个不同的网络,比如可以连接两个IP地址不相同的网络,或连接两个操作系统不同的网络,如WINDOWS与LINUX互连,或连接两个网络协议不同的网络,如TCP/IP与IPX.或拓扑结构不同的网络,如以太网和令牌环网。总之网关是一种中间媒介。而防火墙也可以做网关,但它的主要做用只是用来防病毒或防黑客,网关只算是防火墙的一个功能。网关与防火墙的区别。

解决:ModuleNotFoundError: No module named ‘pymysql’_modulenotfounderror: no module named 'pymysql-程序员宅基地

文章浏览阅读4.1k次,点赞42次,收藏34次。背景在使用之前的代码时,报错: Traceback (most recent call last): File "xxx", line xx, in import pymysql ModuleNotFoundError: No module named 'pymysql'翻译:```追溯(最近一次通话):文件“xxx”,第xx行,在导入pymysqlModuleNotFoundError:没有名为“pymysql”的模块```原因 ......_modulenotfounderror: no module named 'pymysql

android读取生成excel,Android创建与读取Excel-程序员宅基地

文章浏览阅读275次。1 import java.io.File;23 import java.io.IOException;45 import java.util.Locale;6789 import jxl.CellView;1011 import jxl.Workbook;1213 import jxl.WorkbookSettings;1415 import jxl.format.UnderlineStyle;..._android excel生成读取类

VS2015离线安装 安装包损坏或丢失_vs2015离线版csdn-程序员宅基地

文章浏览阅读4.3w次,点赞16次,收藏126次。1、去微软官网下载完成ISO镜像,最好不要在线安装,打开官方链接 https://www.visualstudio.com/zh-cn/downloads/download-visual-studio-vs.aspx按下图操作:2、用虚拟光驱加载,或者直接右键解压。在安装前,先安装两个证书。亲测,安装后,减少了很多“安装包损坏或丢失”的现象。两证书下载地址链接: https:/..._vs2015离线版csdn

解决vue中安装postcss-pxtorem插件,报错“ Error: PostCSS plugin postcss-pxtorem requires PostCSS 8.”_error: postcss plugin postcss-import requires post-程序员宅基地

文章浏览阅读2k次,点赞4次,收藏3次。目前 postcss-pxtorem 版本最高6.0.0,报这个错是因为插件版本太高,降成5.1.1可解决这个报错解决方法:分两步1.执行npm uninstall post-pxtorem2.执行npm i [email protected]_error: postcss plugin postcss-import requires postcss 8.

Linux-ARM开发_linux arm开发-程序员宅基地

文章浏览阅读787次。Linux-ARM开发_linux arm开发