PLupload实现断点续传

关于Plupload的介绍,相信它的官网http://www.plupload.com/已经给得很详细了。Plupload的上传原理简单点说,就是将用户选中的文件(可多个)分隔成一个个小块,依次向服务器上传,这是它能驾驭上传大文件的原因之一,而且在这个过程可以暂停上传,暂停后再继续上传。最重要的是,从头到尾没有一点点UI阻塞,保证了用户体验。下面会开始讲Plupload的实现流程,分析原理,并在最后给出效果图。


在此之前先说说我的项目,做的j2ee项目运用到Spring+SpringMVC+MyBatis的框架集合,是关于一个社交平台的网站,类似于facebook,twitter,微博等,起了一个名字叫YouAndMe。我大胆地构想了这个项目应该有一个用户资料共享的平台,或是一部好看的电影,或是一套电视剧,或是居家必备的食谱,也有可能是好看的风景图,各式各样。用户可以搜索想要的资料并下载。因此首先要解决的就是各式各样(大)文件的上传。


一:下载Plupload插件并引入相应文件

  1. 值得一提的是这个插件只是前端的,后台怎么获取怎么将一个个小块合并等代码是要自己写的。

  2. 下载地址:http://www.plupload.com/download,我下的是Plupload 2.1.9 GPLv2版本的,里面有要用到的css以及js。

  3. 在项目中需要引入:jQuery.plupload.queue.css,jquery-2.0.0.min.js,plupload.full.min.js,jquery.plupload.queue.js,zh_CN.js这些文件。


二:前端准备

1.首先在html中写入如下代码:

[html]  view plain  copy
  1. <div id="uploader">  
  2.   <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>  
  3. </div>  
  4. <button id="toStop">暂停一下</button>  
  5. <button id="toStart">再次开始</button>  

注意div的id必须是uploader,这在插件源码里是有规定的;id为toStop与toStart的按钮是我自己加的,目的是为了实现暂停上传与暂停过后的继续上传。

2.页面加载后通过js初始化组件

[javascript]  view plain  copy
  1. <div id="uploader">  
  2.   <p>Your browser doesn't have Flash, Silverlight or HTML5 support.</p>  
  3. </div>  
  4. <button id="toStop">暂停一下</button>  
  5. <button id="toStart">再次开始</button>  
  6.   
  7.    
  8.   
  9. 注意div的id必须是uploader,这在插件源码里是有规定的;id为toStop与toStart的按钮是我自己加的,目的是为了实现暂停上传与暂停过后的继续上传。  
  10.   
  11. 2.页面加载后通过js初始化组件  
  12.   
  13. <script type="text/javascript">  
  14.   $(function() {  
  15.     // Initialize the widget when the DOM is ready  
  16.     var uploader = $("#uploader").pluploadQueue({  
  17.       // General settings  
  18.       runtimes: 'html5,flash,silverlight,html4',  
  19.       url: "../pluploadUpload",  
  20.   
  21.       // Maximum file size  
  22.       max_file_size: '10000mb',  
  23.   
  24.       chunk_size: '1mb',  
  25.   
  26.       // Resize images on clientside if we can  
  27.       resize: {  
  28.         width: 200,  
  29.         height: 200,  
  30.         quality: 90,  
  31.         crop: true // crop to exact dimensions  
  32.       },  
  33.   
  34.       // Specify what files to browse for  
  35.       filters: [  
  36.         {title: "Image files", extensions: "jpg,gif,png"},  
  37.         {title: "Vedio files", extensions: "mp4,mkv"},  
  38.         {title: "Zip files", extensions: "zip,avi"}  
  39.       ],  
  40.   
  41.       // Rename files by clicking on their titles  
  42.       rename: true,  
  43.   
  44.       // Sort files  
  45.       sortable: true,  
  46.   
  47.       // Enable ability to drag'n'drop files onto the widget (currently only HTML5 supports that)  
  48.       dragdrop: true,  
  49.   
  50.       // Views to activate  
  51.       views: {  
  52.         list: true,  
  53.         thumbs: true// Show thumbs  
  54.         active: 'thumbs'  
  55.       },  
  56.   
  57.       // Flash settings  
  58.       flash_swf_url: 'js/Moxie.swf',  
  59.   
  60.       // Silverlight settings  
  61.       silverlight_xap_url: 'js/Moxie.xap'  
  62.     });  
  63.   
  64.     $("#toStop").on('click'function () {  
  65.       uploader.stop();  
  66.     });  
  67.   
  68.     $("#toStart").on('click'function () {  
  69.       uploader.start();  
  70.     });  
  71.   });  
  72. </script>  


