java+批量下载大文件
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。
这次项目的需求:
支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,ie8,ie9,Chrome,Firefox,360安全浏览器,并且刷新浏览器后仍然能够续传,重启浏览器(关闭浏览器后再打开)仍然能够继续上传,重启电脑后仍然能够上传
支持文件夹的上传,要求服务端能够保留层级结构,并且能够续传。需要支持10万个以上的文件夹上传。
支持低版本的系统和浏览器,因为这个项目的最终运行环境在政府,政府的配置都一般,职员都是办公用,内存都不大,基本上以Windows XP的系统为主。
1、介绍enctype
enctype 属性规定发送到服务器之前应该如何对表单数据进行编码。
enctype作用是告知服务器请求正文的MIME类型(请求消息头content-type的作用一样)
1、1 enctype的取值有三种
值 |
描述 |
application/x-www-form-urlencoded |
在发送前编码所有字符(默认) |
multipart/form-data |
不对字符编码。每一个表单项分割为一个部件 |
text/plain |
空格转换为 “+” 加号,但不对特殊字符编码。 |
1. 当enctype=’application/x-www-form-urlencoded’
2.当enctype=’multipart/form-data’
通过观察发现这个的请求体就发生了变化。这种请求体被称之为多部件请求体。
什么是多部件请求体:就是把每一个表单项分割为一个部件。
以请求头的content-type的boundary后面的一串随机字符串作为分割标识
普通表单项:
//name的意思是文本框里面name的属性值,而admin是我们输入的文本值
Content-Disposition: form-data; name="username"
admin
文件表单项
//filename的意思是:我们上传的文件名称,content-Type的意思是:MIME类型,asdasdas的意思是:文件里面的内容
Content-Disposition: form-data; name="upload"; filename="a.txt"
Content-Type: text/plain
asdasdas
3. 当enctype=’text/plain’
w3c称:空格会变成”+”加号,但是我这里没有发现,只有当get请求的时候,空格会变成”+”号
进入正题
完成上传需要满足3个必要的条件
提供form表单,method必须是post,因为get请求的传输数据一般为2kb,不同浏览器不一样。
form表单属性enctype的必须是multipart/form-data
提供input type=”file”类的上传输入域
大致实现原理:当enctype的值是multipart/form-data时,浏览器会把每个表单项进行分割,分割成不同的部件,以boundary的值为分割标识,这个标识的字符串是随机生成的,最后一个表单项的分割标识字符串末尾会多两个”- -“,代表结束。服务端用request.getHeader(“content-type”)获取分割字符串,然后进行解析。
代码实现
一、开发环境搭建
准备两个第三方jar包
所有依赖包
代码实现
<%@page language="java" import="up6.DBFile" pageEncoding="UTF-8"%>
<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="up6.FileBlockWriter" %>
<%@page import="up6.XDebug" %>
<%@page import="up6.*" %>
<%@page import="up6.biz.*" %>
<%@page import="org.apache.commons.fileupload.FileItem" %>
<%@page import="org.apache.commons.fileupload.FileItemFactory" %>
<%@page import="org.apache.commons.fileupload.FileUploadException" %>
<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>
<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
<%@page import="org.apache.commons.lang.*" %>
<%@page import="java.net.URLDecoder"%>
<%@page import="java.util.Iterator"%>
<%@page import="net.sf.json.JSONObject"%>
<%@page import="java.util.List"%>
<%
out.clear();
String uid = request.getHeader("uid");//
String id = request.getHeader("id");
String lenSvr = request.getHeader("lenSvr");
String lenLoc = request.getHeader("lenLoc");
String blockOffset= request.getHeader("blockOffset");
String blockSize = request.getHeader("blockSize");
String blockIndex = request.getHeader("blockIndex");
String blockMd5 = request.getHeader("blockMd5");
String complete = request.getHeader("complete");
String pathSvr = "";
//参数为空
if( StringUtils.isBlank( uid )
|| StringUtils.isBlank( id )
|| StringUtils.isBlank( blockOffset ))
{
XDebug.Output("param is null");return;
}
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List files = null;
try {files = upload.parseRequest(request);}
catch (FileUploadException e)
{out.println("read file data error:" + e.toString());return;}
FileItem rangeFile = null;
Iterator fileItr = files.iterator();
while (fileItr.hasNext())
{
rangeFile = (FileItem) fileItr.next();
if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr"))
{
pathSvr = rangeFile.getString();
pathSvr = PathTool.url_decode(pathSvr);
}
}
boolean verify = false;
String msg = "";
String md5Svr = "";
long blockSizeSvr = rangeFile.getSize();
if(!StringUtils.isBlank(blockMd5)){md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream());}
verify = Integer.parseInt(blockSize) == blockSizeSvr;
if(!verify){ msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize;}
if(verify && !StringUtils.isBlank(blockMd5))
{
verify = md5Svr.equals(blockMd5); if(!verify) msg = "block md5 error";
}
if(verify)
{
FileBlockWriter res = new FileBlockWriter();
if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc));
res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);
up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex));
JSONObject o = new JSONObject();
o.put("msg", "ok");
o.put("md5", md5Svr);
o.put("offset", blockOffset);
msg = o.toString();
}
rangeFile.delete();
out.write(msg);
%>
下载的必须条件
两个头一个流
content-type
Content-Type是返回消息中非常重要的内容,表示文档内容属于什么MIME类型。
浏览器会根据Content-Type来决定如何显示返回的消息体内容。
默认值是text/html
可以使用request.getServletContext().getMimeType(“文件名”)获取MIME类型。
Content-Disposition
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。
默认值是inline,表示在浏览器窗口中打开。
服务端向客户端游览器发送文件时,如果是浏览器支持的文件类型,一般会默认使用浏览器打开,比如txt、jpg等,会直接在浏览器 中显示。
如果需要提示用户保存,利用Content-Disposition进行一下处理,关键在于一定要加上attachment。
例如:Content-Disposition:attachment;filename=xxx,浏览器就会激活下载框对话框, attachment 表示附件, filname 后面跟随的是显示在下载框中的文件名称。
流
下载就是向客户端响应字节数据! 将一个文件变成字节数组, 使用 response.getOutputStream()
来响应给浏览器。
代码如下,此代码已经实现了断点续传功能,用户在下载过程可以暂停,和继续下载,对服务器造成的压力也比较小。
String fid = request.getHeader("id");
String blockIndex = request.getHeader("blockIndex");//基于1
String blockOffset = request.getHeader("blockOffset");//块偏移,相对于整个文件
String blockSize = request.getHeader("blockSize");//块大小(当前需要下载的大小)
String pathSvr = request.getHeader("pathSvr");//文件在服务器的位置
pathSvr = PathTool.url_decode(pathSvr);
if ( StringUtils.isBlank(fid)
||StringUtils.isBlank(blockIndex)
||StringUtils.isEmpty(blockOffset)
||StringUtils.isBlank(blockSize)
||StringUtils.isBlank(pathSvr))
{
response.setStatus(500);
response.setHeader("err","参数为空");
return;
}
File f = new File(pathSvr);
//文件不存在
if(!f.exists())
{
response.setStatus(500);
OutputStream os = response.getOutputStream();
System.out.println(String.format("%s 文件不存在",pathSvr));
os.close();
return;
}
long fileLen = f.length();
response.setContentType("application/x-download");
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.addHeader("Content-Length",blockSize);
response.setDateHeader("Expires", 0);
OutputStream os = response.getOutputStream();
try
{
RandomAccessFile raf = new RandomAccessFile(pathSvr,"r");
int readToLen = Integer.parseInt(blockSize);
int readLen = 0;
raf.seek( Long.parseLong(blockOffset) );//定位索引
byte[] data = newbyte[1048576];
while( readToLen > 0 )
{
readLen = raf.read(data,0,Math.min(1048576,readToLen) );
readToLen -= readLen;
os.write(data, 0, readLen);
}
os.flush();
os.close();
raf.close();
os = null;
response.flushBuffer();
out.clear();
out = pageContext.pushBody();
}
catch(Exception e)
{
response.setStatus(500);
os.close();
out.close();
e.printStackTrace();
}
finally
{
if(os != null)
{
os.close();
os = null;
}
out.clear();
out = pageContext.pushBody();
}%>
加载文件列表,在下载列表中显示出来
后端代码逻辑大部分是相同的,目前能够支持MySQL,Oracle,SQL。在使用前需要配置一下数据库,可以参考我写的这篇文章:http://blog.ncmem.com/wordpress/2019/08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/
java+批量下载大文件的更多相关文章
- JAVA 批量下载服务器文件到本地指定文件夹并重命名
/** * @功能 下载文件到指定文件夹并重命名 * @param url 请求的路径 * @param filePath 文件将要保存的目录 * @param filename 保存到本地的文件名 ...
- Android 开发工具类 27_多线程下载大文件
多线程下载大文件时序图 FileDownloader.java package com.wangjialin.internet.service.downloader; import java.io.F ...
- java批量下载
最近做了一些有关批量压缩下载的功能,网上也找了一些资源,但都不是太全面,所以自己整理一份,已备不时之需. 直接上代码: // 获取项目路径 private static String WEBC ...
- 转(Response.WriteFile 无法下载大文件解决方法)
以前用Response.WriteFile(filename),但当遇到大文件时无法完整下载. 该方法最大的问题,它不是直接将数据抛到客户端,而是在服务器端(IIS)上缓存.当下载文件比较大时,服务器 ...
- JAVA多线程下载网络文件
JAVA多线程下载网络文件,开启多个线程,同时下载网络文件. 源码如下:(点击下载 MultiThreadDownload.java) import java.io.InputStream; im ...
- python下载大文件
1. wget def download_big_file_with_wget(url, target_file_name): """ 使用wget下载大文件 Note: ...
- Python3 根据m3u8下载视频,批量下载ts文件并且合并
Python3 根据m3u8下载视频,批量下载ts文件并且合并 m3u8是苹果公司推出一种视频播放标准,是一种文件检索格式,将视频切割成一小段一小段的ts格式的视频文件,然后存在服务器中(现在为了减少 ...
- python 下载大文件
当使用requests的get下载大文件/数据时,建议使用使用stream模式. 当把get函数的stream参数设置成False时,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足 ...
- java批量解压文件夹下的所有压缩文件(.rar、.zip、.gz、.tar.gz)
// java批量解压文件夹下的所有压缩文件(.rar..zip..gz..tar.gz) 新建工具类: package com.mobile.utils; import com.github.jun ...
随机推荐
- PAT B1046.猜拳
课本AC #include <cstdio> int main() { int n, failA = 0, failB = 0; scanf("%d", &n) ...
- 高性能MySQL3_笔记1_Mysql的架构与历史
第一层:连接处理.授权认证.安全 第二层:mysql的核心功能,包括查询解析.分析.优化.缓存以及所有的内置函数(例如日期.加密.数学函数), 所有跨存储引擎的功能都在这一层实现:存储过程.触发器.视 ...
- 题解 P2879 【[USACO07JAN]区间统计Tallest Cow】
题目链接: https://www.luogu.org/problemnew/show/P2879 思路: 先不管最大高度,我们读入一对x,y.说明,x+1~y-1之间牛的身高都小于x,y. 然后不妨 ...
- python之字符串类型及其操作
1.1字符串类型的表示 字符串是字符的序列表示,可以由一对单引号('). 双引号(")或三引号(’")构成.其中,单引号和双引号都可以表示单行字符串,两者作用相同.使用单引号时,双 ...
- sass之mixin的全局引入(vue3.0)
sass之mixin的全局引入(vue3.0) 1.scss文件(mixin.scss) /* 渐变 */ @mixin gradual($color, $color1){ background: $ ...
- 3.Struts2-Result
注: 1.在struts.xml文件中使用include标签 可以将另外一个xml文件包含进struts.xml文件中,如: <struts> <constant name=&quo ...
- https和http的post发送总结
本文为转贴内容,感谢作者阿进! 需要转发数据到客户的https的服务器上出现一系列问题总结如下: 1.因为是https首先考虑到用最新的控件NetHTTPClient(只有在XE8以上才有). 2.客 ...
- java_day04_数组
chap04目标:数组---------------------------------------------- 1.概述 数组是一组数据的集合,数组中的每个数据被称为元素.在java中,数组也是对 ...
- thinkphp5 二维码生成
1.下载二维码插件Phpqrcode,地址 https://sourceforge.net/projects/phpqrcode/files/,把下载的文件夹放到\thinkphp\vendor下 2 ...
- python网络编程:socket半连接池、UDP通讯模板
一.TCP半连接池原理 二.UDP通讯 三.UDP聊天 四.UDP聊天2 五.UDP会粘包吗 六.UDP总结 七.UDP与TCP对比 一.TCP半连接池原理 客户端 import socket cli ...