大文件上传

  • Post author:
  • Post category:其他



前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 1.引入文件 -->
    <link rel="stylesheet" type="text/css" href="/css/webuploader.css">

    <script type="text/javascript" src="/js/jquery-2.1.4.min.js"></script>
    <script type="text/javascript" src="/js/webuploader.js"></script>
 
</head>
<body>
<!-- 2.创建页面元素 -->
<div id="uploader">
    <!-- 创建用于拖拽的区域:
        把文件拖到这里,它就会自动上传 -->
    <#--<div id="dndArea">-->
        <#--<p style="font-size: 8px">把文件拖到这里,它就会自动上传</p>-->
    <#--</div>-->

    <!-- 用于显示文件列表 -->
    <div id="fileListDiv" class="container">
        <table id="fileList" class="table table-striped" border="1" width="800px">
            <tr class="filelist-head">
                <th class="file-name">文件名称</th>
                <th class="file-size">大小</th>
                <th width="20%" class="file-pro">进度</th>
                <th class="file-status">状态</th>
                <th width="20%" class="file-manage">操作</th>
            </tr>
        </table>
    </div>

    <!-- 用于选择文件:
        手动选择文件
        filePicker这个控件ID是固定的-->
    <div id="filePicker">文件上传</div>
</div>
<!-- 3.添加js代码 -->
<script type="text/javascript">

    // 监听分块上传的时间点,断点续传
    var fileMd5=[];
    //文件名,用于最后合并时生成相同名字的文件,最好不用中文文件名
    var fileName=[];
    var count=0;//当前正在上传的文件在数组中的下标,一次上传多个文件时使用
    var filesArr=new Array();
    //注册事件函数
    WebUploader.Uploader.register({
            "before-send-file":"beforeSendFile",
            "before-send":"beforeSend",
            "after-send-file":"afterSendFile"
        },{
            //beforeSendFile先于beforeSend事件执行
            beforeSendFile:function(file) {
               fileName.push(file.name);
               // fileName=file.name;
                console.info("上传文件:" + fileName);
                // 创建一个deffered,用于通知是否完成操作
                var deferred = WebUploader.Deferred();
                // 计算文件的唯一标识MD5值,用于断点续传和妙传
                (new WebUploader.Uploader()).md5File(file, 0, 5*1024*1024)
                    .progress(function(percentage){
                      //  $("#"+file.id).find("span.state").text("正在获取文件信息...");
                    })
                    .then(function(val) {
                       // fileMd5 = val;
                        fileMd5.push(val);
                        //$("#" + file.id).find("span.state").text("成功获取文件信息");
                        //放行

                        deferred.resolve();
                    });
                // 通知完成操作
                return deferred.promise();
            },
            beforeSend:function(block) {
                var deferred = WebUploader.Deferred();

                // 支持断点续传,发送到后台判断是否已经上传过
                $.ajax({
                        type:"POST",
                        url:"/check",//后台处理分块接口
                    async: false,
                        data:{
                            // 文件唯一表示
                            fileMd5:fileMd5[0],
                            // 当前分块下标(插件会自动生成,至于生成多少块,插件决定)
                            chunk:block.chunk,
                            // 当前分块大小
                            chunkSize:block.end-block.start
                        },
                        dataType:"json",
                        success:function(response) {
                            if(response.ifExist) {
                                // 分块存在,跳过该分块
                                deferred.reject();
                            } else {
                                // 分块不存在或不完整,重新发送
                                deferred.resolve();
                            }
                        }
                    }
                );

                // 发送文件md5字符串到后台
                this.owner.options.formData.fileMd5 = fileMd5;
                return deferred.promise();
            },
            afterSendFile:function(file) {
                // 当所有分块都发送完毕之后,通知后台合并分块
                var name =fileName.pop();
                var md5=fileMd5.pop();
                async: false,
                $.ajax({
                    type:"POST",
                    url:"/union",//调用后台合并分块的接口
                    data:{
                        fileMd5:md5,
                        fileName:name, //原文件名要传到后台,作为合并后文件的文件名
                        //fileSize:file.get
                        fileSize:file.size
                    },
                    success:function(response){


                    }
                });
            }
        }
    );

    // 上传基本配置--具体上传器,上传每个块
    var uploader = WebUploader.create({
        swf:"/js/Uploader.swf",
        server:"/bigupload",//后台的上传接口,用于接收每个块
        pick:"#filePicker",
        auto:true,
        // dnd:"#dndArea", //拖拽区域
        disableGlobalDnd:true,
        paste:"#uploader",

        // 分块上传设置
        // 是否分块
        chunked:true,
        // 每块文件大小(默认5M)
        chunkSize:5*1024*1024,
        // 开启几个并行线程(默认3个)
        threads:1,
        // fileNumLimit
        // 在上传当前文件时,准备好下一个文件
        prepareNextFile:false,
        duplicate: false //是否支持重复上传
        // chunkRetry : 2, //如果某个分片由于网络问题出错,允许自动重传次数
    });

  // 生成缩略图和上传进度
    uploader.on("fileQueued", function(file) {

            //fileQueued事件
            // 把文件信息追加到fileList的div中

            var $list = $("#fileList");
            $list.append('<tr id="'+ file.id +'" class="file-item">'+
                '<td class="file-name">'+ file.name +'</td>'+
                '<td width="20%" class="file-size">'+ (file.size/1024/1024).toFixed(1)+'M' +'</td>' +
                '<td width="20%" class="file-pro">0%</td>'+
                '<td class="file-status">等待上传</td>'+
                '<td width="20%" class="file-manage"><a class="stop-btn" href="javascript:;">暂停 </a>' +
                '<a class="restart-btn" href="javascript:;">开始 </a>' + '<a class="remove-this" href="javascript:;">取消</a></td>'+
                '</tr>');

            //暂停上传的文件
            $list.on('click','.stop-btn',function(){
                uploader.stop(true);
            });
            //删除上传的文件(取消操作,这并不会重置进度,下次重传还是会从之前的进度开始)
            $list.on('click','.remove-this',function(){
                if ($(this).parents(".file-item").attr('id') == file.id) {
                    uploader.removeFile(file);
                    $(this).parents(".file-item").remove();
                }
            });
            //暂停后继续开始
            $list.on('click','.restart-btn',function(){
                // uploader.start();//用这个是错误的
                // uploader.startUpload(file);//也是错的
                //至于哪个函数才是正确的,看下源码就知道
                //正确的是:upload
                uploader.upload(file);
            });


        }
    );

    //重复添加文件
    var timer1;
    uploader.onError = function(code){
        clearTimeout(timer1);
        timer1 = setTimeout(function(){
            alert('该文件已存在');
        },250);
    };

    // 监控上传进度
    // percentage:代表上传文件的百分比

    // uploader.on("uploadProgress", function(file, percentage) {
    //     //更新进度的具体逻辑,可由自己实现
    // 	//最简单的就是展示数字,例如:87%
    // 	$("#" + file.id).find("span.percentage").text(Math.round(percentage * 100) + "%");
    // });

    //形象一点,可以展示滚动条,进度条

    // 文件上传过程中创建进度条实时显示
    uploader.on( 'uploadProgress', function( file, percentage ) {
        $("td.file-pro").text("");
        var $li = $( '#'+file.id ).find('.file-pro'),
            $percent = $li.find('.file-progress .progress-bar');

        // 避免重复创建
        if ( !$percent.length ) {
            $percent = $('<div class="progress progress-striped active">' +
                '<div class="progress-bar" role="progressbar" style="width: 0%">' +
                '<p class="per" style="line-height: 0px;">0%</p>' +
                '</div>' +
                '</div>' ).appendTo( $li ).find('.progress-bar');
        }

        $li.siblings('.file-status').text('上传中');
        //将百分比赋值到文本控件
        $li.find('.per').text((percentage * 100).toFixed(2) + '%');

        $percent.css('width', percentage * 100 + '%');


    });

    //其他事件
    // 文件上传成功
    uploader.on( 'uploadSuccess', function( file ) {
        $( '#'+file.id ).find('.file-status').text('已上传');
    });

    // 文件上传失败,显示上传出错
    uploader.on( 'uploadError', function( file ) {
        $( '#'+file.id ).find('.file-status').text('上传出错');
    });

