欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SSM框架+Plupload实现分块上传大文件示例

程序员文章站 2024-03-05 20:11:43
关于plupload的介绍,相信它的官网已经给得很详细了。plupload的上传原理简单点说,就是将用户选中的文件(可多个)分隔成一个个小块,依次向服务器上传,这是它能驾驭...

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

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

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

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

下载地址:,我下的是plupload 2.1.9 gplv2版本的,里面有要用到的css以及js。

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

二:前端准备

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

<div id="uploader">
 <p>your browser doesn't have flash, silverlight or html5 support.</p>
</div>
<button id="tostop">暂停一下</button>
<button id="tostart">再次开始</button>

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

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

<script type="text/javascript">
 $(function() {
  // initialize the widget when the dom is ready
  var uploader = $("#uploader").pluploadqueue({
   // general settings
   runtimes: 'html5,flash,silverlight,html4',
   url: "../pluploadupload",

   // maximum file size
   max_file_size: '10000mb',

   chunk_size: '1mb',

   // resize images on clientside if we can
   resize: {
    width: 200,
    height: 200,
    quality: 90,
    crop: true // crop to exact dimensions
   },

   // specify what files to browse for
   filters: [
    {title: "image files", extensions: "jpg,gif,png"},
    {title: "vedio files", extensions: "mp4,mkv"},
    {title: "zip files", extensions: "zip,avi"}
   ],

   // rename files by clicking on their titles
   rename: true,

   // sort files
   sortable: true,

   // enable ability to drag'n'drop files onto the widget (currently only html5 supports that)
   dragdrop: true,

   // views to activate
   views: {
    list: true,
    thumbs: true, // show thumbs
    active: 'thumbs'
   },

   // flash settings
   flash_swf_url: 'js/moxie.swf',

   // silverlight settings
   silverlight_xap_url: 'js/moxie.xap'
  });

  $("#tostop").on('click', function () {
   uploader.stop();
  });

  $("#tostart").on('click', function () {
   uploader.start();
  });
 });
</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映射

  @autowired
  private pluploadservice pluploadservice;

  /**plupload文件上传处理方法*/
  @requestmapping(value="/pluploadupload")
  public void upload(plupload plupload,httpservletrequest request,httpservletresponse response) {

    string filedir = "pluploaddir";//文件保存的文件夹
    plupload.setrequest(request);//手动传入plupload对象httpservletrequest属性

    int userid = ((user)request.getsession().getattribute("user")).getuserid();

    //文件存储绝对路径,会是一个文件夹,项目相应servlet容器下的"pluploaddir"文件夹,还会以用户唯一id作划分
    file dir = new file(request.getsession().getservletcontext().getrealpath("/") + filedir+"/"+userid);
    if(!dir.exists()){
      dir.mkdirs();//可创建多级目录,而mkdir()只能创建一级目录
    }
    //开始上传文件
    pluploadservice.upload(plupload, dir);
  }

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

四:plupload类

package web.plupload;

import org.springframework.web.multipart.multipartfile;

import javax.servlet.http.httpservletrequest;

/**
 * plupload实体类固定格式,属性名不可修改
 * 因为multipartfile要用到spring web的依赖,而该依赖在web模块中才引入,所以不把该实体类放在entity模块
 */
public class plupload {
  /**文件原名*/
  private string name;
  /**用户上传资料被分解总块数*/
  private int chunks = -1;
  /**当前块数(从0开始计数)*/
  private int chunk = -1;
  /**httpservletrequest对象,不会自动赋值,需要手动传入*/
  private httpservletrequest request;
  /**保存文件上传信息,不会自动赋值,需要手动传入*/
  private multipartfile multipartfile;

  public string getname() {
    return name;
  }

  public void setname(string name) {
    this.name = name;
  }

  public int getchunks() {
    return chunks;
  }

  public void setchunks(int chunks) {
    this.chunks = chunks;
  }

  public int getchunk() {
    return chunk;
  }

  public void setchunk(int chunk) {
    this.chunk = chunk;
  }

  public httpservletrequest getrequest() {
    return request;
  }

  public void setrequest(httpservletrequest request) {
    this.request = request;
  }

  public multipartfile getmultipartfile() {
    return multipartfile;
  }

  public void setmultipartfile(multipartfile multipartfile) {
    this.multipartfile = multipartfile;
  }
}

再次提醒类名与属性名不可随意改变。通过规定好的正确的属性名,客户端通过http发送块文件至服务端controller,得到的plupload对象才能传入正确的文件信息。

关于属性的说明代码注释已经说得很清楚了,可以自行研究学习。

五:pluploadservice类

package web.plupload;

import entity.user;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.component;
import org.springframework.util.multivaluemap;
import org.springframework.web.multipart.multipartfile;
import org.springframework.web.multipart.multiparthttpservletrequest;

import java.io.*;
import java.sql.timestamp;
import java.util.iterator;
import java.util.list;

