批量下载,多文件压缩打包zip下载


0、写在前面的话

图片批量下载,要求下载时集成为一个压缩包进行下载。从昨天下午折腾到现在,踩坑踩得莫名其妙,还是来唠唠,给自己留个印象的同时,也希望给需要用到这个方法的人带来一些帮助。

1、先叨叨IO

叨叨IO是因为网络传输无非也就是流的传递,所以下载文件到本地的话实际上也是IO的东西,这个和读取本地文件然后写入到本地另一个文件的操作是基本一样的。

我在自己IO基础的博客中(《[03] 节点流和处理流》)其实也有提到示例,拿复写文件来说,大概是如下过程:
1007017-20180618220825691-1558843369.jpg
对于读取文件(不仅仅是文本)到服务器内存,常见的是通过InputStream读取File,所以你可能也经常看到如下类似的代码:
outputStream = new FileOutputStream(file);		   	
byte[] temp = new byte[1024];
int size = -1;
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完outputStream.write(temp, 0, size);
}
1
outputStream = new FileOutputStream(file);          
2
byte[] temp = new byte[1024];
3
int size = -1;
4
while ((size = inputStream.read(temp)) != -1) { // 每次读取1KB,直至读完
5
    outputStream.write(temp, 0, size);
6
}

我这里想说的是,不论何种形式,只需要知道的是,在写出之前,要获取将写出的数据,这个数据常常是作为byte[ ]类型的。

同时,因为是写出到本地文件,所以这里图示中的OutputStream无非应该是使用FileOutputStream罢了。那么以此来类比网络传输下载的话,OutputStream显然就替换成响应HttpServletResponse中的OutputStream就可以了。本质都是输出流,不同的类型决定你输出的方式等。

2、再叨叨ajax

当你把文件数据的二进制放到了响应的流中,也确实在响应中返回了,可是浏览器就是不争气,不给面子,不启动下载。这个时候,你要看看,你发送请求的方式,是否采取了ajax请求。如下图采用ajax请求资源,确实收到了流信息,但是反馈在浏览器上却什么也没发生:
1007017-20180618220826463-1486534732.png
原因在于,ajax的返回值类型是json/text/html/xml类型,或者可以说ajax的发送,接受都只能是string字符串,不能流类型,所以无法实现文件下载,强用会出现response冲突。

但用ajax仍然可以获得文件的内容,该文件将被保留在内存中,无法将文件保存到磁盘。这是因为js无法和磁盘进行交互,否则这会是一个严重的安全问题,js无法调用到浏览器的下载处理机制和程序,会被浏览器阻塞。

所以在前端,简单一点,使用 window.location.href 的方式访问url,实现下载。

3、正儿八经的批量下载实现

下载前,因为批量下载需要打包为压缩包,所以要用到一个三方jar,maven地址如下:
<dependency><groupId>ant</groupId><artifactId>ant</artifactId><version>1.6.5</version>
</dependency>
5
1
<dependency>
2
  <groupId>ant</groupId>
3
  <artifactId>ant</artifactId>
4
  <version>1.6.5</version>
5
</dependency>

先贴代码,然后再进行说明:
/*** 批量下载** @param idxs 图片的id拼接字符串,用逗号隔开*/
public String downloadBatch(String idxs) {String[] ids = idxs.split(",");try {HttpServletResponse response = ServletActionContext.getResponse();OutputStream out = setDownloadOutputStream(response, String.valueOf(new Date().getTime()), "zip");ZipOutputStream zipOut = new ZipOutputStream(out);for (int i = 0; i < ids.length; i++) {Picture picture = Picture.get(Picture.class, Long.parseLong(ids[i]));byte[] data = picture.getData();zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue()));writeBytesToOut(zipOut, data, BUFFER_SIZE);zipOut.closeEntry();}zipOut.flush();zipOut.close();} catch (IOException e) {e.printStackTrace();log.warn("下载失败:" + e.getMessage());}return null;
}
x
1
/**
2
 * 批量下载
3
 *
4
 * @param idxs 图片的id拼接字符串,用逗号隔开
5
 */
6
public String downloadBatch(String idxs) {
7
    String[] ids = idxs.split(",");
8
    try {
9
        HttpServletResponse response = ServletActionContext.getResponse();
10
        OutputStream out = setDownloadOutputStream(response, String.valueOf(new Date().getTime()), "zip");
11
        ZipOutputStream zipOut = new ZipOutputStream(out);
12
13
        for (int i = 0; i < ids.length; i++) {
14
            Picture picture = Picture.get(Picture.class, Long.parseLong(ids[i]));
15
            byte[] data = picture.getData();
16
            zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue()));
17
            writeBytesToOut(zipOut, data, BUFFER_SIZE);
18
            zipOut.closeEntry();
19
        }
20
        zipOut.flush();
21
        zipOut.close();
22
23
    } catch (IOException e) {
24
        e.printStackTrace();
25
        log.warn("下载失败:" + e.getMessage());
26
    }
27
28
    return null;
29
}

/*** 设置文件下载的response格式** @param response 响应* @param fileName 文件名称* @param fileType 文件类型* @return 设置后响应的输出流OutputStream* @throws IOException*/
private static OutputStream setDownloadOutputStream(HttpServletResponse response, String fileName, String fileType) throws IOException {fileName = new String(fileName.getBytes(), "ISO-8859-1");response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "." + fileType);response.setContentType("multipart/form-data");return response.getOutputStream();
}
15
1
/**
2
 * 设置文件下载的response格式
3
 *
4
 * @param response 响应
5
 * @param fileName 文件名称
6
 * @param fileType 文件类型
7
 * @return 设置后响应的输出流OutputStream
8
 * @throws IOException
9
 */
10
private static OutputStream setDownloadOutputStream(HttpServletResponse response, String fileName, String fileType) throws IOException {
11
    fileName = new String(fileName.getBytes(), "ISO-8859-1");
12
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName + "." + fileType);
13
    response.setContentType("multipart/form-data");
14
    return response.getOutputStream();
15
}

/*** 将byte[]类型的数据,写入到输出流中** @param out 输出流* @param data 希望写入的数据* @param cacheSize 写入数据是循环读取写入的,此为每次读取的大小,单位字节,建议为4096,即4k* @throws IOException*/
private static void writeBytesToOut(OutputStream out, byte[] data, int cacheSize) throws IOException {int surplus = data.length % cacheSize;int count = surplus == 0 ? data.length / cacheSize : data.length / cacheSize + 1;for (int i = 0; i < count; i++) {if (i == count - 1 && surplus != 0) {out.write(data, i * cacheSize, surplus);continue;}out.write(data, i * cacheSize, cacheSize);}
}
1
/**
2
 * 将byte[]类型的数据,写入到输出流中
3
 *
4
 * @param out 输出流
5
 * @param data 希望写入的数据
6
 * @param cacheSize 写入数据是循环读取写入的,此为每次读取的大小,单位字节,建议为4096,即4k
7
 * @throws IOException
8
 */
9
private static void writeBytesToOut(OutputStream out, byte[] data, int cacheSize) throws IOException {
10
    int surplus = data.length % cacheSize;
11
    int count = surplus == 0 ? data.length / cacheSize : data.length / cacheSize + 1;
12
    for (int i = 0; i < count; i++) {
13
        if (i == count - 1 && surplus != 0) {
14
            out.write(data, i * cacheSize, surplus);
15
            continue;
16
        }
17
        out.write(data, i * cacheSize, cacheSize);
18
    }
19
}

第一段代码为下载的主方法,用到了两个子方法,分别贴在之后的第二段代码和第三段代码。