关于这部分的功能可以查看pluploadQueue的文档:http://www.plupload.com/docs/pluploadQueue。也很容易看懂,这里简单地说说部分参数的意义。

url就是服务器处理该上传的地址。filters是过滤器的意思,规定哪些格式的文件可以上传。dragdrop:true设置了可以拖拽文件至选定框。

注意:在暂停与继续上传时要用到uploader.stop()uploader.start(),这个uploader实例由$("#uploader").pluploadQueue({...})时产生。在官网给出的例子中有两种情况:一是注册时一次性全部给定参数,但是这样是不会返回一个uploader实例的;二是注册时不给参数,会返回uploader实例,再对这个uploader实例绑定事件时一步步给出参数。但很明显我这里给定了参数又同时返回了一个uploader实例,只要修改一个源码:打开jquery.plupload.queue.js源码找到定义pluploadQueue这块,将if (settings) 内的返回return this,改成return uploaders[$(this[0]).attr('id')]。这样,点击暂停按钮时,当前上传会暂停,点击开始按钮时,又继续。


三:Controller映射

[java]  view plain  copy
  1. /**Plupload文件上传处理方法*/  
  2.     @RequestMapping(value="/pluploadUpload")  
  3.     public void upload(Plupload plupload,HttpServletRequest request,HttpServletResponse response) {  
  4.   
  5.         String FileDir = "pluploadDir";//文件保存的文件夹  
  6.         plupload.setRequest(request);//手动传入Plupload对象HttpServletRequest属性  
  7.   
  8.         int userId = ((User)request.getSession().getAttribute("user")).getUserId();  
  9.   
  10.         //文件存储绝对路径,会是一个文件夹,项目相应Servlet容器下的"pluploadDir"文件夹,还会以用户唯一id作划分  
  11.         File dir = new File(request.getSession().getServletContext().getRealPath("/") + FileDir+"/"+userId);  
  12.         if(!dir.exists()){  
  13.             dir.mkdirs();//可创建多级目录,而mkdir()只能创建一级目录  
  14.         }  
  15.         //开始上传文件  
  16.         PluploadService.upload(plupload, dir);  
  17.     }  


在这里,我规定不同用户上传的资料会根据唯一id分开不同的文件夹,基于注释代码很容易看懂,你或许会困惑于Plupload与PluploadService,下面就会给出。


