JavaWeb文件上传下载实例讲解(酷炫的文件上传技术)
一、课程概述
在web应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲javaweb中的文件上传功能的相关技术实现,并且随着互联网技术的飞速发展,用户对网站的体验要求越来越高,在文件上传功能的技术上也出现许多创新点,例如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,大文件断点续传,大文件秒传等等。
本课程需要的基础知识:
了解基本的http协议内容
基本io流操作技术
servlet基础知识
javascript/jquery技术基础知识
二、文件上传的基础
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,并且所有流数据都会随着http请求携带到服务器端。所以,文件上传时的请求内容格式要能够基本看懂。
文件上传页面:
<form action="/itheimaupload/uploadservlet" method="post" enctype="multipart/form-data"> 请选择上传的文件:<input type="file" name="attach"/><br/> <input type="submit" value="提交"/> </form>
http请求内容:
三、java后台使用servlet接收文件
如果使用servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般后台选择采用apache的开源工具common-fileupload这个文件上传组件。
//java后台代码:commons-fileupload组件上传文件 public class uploadservlet extends httpservlet { public void doget(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { //1.配置缓存 diskfileitemfactory factory = new diskfileitemfactory(1*1024*1024,new file("c:/tempfiles/")); //2.创建servlefileupload对象 servletfileupload sfu = new servletfileupload(factory); //解决文件名称中文问题 sfu.setheaderencoding("utf-8"); //3.解析 try { list<fileitem> list = sfu.parserequest(request); //解析所有内容 if(list!=null){ for(fileitem item:list){ //判断是否为普通表单参数 if(item.isformfield()){ //普通表单参数 //获取表单的name属性名称 string fieldname = item.getfieldname(); //获取表单参数值 string value = item.getstring("utf-8"); }else{ //文件 if(item.getname()!=null && !item.getname().equals("")) { //保存到服务器硬盘 fileutils.copyinputstreamtofile(item.getinputstream(), new file("c:/targetfiles/"+item.getname())); item.delete(); } } } } } catch (fileuploadexception e) { e.printstacktrace(); } } public void dopost(httpservletrequest request, httpservletresponse response) throws servletexception, ioexception { doget(request, response); } }
四、使用webuploader上传组件
文件上传页面的前端我们可以选择使用一些比较好用的上传组件,例如百度的开源组件webuploader,这个组件基本能满足文件上传的一些日常所需功能,如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,甚至是大文件断点续传,大文件秒传。
下载webupload组件
到webupload官网下载webupload包
webupload目录结构:
基本文件上传demo(包含上传进度)
前端
1.1 在页面导入所需css,js
<link rel="stylesheet" type="text/css" href="${pagecontext.request.contextpath}/css/webuploader.css"> <script type="text/javascript" src="${pagecontext.request.contextpath }/js/jquery-1.10.2.min.js"></script> <script type="text/javascript" src="${pagecontext.request.contextpath }/js/webuploader.js"></script>
1.2 编写上传页面标签
<!-- 上传div --> <div id="uploader"> <!-- 显示文件列表信息 --> <ul id="filelist"></ul> <!-- 选择文件区域 --> <div id="filepicker">点击选择文件</div> </div>
1.3 编写webupload代码
<script type="text/javascript"> //1.初始化webupload,以及配置全局的参数 var uploader = webuploader.create( { //flashk控件的地址 swf: "${pagecontext.request.contextpath}/js/uploader.swf", //后台提交地址 server:"${pagecontext.request.contextpath}/uploadservlet", //选择文件控件的标签 pick:"#filepicker", //自动上传文件 auto:true, } ); //2.选择文件后,文件信息队列展示 // 注册filequeued事件:当文件加入队列后触发 // file: 代表当前选择的文件 uploader.on("filequeued",function(file){ //追加文件信息div $("#filelist").append("<div id='"+file.id+"' class='fileinfo'><span>"+file.name+"</span><div class='state'>等待上传...</div><span class='text'></span></div>"); }); //3.注册上传进度监听 //file: 正在上传的文件 //percentage: 当前进度的比例。最大为1.例如:0.2 uploader.on("uploadprogress",function(file,percentage){ var id = $("#"+file.id); //更新状态信息 id.find("div.state").text("上传中..."); //更新上传百分比 id.find("span.text").text(math.round(percentage*100)+"%"); }); //4.注册上传完毕监听 //file:上传完毕的文件 //response:后台回送的数据,以json格式返回 uploader.on("uploadsuccess",function(file,response){ //更新状态信息 $("#"+file.id).find("div.state").text("上传完毕"); });
2)后端servlet代码
diskfileitemfactory factory = new diskfileitemfactory(); servletfileupload sfu = new servletfileupload(factory); sfu.setheaderencoding("utf-8"); try { list<fileitem> items = sfu.parserequest(request); for(fileitem item:items){ if(item.isformfield()){ //普通信息 }else{ //文件信息 //判断只有文件才需要进行保存处理 system.out.println("接收的文件名称:"+item.getname()); //拷贝文件到后台的硬盘 fileutils.copyinputstreamtofile(item.getinputstream(), new file(serverpath+"/"+item.getname())); system.out.println("文件保存成功"); } } } catch (fileuploadexception e) { e.printstacktrace(); }
生成图片缩略图
关键点:调用uploader.makethumb()方法生成缩略图
uploader.on("filequeued",function(file){ //追加文件信息div $("#filelist").append("<div id='"+file.id+"' class='fileinfo'><img/><span>"+file.name+"</span><div class='state'>等待上传...</div><span class='text'></span></div>"); //制造图片缩略图:调用makethumb()方法 //error: 制造缩略图失败 //src: 缩略图的路径 uploader.makethumb(file,function(error,src){ var id = $("#"+file.id); //如果失败,则显示“不能预览” if(error){ id.find("img").replacewith("不能预览"); } //成功,则显示缩略图到指定位置 id.find("img").attr("src",src); }); });
拖拽,黏贴上传
1)页面添加拖拽区域的div
<!-- 上传div --> <div id="uploader"> <!-- 文件拖拽区域 --> <div id="dndarea"> <p>将文件直接拖拽到这里即可自动上传</p> </div> <!-- 显示文件列表信息 --> <ul id="filelist"></ul> <!-- 选择文件区域 --> <div id="filepicker">点击选择文件</div> </div>
2)在webuploader的全局配置参数添加拖拽功能的参数
//1.初始化webupload,以及配置全局的参数 var uploader = webuploader.create( { //flashk控件的地址 swf: "${pagecontext.request.contextpath}/js/uploader.swf", //后台提交地址 server:"${pagecontext.request.contextpath}/uploadservlet", //选择文件控件的标签 pick:"#filepicker", //自动上传文件 auto:true, //开启拖拽功能,指定拖拽区域 dnd:"#dndarea", //禁用页面其他地方的拖拽功能,防止页面直接打开文件 disableglobaldnd:true //开启黏贴功能 paste:"#uploader" } );
大文件分块上传
1)在webuploader全局参数中添加分块上传参数
//1.初始化webupload,以及配置全局的参数 var uploader = webuploader.create( { //flashk控件的地址 swf: "${pagecontext.request.contextpath}/js/uploader.swf", //后台提交地址 server:"${pagecontext.request.contextpath}/uploadservlet", //选择文件控件的标签 pick:"#filepicker", //自动上传文件 auto:true, //开启拖拽功能,指定拖拽区域 dnd:"#dndarea", //禁用页面其他地方的拖拽功能,防止页面直接打开文件 disableglobaldnd:true, //开启黏贴功能 paste:"#uploader", //分块上传设置 //是否分块上传 chunked:true, //每块文件大小(默认5m) chunksize:5*1024*1024, //开启几个并发线程(默认3个) threads:3, //在上传当前文件时,准备好下一个文件 preparenextfile:true } );
2)监控上传文件的三个时间点
添加以上三个配置后,会发现当文件超过5m时,webuploader自动把文件会分几个请求发送给后台
每个分块请求,包含的信息:
可以监听文件分块上传的三个重要的时间点。
before-send-file : 在所有分块发送之前调用 before-send: 如果有分块,在每个分块发送之前调用 after-send-file: 在所有分块发送完成之后调用 //5.监控文件上传的三个时间点(注意:该段代码必须放在webuploader.create之前) //时间点1::所有分块进行上传之前(1.可以计算文件的唯一标记;2.可以判断是否秒传) //时间点2: 如果分块上传,每个分块上传之前(1.询问后台该分块是否已经保存成功,用于断点续传) //时间点3:所有分块上传成功之后(1.通知后台进行分块文件的合并工作) webuploader.uploader.register({ "before-send-file":"beforesendfile", "before-send":"beforesend", "after-send-file":"aftersendfile" },{ //时间点1::所有分块进行上传之前调用此函数 beforesendfile:function(){ //1.计算文件的唯一标记,用于断点续传和秒传 //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能 }, //时间点2:如果有分块上传,则 每个分块上传之前调用此函数 beforesend:function(){ //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能 }, //时间点3:所有分块上传成功之后调用此函数 aftersendfile:function(){ //1.如果分块上传,则通过后台合并所有分块文件 } });
before-send-file逻辑:
//利用md5file()方法计算文件的唯一标记符 //该函数接收一个deferred beforesendfile:function(file){ //创建一个deffered var deferred = webuploader.deferred(); //1.计算文件的唯一标记,用于断点续传和秒传 (new webuploader.uploader()).md5file(file,0,5*1024*1024) .progress(function(percentage){ $("#"+file.id).find("div.state").text("正在获取文件信息..."); }) .then(function(val){ uniquefiletag = val; $("#"+file.id).find("div.state").text("成功获取文件信息"); //只有文件信息获取成功,才进行下一步操作 deferred.resolve(); }); //alert(uniquefiletag); //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能 //返回deffered return deferred.promise(); }
before-send逻辑:
//向后台发送当前文件的唯一标记,用于后台创建保存分块文件的目录 beforesend:function(){ //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录 this.owner.options.formdata.filemd5 = filemd5; }
3)后台需要保存所有分块文件
//为每个文件创建一个目录,并保存这个文件的所有分块文件 //判断是否已经分块上传 if(chunks!=null){ system.out.println("分块处理..."); //进行分块上传了 //建立一个临时目录,用于保存所有分块文件 file chunksdir = new file(serverpath+"/"+filemd5); if(!chunksdir.exists()){ chunksdir.mkdir(); } if(chunk!=null){ //保存分块文件 file chunkfile = new file(chunksdir.getpath()+"/"+chunk); fileutils.copyinputstreamtofile(item.getinputstream(), chunkfile); }
4)前台通知后台合并所有分块文件
//前台通知后台合并文件 after-send-file逻辑: aftersendfile:function(file){ //1.如果分块上传,则通过后台合并所有分块文件 //请求后台合并文件 $.ajax( { type:"post", url:"${pagecontext.request.contextpath}/uploadcheckservlet?action=mergechunks", data:{ //文件唯一标记 filemd5:filemd5, //文件名称 filename:file.name }, datatype:"json", success:function(response){ alert(response.msg); } } ); } //后台合并所有分块文件 if("mergechunks".equals(action)){ system.out.println("开始合并文件..."); //合并文件 string filemd5 = request.getparameter("filemd5"); string filename = request.getparameter("filename"); //读取目录里面的所有文件 file f = new file(serverpath+"/"+filemd5); file[] filearray = f.listfiles(new filefilter(){ //排除目录,只要文件 public boolean accept(file pathname) { if(pathname.isdirectory()){ return false; } return true; } }); //转成集合,便于排序 list<file> filelist = new arraylist<file>(arrays.aslist(filearray)); //从小到大排序 collections.sort(filelist, new comparator<file>() { public int compare(file o1, file o2) { if(integer.parseint(o1.getname()) < integer.parseint(o2.getname())){ return -1; } return 1; } }); file outputfile = new file(serverpath+"/"+filename); //创建文件 outputfile.createnewfile(); //输出流 filechannel outchannel = new fileoutputstream(outputfile).getchannel(); //合并 filechannel inchannel; for(file file : filelist){ inchannel = new fileinputstream(file).getchannel(); inchannel.transferto(0, inchannel.size(), outchannel); inchannel.close(); //删除分片 file.delete(); } //清除文件夹 file tempfile = new file(serverpath+"/"+filemd5); if(tempfile.isdirectory() && tempfile.exists()){ tempfile.delete(); } //关闭流 outchannel.close(); response.setcontenttype("text/html;charset=utf-8"); response.getwriter().write("{\"msg\":\"合并成功\"}"); }
大文件断点续传
在实现了分块上传的基础上,实现断点续传就非常简单了!!!
前端:
//时间点2:如果有分块上传,则 每个分块上传之前调用此函数 //block:代表当前分块对象 beforesend:function(block){ //1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能 var deferred = webuploader.deferred(); //请求后台是否保存完成该文件信息,如果保存过,则跳过,如果没有,则发送该分块内容 $.ajax( { type:"post", url:"${pagecontext.request.contextpath}/uploadcheckservlet?action=checkchunk", data:{ //文件唯一标记 filemd5:filemd5, //当前分块下标 chunk:block.chunk, //当前分块大小 chunksize:block.end-block.start }, datatype:"json", success:function(response){ if(response.ifexist){ //分块存在,跳过该分块 deferred.reject(); }else{ //分块不存在或者不完整,重新发送该分块内容 deferred.resolve(); } } } ); //携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录 this.owner.options.formdata.filemd5 = filemd5; return deferred.promise(); },
后台:
//检查该分块是否存在或者完整保存 private void checkchunk(httpservletrequest request, httpservletresponse response) throws ioexception, filenotfoundexception { system.out.println("checkchunk..."); string filemd5 = request.getparameter("filemd5"); string chunk = request.getparameter("chunk"); string chunksize = request.getparameter("chunksize"); file checkfile = new file(serverpath+"/"+filemd5+"/"+chunk); response.setcontenttype("text/html;charset=utf-8"); //检查文件是否存在,且大小是否一致 if(checkfile.exists() && checkfile.length()==integer.parseint(chunksize)){ response.getwriter().write("{\"ifexist\":1}"); }else{ response.getwriter().write("{\"ifexist\":0}"); } }
文件秒传
在所有分块请求之前,就已经可以进行实现秒传功能!!!
前端:
beforesendfile:function(file){ //创建一个deffered var deferred = webuploader.deferred(); //1.计算文件的唯一标记,用于断点续传和秒传 (new webuploader.uploader()).md5file(file,0,5*1024*1024) .progress(function(percentage){ $("#"+file.id).find("div.state").text("正在获取文件信息..."); }) .then(function(val){ filemd5 = val; $("#"+file.id).find("div.state").text("成功获取文件信息"); //2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能 $.ajax( { type:"post", url:"${pagecontext.request.contextpath}/uploadcheckservlet?action=filecheck", data:{ //文件唯一标记 filemd5:filemd5 }, datatype:"json", success:function(response){ if(response.ifexist){ $("#"+file.id).find("div.state").text("秒传成功"); //如果存在,则跳过该文件,秒传成功 deferred.reject(); }else{ //继续上传 deferred.resolve(); } } } ); }); //返回deffered return deferred.promise(); },
后台:
//检查文件的md5数据是否跟在数据库存在 private void filecheck(httpservletrequest request, httpservletresponse response) throws ioexception, filenotfoundexception { string filemd5 = request.getparameter("filemd5"); //模拟数据库 map<string,string> database = new hashmap<string,string>(); database.put("576018603f4091782b68b78af85704a1", "01.课程回顾.itcast"); response.setcontenttype("text/html;charset=utf-8"); if(database.containskey(filemd5)){ response.getwriter().write("{\"ifexist\":1}"); }else{ response.getwriter().write("{\"ifexist\":0}"); } }
以上所述是小编给大家介绍的javaweb文件上传下载实例讲解(酷炫的文件上传技术),希望对大家有所帮助
上一篇: Java多线程-线程的同步与锁的问题