文件的下载其实很简单,刚才在叨叨IO中也提到了,所以对于网络传输下载的IO来说,整体也就三个步骤:
  • 设置文件ContentType类型和文件头
  • 读取文件数据为byte[]
  • 将数据写入到响应response的输出流中

设置请求头信息,在方法 setDownloadOutputStream() 中已经写明了,是文件所以要告知文件处理应该为 attachement,并附上文件名(转码ISO-8859-1避免中文乱码)。而文件内容的类型,统一设置为 multipart/form-data 即可,交给浏览器自行判断下载的文件类型。

读取文件数据,因为本例中我的图片数据直接存储在数据库字段中,所以取出时直接获取的就是byte[]。如果你的方式是文件存放在本地,数据库只是存储了文件的物理地址,那么你得多做做操作,用FileInputStream把文件的内容先读出来,结果和目的都是一样的,就是获取文件的byte[]。

获得了数据,那么直接通过OutputStream的write方法写入即可。(这里的写入方法我用了循环写入,当初是想着避免内存紧张,可是现在回过头来想不对啊,读文件的时候才吃内存的,这里我已经读完了再写,循环与否已经不重要了。所以实际上应该是边读边写,才是良性的,可是我的byte[]存在数据库字段里,取出来时就全部读入到内存中了,所以这里实际上是不需要循环写入的,我这是画蛇添足了。另外,如果是从File读的话,则边读边写,见目录1叨叨IO中的小段代码)

写入到输出流了,flush()刷新一下,即可。

上面这些是对于文件下载通用的,如果是批量压缩包形式,在第一段代码中黄色部分,来进行重点说明:
  • ZipOutputStream zipOut = new ZipOutputStream(out); 
  • //把响应的输出流包装一下而已,便于使用相关压缩方法,就像多了个包装袋

  • zipOut.putNextEntry(new ZipEntry(i + "_" + picture.getName() + "." + picture.getFormat().getValue())); 
  • zipOut.closeEntry();
  • //压缩包中的多个文件,实际上每个就是这里的ZipEntry对象,每开始写入某个文件的内容时,必须先putNextEntry(new ZipEntry(String fileName)),然后才可以写入,写完这个文件,必须使用closeEntry()说明,已经写完了第一个文件。就好像putNextEntry是在说 “我要开始写压缩包的下一个文件啦”,而closeEntry则是在说“压缩包里的这个文件我已经写完啦”。循环反复,最终把所有文件写入这个“披着压缩输出流外壳的响应输出流”

  • zipOut.flush();
  • 写入完成后,刷一下即可。就像去超市买东西,购物车装好了,总得结一次帐才能把东西拿走吧。当然,最后别忘了close关闭。

4、参考链接

  • 不能用ajax请求下载文件
  • 文件下载(只需要简单的四步),Java中都通用
  • Java IO 压缩流(ZipOutputStream/ZipInputStream)


转载于:https://www.cnblogs.com/deng-cc/p/7809364.html

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

选了combobox里的选项后没激发change事件_35岁前多用利弊分析,35岁后要有“安全边际”...

想冒险&#xff0c;要趁早1/6、距离糟糕的事情发生&#xff0c;还有多远&#xff1f;我现在同时在职场和投资两类战线写文章&#xff0c;读者也开始串戏。前几天在我另外一个投资号上&#xff0c;有人问我&#xff1a;如果你把投资理念原则扩大到生活中&#xff0c;你觉得最有启…

[W班]第二次结对作业成绩评价

作业地址&#xff1a; https://edu.cnblogs.com/campus/fzu/FZUSoftwareEngineering1715W/homework/1016 作业要求&#xff1a; 1、代码具有规范性。 2、实现的程序语言不做限制性要求&#xff0c;但需要能生成Windows平台的可执行文件。C/C/C#编译后即可生成&#xff0c;其他…

resnet50结构_无需额外数据、Tricks、架构调整,CMU开源首个将ResNet50精度提升至80%+新方法

