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,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",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",//开启拖拽功能,指定拖拽区域 dnd:"#dndArea",//禁用页面其他地方的拖拽功能,防止页面直接打开文件 disableGlobalDnd:true //开启黏贴功能 paste:"#uploader" } ); 大文件分块上传 1)在webuploader全局参数中添加分块上传参数 //1.初始化WebUpload,以及配置全局的参数 var uploader = WebUploader.create( { //flashk控件的地址 swf: "${pageContext.request.contextPath}/js/Uploader.swf",//禁用页面其他地方的拖拽功能,防止页面直接打开文件 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,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",//当前分块下标 chunk:block.chunk,//当前分块大小 chunkSize:block.end-block.start },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,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 },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,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文件上传下载实例讲解(酷炫的文件上传技术),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |