我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。
这次项目的需求:
支持大文件的上传和续传,要求续传支持所有浏览器,包括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包
commons-io包
commons-upload包
所有依赖包
代码实现
<%@ 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 = new byte[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。在使用前需要配置一下数据库,可以参考我写的这篇文章:java http大文件断点续传上传 – 泽优软件博客
欢迎入群一起讨论“374992201”
来源:https://www.cnblogs.com/zyzzz/p/15936437.html
本站部分图文来源于网络,如有侵权请联系删除。