本文是CMU的Zhiqiang Shen提出的一种提升标准ResNet50精度的方法&#xff0c;它应该是首个将ResNet50的Top1精度刷到80%的(无需额外数据&#xff0c;无需其他tricks&#xff0c;无需网络架构调整)。该文对于研究知识蒸馏的同学应该是有不少可参考的价值&#xff0c;尤其是里面提…

linq to sql 行转列_SQL 难题:行转列

问题&#xff1a;有一张学生成绩表sc&#xff08;sid 学号&#xff0c;cid 课程&#xff0c;score 成绩&#xff09;&#xff0c;请查询出每个学生的英语、数学的成绩&#xff08;行转列&#xff0c;一个学生只有一行记录&#xff09;。建表语句&#xff1a;create 实现方式1—…

c++测试cpu_测评丨NXP系列 LS1028 LS1046等产品网络性能测试

号外号外&#xff01;继OK1012A-C面市以来&#xff0c;飞凌嵌入式公司相继推出了OK1043A-C、OK1046A-C&#xff0c;以及最新上市的OK1028A-C&#xff0c;OK10XX系列产品也是一个大家族了。正所谓春兰秋菊&#xff0c;各擅胜场。下面小编就各产品的网络性能为您简单介绍一下。先…

四.Windows I/O模型之重叠IO(overlapped)模型

1.适用于除Windows CE之外的各种Windows平台.在使用这个模型之前应该确保该系统安装了Winsock2.重叠模型的基本设计原理是使用一个重叠的数据结构&#xff0c;一次投递一个或多个Winsock I/O请求。在重叠模型中&#xff0c;收发数据使用WSA开头的函数。2.WSA_FLAG_OVERLAPPED标…

vscode怎样导入数据_【Python开发】用VSCode+Jupyter notebook 编写 Python

版权声明&#xff1a;小博主水平有限&#xff0c;希望大家多多指导。本文仅代表作者本人观点。1、过去&#xff0c;想要在 VSCode 中运行 Jupyter notebook 需要安装一个 Neuron 扩展&#xff0c;我也装过&#xff0c;感觉很强大、很方便。不过现在&#xff0c;VSCode 中 Pytho…

springboot怎么杀进程_全新Steam在线游戏 Among us太空狼人杀攻略

众多游戏爱好者已加入我们&#xff01;带你发现好游戏&#xff01;休闲娱乐小游戏&#xff01;点击下方↓↓↓↓"开始游戏"&#xff0c;赶紧进入吧&#xff01;&#xff01;戳“开始游戏”玩百款火爆小游戏&#xff01;《Among us》游戏好玩吗&#xff1f;《Among us…

kafka 怎么样连接图形化界面_从零开始搭建Kafka+SpringBoot分布式消息系统

前言由于kafka强依赖于zookeeper&#xff0c;所以需先搭建好zookeeper集群。由于zookeeper是由java编写的&#xff0c;需运行在jvm上&#xff0c;所以首先应具备java环境。(ps&#xff1a;默认您的centos系统可联网&#xff0c;本教程就不教配置ip什么的了)(ps2&#xff1a;没有…

《Iterative-GAN》的算法伪代码整理

花了一下午时间整理本人的论文Iterative-GAN的算法伪代码&#xff0c;由于篇幅较长&#xff0c;投会议方面的文章就不加入了&#xff0c;以后如果投期刊再说。留此存档。 转载于:https://www.cnblogs.com/punkcure/p/7821031.html

h5能调取摄像头吗_高质感的国产中型车,实力能比肩本田雅阁吗?带你看红旗H5...

中国品牌的豪华中型车&#xff0c;带你看红旗H5伴随着经济的快速发展&#xff0c;大家的钱包现在也是越来越鼓&#xff0c;也开始向往更加美好的生活。曾经很多人买车都是为了满足基本的代步需求&#xff0c;如今也开始在车辆的品质与行驶质感上有了更高要求。而为了迎合市场变…