四:Plupload类

 
[java]  view plain  copy
  1. package web.plupload;  
  2.   
  3. import org.springframework.web.multipart.MultipartFile;  
  4.   
  5. import javax.servlet.http.HttpServletRequest;  
  6.   
  7. /** 
  8.  * Plupload实体类固定格式,属性名不可修改 
  9.  * 因为MultipartFile要用到Spring web的依赖,而该依赖在web模块中才引入,所以不把该实体类放在entity模块 
  10.  */  
  11. public class Plupload {  
  12.     /**文件原名*/  
  13.     private String name;  
  14.     /**用户上传资料被分解总块数*/  
  15.     private int chunks = -1;  
  16.     /**当前块数(从0开始计数)*/  
  17.     private int chunk = -1;  
  18.     /**HttpServletRequest对象,不会自动赋值,需要手动传入*/  
  19.     private HttpServletRequest request;  
  20.     /**保存文件上传信息,不会自动赋值,需要手动传入*/  
  21.     private MultipartFile multipartFile;  
  22.   
  23.     public String getName() {  
  24.         return name;  
  25.     }  
  26.   
  27.     public void setName(String name) {  
  28.         this.name = name;  
  29.     }  
  30.   
  31.     public int getChunks() {  
  32.         return chunks;  
  33.     }  
  34.   
  35.     public void setChunks(int chunks) {  
  36.         this.chunks = chunks;  
  37.     }  
  38.   
  39.     public int getChunk() {  
  40.         return chunk;  
  41.     }  
  42.   
  43.     public void setChunk(int chunk) {  
  44.         this.chunk = chunk;  
  45.     }  
  46.   
  47.     public HttpServletRequest getRequest() {  
  48.         return request;  
  49.     }  
  50.   
  51.     public void setRequest(HttpServletRequest request) {  
  52.         this.request = request;  
  53.     }  
  54.   
  55.     public MultipartFile getMultipartFile() {  
  56.         return multipartFile;  
  57.     }  
  58.   
  59.     public void setMultipartFile(MultipartFile multipartFile) {  
  60.         this.multipartFile = multipartFile;  
  61.     }  
  62. }  

 
  1. 再次提醒类名与属性名不可随意改变。通过规定好的正确的属性名,客户端通过Http发送块文件至服务端Controller,得到的plupload对象才能传入正确的文件信息。
  2. 关于属性的说明代码注释已经说得很清楚了,可以自行研究学习。

五:PluploadService类

 
[java]  view plain  copy
  1. package web.plupload;  
  2.   
  3. import org.springframework.util.MultiValueMap;  
  4. import org.springframework.web.multipart.MultipartFile;  
  5. import org.springframework.web.multipart.MultipartHttpServletRequest;  
  6.   
  7. import java.io.*;  
  8. import java.util.Iterator;  
  9. import java.util.List;  
  10.   
  11. /** 
  12.  * Plupload Service模块,同Plupload实体类一样,因为要用到Spring web相关依赖,所以不将其放在Service模块 
  13.  */  
  14. public class PluploadService {  
  15.   
  16.     public static void upload(Plupload plupload,File pluploadDir){  
  17.         String fileName = ""+System.currentTimeMillis()+plupload.getName();//在服务器内生成唯一文件名  
  18.         upload(plupload,pluploadDir,fileName);  
  19.     }  
  20.   
  21.     private static void upload(Plupload plupload,File pluploadDir,String fileName){  
  22.   
  23.         int chunks = plupload.getChunks();//用户上传文件被分隔的总块数  
  24.         int nowChunk = plupload.getChunk();//当前块,从0开始  
  25.   
  26.         //这里Request请求类型的强制转换可能出错,配置文件中向SpringIOC容器引入multipartResolver对象即可。  
  27.         MultipartHttpServletRequest multipartHttpServletRequest  = (MultipartHttpServletRequest)plupload.getRequest();  
  28.         //调试发现map中只有一个键值对  
  29.         MultiValueMap map = multipartHttpServletRequest.getMultiFileMap();  
  30.   
  31.         if(map!=null){  
  32.             try{  
  33.                 Iterator iterator = map.keySet().iterator();  
  34.                 while(iterator.hasNext()){  
  35.   
  36.                     String key = iterator.next();  
  37.                     List multipartFileList = map.get(key);  
  38.   
  39.                     for(MultipartFile multipartFile:multipartFileList){//循环只进行一次  
  40.   
  41.                         plupload.setMultipartFile(multipartFile);//手动向Plupload对象传入MultipartFile属性值  
  42.                         File targetFile = new File(pluploadDir+"/"+fileName);//新建目标文件,只有被流写入时才会真正存在  
  43.                         if(chunks>1){//用户上传资料总块数大于1,要进行合并  
  44.   
  45.                             File tempFile = new File(pluploadDir.getPath()+"/"+multipartFile.getName());  
  46.                             //第一块直接从头写入,不用从末端写入  
  47.                             savePluploadFile(multipartFile.getInputStream(),tempFile,nowChunk==0?false:true);  
  48.   
  49.                             if(chunks-nowChunk==1){//全部块已经上传完毕,此时targetFile因为有被流写入而存在,要改文件名字  
  50.                                 tempFile.renameTo(targetFile);  
  51.                             }  
  52.                         }  
  53.                         else{  
  54.                             //只有一块,就直接拷贝文件内容  
  55.                             multipartFile.transferTo(targetFile);  
  56.                         }  
  57.                     }  
  58.                 }  
  59.             }  
  60.             catch (IOException e){  
  61.                 e.printStackTrace();  
  62.             }  
  63.         }  
  64.     }  
  65.     private static void savePluploadFile(InputStream inputStream,File tempFile,boolean flag){  
  66.         OutputStream outputStream = null;  
  67.         try {  
  68.             if(flag==false){  
  69.                 //从头写入  
  70.                 outputStream = new BufferedOutputStream(new FileOutputStream(tempFile));  
  71.             }  
  72.             else{  
  73.                 //从末端写入  
  74.                 outputStream = new BufferedOutputStream(new FileOutputStream(tempFile,true));  
  75.             }  
  76.             byte[] bytes = new byte[1024];  
  77.             int len = 0;  
  78.             while ((len = (inputStream.read(bytes)))>0){  
  79.                 outputStream.write(bytes,0,len);  
  80.             }  
  81.         }  
  82.         catch (FileNotFoundException e){  
  83.             e.printStackTrace();  
  84.         }  
  85.         catch (IOException e){  
  86.             e.printStackTrace();  
  87.         }  
  88.         finally {  
  89.             try{  
  90.                 outputStream.close();  
  91.                 inputStream.close();  
  92.             }  
  93.             catch (IOException e){  
  94.                 e.printStackTrace();  
  95.             }  
  96.         }  
  97.     }  
  98. }  

 

