百度Web Uploader组件实现文件上传之分片上传(一)
当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。多的不说了直接怼代码
前端是三个监听:一个是获取md5,一个是分片,最后一个是合并代码
<!DOCTYPE html> <html ng-app="uploadApp" ng-controller="uploadCtl"> <head> <title>fileupload.html</title> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="this is my page"> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" href="../themes/reset.css" type="text/css"></link> <link rel="stylesheet" href="../themes/webuploader.css" type="text/css"></link> <script type="text/javascript" src="../plugins/jquery-3.2.0.min.js"></script> <script type="text/javascript" src="../plugins/webuploader.min.js"></script> <script type="text/javascript"> $(function(){ var fileName; var fileMd5; //监听分块上传过程中的三个时间点 WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send":"beforeSend", "after-send-file":"afterSendFile", },{ //时间点1:所有分块进行上传之前调用此函数 beforeSendFile:function(file){ fileName=file.name; var deferred = WebUploader.Deferred(); //1、使用md5计算文件的唯一标记,用于断点续传 (new WebUploader.Uploader()).md5File(file,0,10*1024*1024) .progress(function(percentage){ $('#item1').find("p.state").text("正在读取文件信息..."); }) .then(function(val){ fileMd5=val; $('#item1').find("p.state").text("成功获取文件信息..."); //获取文件信息后进入下一步 deferred.resolve(); }); //调用deferred.resolve();无效 return deferred.promise(); }, //时间点2:如果有分块上传,则每个分块上传之前调用此函数 beforeSend:function(block,file){ console.log("分块"+fileName.replace(/.+\./, "")); var deferred = WebUploader.Deferred(); $.ajax({ type:"POST", url:"/servlet/mergeFile?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; deferred.resolve(); return deferred.promise(); }, //时间点3:所有分块上传成功后调用此函数 afterSendFile:function(){ console.log("合并") ; //如果分块上传成功,则通知后台合并分块 $.ajax({ type:"POST", url:"/servlet/mergeFile?action=mergeChunks", data:{ fileMd5:fileMd5, }, success:function(response){ alert("上传成功"); var path = "uploads/"+fileMd5+".mp4"; $("#item1").attr("src",path); } }); } }); var uploader = WebUploader.create({ // swf文件路径 swf: '<%=basePath%>scripts/webuploader-0.1.5/Uploader.swf', // 文件接收服务端。 server: '/servlet/FileUpLoad', // 选择文件的按钮。可选。 // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick: {id: '#picker', //这个id是你要点击上传文件的id,自己设置就好</span> multiple:true}, // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传! resize: true, auto:true, //上传并发数 threads:5, //开启分片上传 chunked: true, chunkSize:10*1024*1024, /* accept: { //限制上传文件为MP4 extensions: 'mp4', mimeTypes: 'video/mp4', } */ }); // 当有文件被添加进队列的时候 uploader.on( 'fileQueued', function( file ) { $('#item1').empty(); $('#item1').html('<div id="' + file.id + '" class="item">'+ '<a class="upbtn" id="btn" onclick="stop()">[取消上传]</a>'+ '' + '<p class="state">等待上传...</div>' ); }); // 文件上传过程中创建进度条实时显示。 uploader.on( 'uploadProgress', function( file, percentage ) { $('#item1').find('p.state').text(file.name+'上传中 '+Math.round(percentage * 100) + '%'); }); uploader.on( 'uploadSuccess', function( file ) { $( '#'+file.id ).find('p.state').text('已上传'); }); uploader.on( 'uploadError', function( file ) { $( '#'+file.id ).find('p.state').text('上传出错'); }); uploader.on( 'uploadComplete', function( file ) { $( '#'+file.id ).find('.progress').fadeOut(); }); function start(){ uploader.upload(); $('#btn').attr("onclick","stop()"); $('#btn').text("取消上传"); } function stop(){ uploader.stop(true); $('#btn').attr("onclick","start()"); $('#btn').text("继续上传"); } }); </script> </head> <body> <div id="uploader" class="wu-example"> <!--用来存放文件信息--> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="picker">选择文件</div> <button id="ctlBtn" class="btn btn-default">开始上传</button> </div> </div> <div id="item1"></div> </body> </html>
后端:一个保持文件servlet,一个判断分片和合并文件servelt
保持文件servlet
import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.List; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Path; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; @Path("/fileupload") public class ZFileCommand extends HttpServlet{ private static final long serialVersionUID = -2720014423604662780L; // 1.文件上传路径 private static final String UPLOAD_DIRECTORY = "D:/文件上传"; // 2.设置临时存储文件大小,当超过大小时,将先存储超出大小文件在临时目录 private static final int MEMORY_THRESHOLD = 1024 * 1024 * 30; // 3.设置最大文件上传值 private static final int MAX_FILE_SIZE = 1024 * 1024 * 2000; // 4.最大请求值 private static final int MAX_REQUEST_SIZE = 1024 * 1024 * 2048; public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); //获取文件名 String filename=request.getParameter("name"); //防止读取name名乱码 filename=new String(filename.getBytes("iso-8859-1"),"utf-8"); //在控制台打印文件名 System.out.println("文件名:"+filename); //设置文件MIME类型 response.setContentType(getServletContext().getMimeType(filename)); //设置Content-Disposition String realName = filename.substring(filename.indexOf("_")+1); response.setHeader("Content-Disposition", "attachment;filename="+realName); //输入流为项目文件,输出流指向浏览器 InputStream is=new FileInputStream(UPLOAD_DIRECTORY+filename); ServletOutputStream os =response.getOutputStream(); /* * 设置缓冲区 * is.read(b)当文件读完时返回-1 */ int len=-1; byte[] b=new byte[1024]; while((len=is.read(b))!=-1){ os.write(b,0,len); } //关闭流 is.close(); os.close(); } /** * @摘要 提供文件上传的方法 */ public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { //1.设置字符编码为utf-8 request.setCharacterEncoding("utf-8"); response.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); // 2.检测是否为多媒体上传 if (!ServletFileUpload.isMultipartContent(request)) { // 2.1如果不是则停止 PrintWriter writer = response.getWriter(); writer.println("Error: 表单必须包含 enctype=multipart/form-data"); writer.flush(); return ; } // 3.配置上传参数 DiskFileItemFactory factory = new DiskFileItemFactory(); //4. 设置内存临界值 - 超过后将产生临时文件并存储于临时目录中 factory.setSizeThreshold(MEMORY_THRESHOLD); // 5.设置临时存储目录 java.io.tmpdir默认的临时文件路径为服务器的temp目录 factory.setRepository(new File(System.getProperty("java.io.tmpdir"))); ServletFileUpload upload = new ServletFileUpload(factory); // 6.设置最大文件上传值 upload.setFileSizeMax(MAX_FILE_SIZE); // 7.设置最大请求值 (包含文件和表单数据) upload.setSizeMax(MAX_REQUEST_SIZE); //8. 如果目录不存在则创建 File uploadDir = new File(UPLOAD_DIRECTORY); if (!uploadDir.exists()) { uploadDir.mkdir(); } String fileMd5 = null; String chunk = null; try { // 10.解析请求的内容提取文件数据 List<FileItem> formItems = upload.parseRequest(request); // 10.1迭代表单数据 if (formItems != null && formItems.size() > 0) { for (FileItem item : formItems) { if (item.isFormField()) { String fieldName = item.getFieldName(); if(fieldName.equals("fileMd5")){ fileMd5 = item.getString("utf-8"); } if(fieldName.equals("chunk")){ chunk = item.getString("utf-8"); } }else{ String nFileName = new File(item.getName()).getName(); File file = new File(UPLOAD_DIRECTORY+"/"+fileMd5); if(!file.exists()){ file.mkdir(); } nFileName=nFileName.substring(0,nFileName.lastIndexOf(".")) ; item.write(new File(UPLOAD_DIRECTORY+"/"+fileMd5+"/"+chunk)); item.delete(); } } } } catch (Exception ex) { PrintWriter writer=response.getWriter(); writer.print("error"); } } }
一个判断分片和合并文件servelt
import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class mergeFile extends HttpServlet { private static final long serialVersionUID = 1L; private static final String UPLOAD_DIRECTORY = "D:/文件上传"; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doGet(request, response); doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String savePath = this.getServletConfig().getServletContext() .getRealPath(""); String folad = "uploads"; savePath = "D:/文件上传"; String action = request.getParameter("action"); if(action.equals("mergeChunks")){ //合并文件 //需要合并的文件的目录标记 String fileMd5 = request.getParameter("fileMd5"); //读取目录里的所有文件 File f = new File(savePath+"/"+fileMd5); File[] fileArray = f.listFiles(new FileFilter(){ //排除目录只要文件 public boolean accept(File pathname) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub if(Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())){ return -1; } return 1; } }); //UUID.randomUUID().toString()-->随机名 File outputFile = new File(savePath+"/"+fileMd5+".mp4"); //创建文件 outputFile.createNewFile(); //输出流 FileChannel outChnnel = new FileOutputStream(outputFile).getChannel(); //合并 FileChannel inChannel; for(File file : fileList){ inChannel = new FileInputStream(file).getChannel(); inChannel.transferTo(0, inChannel.size(), outChnnel); inChannel.close(); //删除分片 file.delete(); } outChnnel.close(); //清除文件夹 File tempFile = new File(savePath+"/"+fileMd5); if(tempFile.isDirectory() && tempFile.exists()){ tempFile.delete(); } System.out.println("合并成功"); }else if(action.equals("checkChunk")){ //检查当前分块是否上传成功 String fileMd5 = request.getParameter("fileMd5"); //分块次数 String chunk = request.getParameter("chunk"); //分块大小 String chunkSize = request.getParameter("chunkSize"); File checkFile = new File(savePath+"/"+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}"); } } }
附件:源码