lstm网络_LSTM(长短期记忆网络)

在上篇文章一文看尽RNN(循环神经网络)中&#xff0c;我们对RNN模型做了总结。由于RNN也有梯度消失的问题&#xff0c;因此很难处理长序列的数据&#xff0c;大牛们对RNN做了改进&#xff0c;得到了RNN的特例LSTM(Long Short-Term Memory)&#xff0c;它可以避免常规RNN的梯度消…

ant接口用什么天线_手机听收音机时,为什么必须用耳机作为天线?

名侦探柯基-十万个为什么 第七十六期起因&#xff0c;观看活着韩国丧尸电影时的一幕&#xff0c;刘亚仁想听电台广播&#xff0c;却无奈于所有设备都是无线的&#xff0c;由此疑惑到&#xff0c;只有插入有线的耳机&#xff0c;才能收听广播吗&#xff1f;耳机线就是天线&#…

qt c++ 图片预览_Qt多语言国际化

Qt附加工具介绍Qt Assistant&#xff08;Qt助手)Qt Linguist&#xff08;Qt语言家&#xff09;Qt Designer&#xff08;Qt设计师&#xff09;Qt AssistantQt Assistant是可配置且可重新发布的文档阅读器&#xff0c;可以方便地进行定制并与Qt应用程序一起重新发布。Qt Assistan…

Icon+启动图尺寸

1、LaunchImage 启动图 命名格式&#xff1a; 1x -> xxx.png 2x -> xxx2x.png Retina 4 -> xxx2x.png     转载于:https://www.cnblogs.com/z-z-z/p/7828082.html

智商情商哪个重要_《所谓逆商高,就是心态好》:逆商,比情商和智商更重要...

所谓“逆商”&#xff0c;是指人们遇到逆境时的应对能力&#xff0c;即战胜挫折、摆脱困境和超越困难的能力。我们一生会面临各种各样的难题&#xff0c;也许是考试失利&#xff0c;也许是和心爱的人分离&#xff0c;也许是工作上竞争失败……在失意的时候你会做何选择&#xf…

mysql 排名_学会在MySQL中实现Rank高级排名函数,所有取前几名问题全部解决.

MySQL中没有Rank排名函数&#xff0c;当我们需要查询排名时&#xff0c;只能使用MySQL数据库中的基本查询语句来查询普通排名。尽管如此&#xff0c;可不要小瞧基础而简单的查询语句&#xff0c;我们可以利用其来达到Rank函数一样的高级排名效果。在这里我用一个简单例子来实现…

意大利_【解读】去意大利留学,一定要学意大利语吗?意大利语难吗?

喜欢意大利&#xff0c;想去意大利留学&#xff0c;但不想学意大利语可以吗&#xff1f;意大利语太难了&#xff0c;听说有英授专业(本来就要学英语、考雅思所以不担心英语)……问题来了去意大利留学&#xff0c;选择英授专业的话还需要学意大利语吗&#xff1f;我们一点点剖析…

MD5、SHA1、SHA256的简单讲解

简述: 最近在研究系统以及驱动&#xff0c;当下载比较大的文件时总会提供SHA1或者SHA256&#xff0c;下载结束后使用校验工具得到的值与它进行比对来判断下载是否成功。 使用工具校验 certutil -hashfile 文件名 sha1/sha256/md5正文: MD5、SHA1、SHA256这些都被称为 哈希…

java swarm集群_52个Java程序员不可或缺的 Docker 工具

Docker工具分类列表编排和调度持续集成/持续部署(CI / CD)监控记录安全存储/卷管理联网服务发现构建管理编排和调度 1. KubernetesKubernetes是市场上最实用的最受欢迎的容器编排引擎。最初作为一个Google项目开始&#xff0c;成千上万的团队使用它来部署生产中的容器。谷歌声称…