1.PluploadService这个类名是我自己起的,还可以吧~

2.在Controller的最后一行PluploadService.upload(plupload, dir);中将客户端提交至服务器生成的plupload对象与规定保存的文件夹目录,以参数的形式传入PluploadService的upload方法中。

3.在upload(Plupload plupload,File pluploadDir)方法中,为文件生成一个唯一的文件名以便存储在服务器中。

4.chunks是用户一次性选中要上传的文件被分隔后的总块数;nowChunk是这次上传中块的编号,从0开始,为什么用”这次“呢?前面提到过Plupload就是依次地将块从客户端提交至服务器,因此在文件上传中,会有很多次Http请求,而chunks是不变的,nowChunk会一次次增加。

5.将HttpServletRequest强制转换为MultipartHttpServletRequest时可能会出错,但这个错误可以避免,只需在SpringIOC容器中注入一个名为multipartResolver的对象

 
[html]  view plain  copy
  1. <bean id="multipartResolver"  
  2.           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">  
  3.         <!-- set the max upload size100MB -->  
  4.         <property name="maxUploadSize">  
  5.             <value>104857600</value>  
  6.         </property>  
  7.         <property name="maxInMemorySize">  
  8.             <value>4096</value>  
  9.         </property>  
  10.         <property name="defaultEncoding" value="UTF-8"></property>  
  11.     </bean>  
 

6.通过MultipartHttpServletRequest拿到MultiValueMap(经过调试发现这个map只有一对键值对),其Value类型为MultipartFile,这个MultipartFile其实就是当前的块,也难怪为什么map中只有一个键值对了。

7.plupload.setMultipartFile(multipartFile);手动为plupload对象传入MultipartFile属性值

8.如果总块数chunks大于1,那就要考虑将上传过来的一个个小块合成一个文件,否则那就直接拷贝块文件到目标文件multipartFile.transferTo(targetFile);

9.在chunks大于1时,首先要新建一个临时文件tempFile,用于不断不断将一个个小块写入这个tempFile,等写完后(chunks-nowChunk==1),就将其重命名(tempFile.renameTo(targetFile);)。

10.savePluploadFile(multipartFile.getInputStream(),tempFile,nowChunk==0?false:true);方法用于合并一个个小块文件,如果是第一块的话,就从头开始写入(new FileOutputStream(tempFile)),否则全部从末端写入(new FileOutputStream(tempFile,true))。


转载:https://blog.csdn.net/qq_19707521/article/details/52288139

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值