</script>
</body>
</html>


后端代码

package com.lopo.controller;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * 合并文件
 */
@RestController
public class BigfileController {
    //文件后台统一存放位置
    private static final String serverPath = "C:\\upload";

    /**
     * 文件合并
     *
     * @param request
     * @param response
     * @throws IOException
     */
    //  String fileMd5=null;
    @PostMapping("/union")
    public void BigFileUnion(HttpServletRequest request, HttpServletResponse response) throws IOException {

        // 获得需要合并的目录
        String fileMd5 = request.getParameter("fileMd5");
        //文件的原始文件名
        String fileName = request.getParameter("fileName");
        
        Integer fileSize = Integer.valueOf(request.getParameter("fileSize"));
        System.out.println("fileSize = " + fileSize);
        // 读取目录所有文件
        File f = new File(serverPath + File.separator + fileMd5);
        File[] fileArray = f.listFiles();
//            File[] fileArray = f.listFiles(new FileFilter() {
//                // 排除目录,只要文件
//                public boolean accept(File pathname) {
//                    if (pathname.isDirectory()) {
//                        return false;
//                    }
//                    return true;
//                }
//            });

        // 转成集合,便于排序
        List<File> fileList = new ArrayList<>(Arrays.asList(fileArray));
        //System.out.println("fileList = " + fileList);
        // 从小到大排序
        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 + File.separator + File.separator + fileName);
        System.out.println("outputFile = " + outputFile);
        File parentFile = outputFile.getParentFile();
        if (!parentFile.exists()) {
            parentFile.mkdirs();
        }
        // 创建文件
        outputFile.createNewFile();