import service.youandmeservice;

/**
 * plupload service模块,同plupload实体类一样,因为要用到spring web相关依赖,所以不将其放在service模块
 */
@component  //将写好的类注入springioc容器中让controller自动装载
public class pluploadservice {

  @autowired
  private youandmeservice youandmeservice;

  public void upload(plupload plupload,file pluploaddir){
    string filename = ""+system.currenttimemillis()+plupload.getname();//在服务器内生成唯一文件名
    upload(plupload, pluploaddir, filename);
  }

  private void upload(plupload plupload,file pluploaddir,string filename){

    int chunks = plupload.getchunks();//用户上传文件被分隔的总块数
    int nowchunk = plupload.getchunk();//当前块,从0开始

    //这里request请求类型的强制转换可能出错,配置文件中向springioc容器引入multipartresolver对象即可。
    multiparthttpservletrequest multiparthttpservletrequest = (multiparthttpservletrequest)plupload.getrequest();
    //调试发现map中只有一个键值对
    multivaluemap<string,multipartfile> map = multiparthttpservletrequest.getmultifilemap();

    if(map!=null){
      try{
        iterator<string> iterator = map.keyset().iterator();
        while(iterator.hasnext()){

          string key = iterator.next();
          list<multipartfile> multipartfilelist = map.get(key);

          for(multipartfile multipartfile:multipartfilelist){//循环只进行一次

            plupload.setmultipartfile(multipartfile);//手动向plupload对象传入multipartfile属性值
            file targetfile = new file(pluploaddir+"/"+filename);//新建目标文件,只有被流写入时才会真正存在
            if(chunks>1){//用户上传资料总块数大于1,要进行合并

              file tempfile = new file(pluploaddir.getpath()+"/"+multipartfile.getname());
              //第一块直接从头写入,不用从末端写入
              savepluploadfile(multipartfile.getinputstream(),tempfile,nowchunk==0?false:true);

              if(chunks-nowchunk==1){//全部块已经上传完毕,此时targetfile因为有被流写入而存在,要改文件名字
                tempfile.renameto(targetfile);

                //每当文件上传完毕,将上传信息插入数据库
                timestamp now = new timestamp(system.currenttimemillis());
                youandmeservice.uploadinfo(filename,((user)(plupload.getrequest().getsession().getattribute("user"))).getusername(),now);
              }
            }
            else{
              //只有一块,就直接拷贝文件内容
              multipartfile.transferto(targetfile);

              //每当文件上传完毕,将上传信息插入数据库
              timestamp now = new timestamp(system.currenttimemillis());
              youandmeservice.uploadinfo(filename, ((user) (plupload.getrequest().getsession().getattribute("user"))).getusername(), now);
            }
          }
        }
      }
      catch (ioexception e){
        e.printstacktrace();
      }
    }
  }
  private void savepluploadfile(inputstream inputstream,file tempfile,boolean flag){
    outputstream outputstream = null;
    try {
      if(flag==false){
        //从头写入
        outputstream = new bufferedoutputstream(new fileoutputstream(tempfile));
      }
      else{
        //从末端写入
        outputstream = new bufferedoutputstream(new fileoutputstream(tempfile,true));
      }
      byte[] bytes = new byte[1024];
      int len = 0;
      while ((len = (inputstream.read(bytes)))>0){
        outputstream.write(bytes,0,len);
      }
    }
    catch (filenotfoundexception e){
      e.printstacktrace();
    }
    catch (ioexception e){
      e.printstacktrace();
    }
    finally {
      try{
        outputstream.close();
        inputstream.close();
      }
      catch (ioexception e){
        e.printstacktrace();
      }
    }
  }
}

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的对象

<bean id="multipartresolver"
     class="org.springframework.web.multipart.commons.commonsmultipartresolver">
    <!-- set the max upload size100mb -->
    <property name="maxuploadsize">
      <value>104857600</value>
    </property>
    <property name="maxinmemorysize">
      <value>4096</value>
    </property>
    <property name="defaultencoding" value="utf-8"></property>
  </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))。

写到这里,基于plupload实现断点续传的代码已经全部给出了,大家要自己整合至项目中,这里没有给出完整的demo,嗯还是那句话,授之于鱼不如授之以渔。

上传项目效果图:

1.选定上传文件:

SSM框架+Plupload实现分块上传大文件示例

2.开始上传,有进度条显示:

SSM框架+Plupload实现分块上传大文件示例

3.暂停上传:

SSM框架+Plupload实现分块上传大文件示例

4.暂停后继续上传:

SSM框架+Plupload实现分块上传大文件示例

5.上传完毕:

SSM框架+Plupload实现分块上传大文件示例

6.目标文件夹下有相应的文件:

SSM框架+Plupload实现分块上传大文件示例

7.上传过程中的网络请求,体现分块上传:

SSM框架+Plupload实现分块上传大文件示例

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。