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组件
http://fex.baidu.com/webuploader/到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文件上传下载实例讲解(酷炫的文件上传技术),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!