        // 输出流
        FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
        FileChannel outChannel = fileOutputStream.getChannel();

        // 合并,核心就是FileChannel,将多个文件合并为一个文件
        FileChannel inChannel;
        for (File file : fileList) {
            inChannel = new FileInputStream(file).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
            inChannel.close();
            // 删除分片
            file.delete();
        }


        // 关闭流
        fileOutputStream.close();
        outChannel.close(); 
        // 清除文件夹
        File tempFile = new File(serverPath + File.separator + fileMd5);
        if (tempFile.isDirectory() && tempFile.exists()) {
            tempFile.delete();
        }

    }

    @ResponseBody
    @PostMapping("/check")
    public String BigFileCheck(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // 校验文件是否已经上传并返回结果给前端,就一个作用:校验块是否存在,假如不存在,前端会再次用上传器传到后台

        // 文件唯一表示
        String fileMd5 = request.getParameter("fileMd5");
        // 当前分块下标
        String chunk = request.getParameter("chunk");
        // 当前分块大小
        String chunkSize = request.getParameter("chunkSize");

        // 直接根据块的索引号找到分块文件
        File checkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk);


        // 检查文件是否存在,且大小一致(必须满足这两个条件才认为块是已传成功)
      //  response.setContentType("text/html;charset=utf-8");
        if (checkFile.exists() && checkFile.length() == Integer.parseInt((chunkSize)) && fileMd5.length() == 32) {
//        response.getWriter().write("{\"ifExist\":1}");
            return "{\"ifExist\":1}";
        } else {
            //假如文件没存在,说明没有上传成功,返回0
            // response.getWriter().write("{\"ifExist\":0}");
            return "{\"ifExist\":0}";
        }
    }


    /**
     * 文件上传
     * @param request
     * @param response
     * @throws IOException
     */
    @PostMapping("/bigupload")
    public void BigFileUpload(HttpServletRequest request, HttpServletResponse response) throws IOException {
     //   response.getWriter().append("Served at: ").append(request.getContextPath());

        System.out.println("进入FileUploadServlet后台...");

        // 1.创建DiskFileItemFactory对象,配置缓存用
        DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
        // 2. 创建 ServletFileUpload对象
        ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);

        // 3. 设置文件名称编码
        servletFileUpload.setHeaderEncoding("utf-8");
        // 4. 开始解析文件
        // 文件md5获取的字符串
        String fileMd5 = null;
        // 文件的索引
        String chunk = null;
        try {
            List<FileItem> items = servletFileUpload.parseRequest(request);
            for (FileItem fileItem : items) {

                if (fileItem.isFormField()) {
                    //普通数据,例如字符串
                    String fieldName = fileItem.getFieldName();
                    if ("info".equals(fieldName)) {
                        String info = fileItem.getString("utf-8");
                        System.out.println("info:" + info);
                    }
                    if ("fileMd5".equals(fieldName)) {
                        fileMd5 = fileItem.getString("utf-8");
                        System.out.println("fileMd5:" + fileMd5);
                    }
                    if ("chunk".equals(fieldName)) {
                        chunk = fileItem.getString("utf-8");
                        System.out.println("chunk:" + chunk);
                    }
                } else {
                    if (StringUtils.isEmpty(fileMd5)) {
                        fileMd5 = "test";//假如md5没有,就用test作为目录名
                    }
                    if (StringUtils.isEmpty(chunk)) {
                        chunk = fileItem.getName();//filename
                    }

                    // 如果文件夹没有创建文件夹
                    File file = new File(serverPath + File.separator + fileMd5);
                    if (!file.exists()) {
                        file.mkdirs();
                    }
                    //这时保存的每个块,块先存好,后续会调合并接口,将所有块合成一个大文件
                    File chunkFile = new File(serverPath + File.separator + fileMd5 + File.separator + chunk);
                    FileUtils.copyInputStreamToFile(fileItem.getInputStream(), chunkFile);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}



版权声明:本文为weixin_45944917原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。