美文网首页Springboot程序员javaWeb学习
HTML5结合springboot带进度条大文件分段上传

HTML5结合springboot带进度条大文件分段上传

作者: 极微 | 来源:发表于2018-09-14 19:38 被阅读138次
    一、背景

    web应用文件上传是经常会碰到的,一般我们上传的都是几十MB以内的文件,当我们要传输视频等大文件时,首先springboot的默认配置为10MB,大于这个我们是传不上服务器的,如果我们修改了默认配置,那无疑会加重服务器的负担。其次当文件大于一定程度时,不仅浏览器也会占用大量内存,而且http传输极可能会中断。
    这里本人参考了大量资料做了一个http文件分段上传,支持GB级别和分段续传。前端主要用了HTML5的File API切割文件,后端主要用了java文件合并功能。不多说了,上代码。

    二、前端代码
    <script type="text/javascript">
        function uploadFile(){
            var file = $("#file")[0].files[0];  //文件对象
            if(file==undefined){
                alert("请先选中文件");
                return;
            }
            if(file.size>1024*1024*1024*2){
                alert("文件不能大于2GB");
                return;
            }
            $("#upload").attr("disabled","disabled");
            isUpload(file);
        }
        function isUpload (file) {
            var form=new FormData();
            var reader = new FileReader();
            //绑定读取失败事件
            $(reader).error(function(e){
                $("#upload").removeAttr("disabled");
                alert("读取失败");
            })
            
            var fileWithSize=file;
            //根据文件大小计算fileMd5值
            if(file.size>1024*1024*50){
                var fileStart=file.slice(0,1024*1024);
                var fileEnd=file.slice(file.size+1);
                var arr=[fileStart,fileEnd];
                fileWithSize = new Blob(arr, { type: "text/plain" });
            }
            //绑定读取成功事件
            $(reader).load(function(e){
                    var fileMd5 = hex_md5(reader.result);
    
                form.append("fileMd5", fileMd5);  
                form.append("videoSize", file.size);  
                //校验是否上传过该文件,返回上传进度
                $.ajax({ 
                    url: "${basePath}/global/isFileExist",
        
                    type: "POST",
    
                    data: form,
    
                    async: true,        //异步
    
                    processData: false,  //很重要,告诉jquery不要对form进行处理
    
                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type
    
                    success: function(data){
                        if(data.code==1){
                            $("#process").css("width","100%");
                            $("#process").html("100%");
                            $("#upload").removeAttr("disabled");
                            alert(data.msg)
                        }else{//视频未上传或者部分上传
                            //分片上传
                            $("#process").css("width","0%");
                            $("#process").html("");
                            uploadBySplit(file,fileMd5,0);
                        }
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        $("#upload").removeAttr("disabled");
                        alert("服务器出错!");
                    }
                });
            })
            
            reader.readAsBinaryString(fileWithSize);
        }
        //分片上传
        function uploadBySplit(file,fileMd5,i){
            var splitSize=1024*1024*20;//分片大小20M
            var size=file.size;//总大小
            splitCount = Math.ceil(size / splitSize);  //总片数
            
            if(i==splitCount){
                $("#upload").removeAttr("disabled");
                return;
            }
            
            //计算每一片的起始与结束位置
            var start = i * splitSize;
            var end = Math.min(size, start + splitSize);
            
            var fileData=file.slice(start,end);
            var reader = new FileReader();
            $(reader).load(function(e){
                    var md5 = hex_md5(reader.result);
    
                //构造一个表单,FormData是HTML5新增的
                var form = new FormData();
                form.append("fileMd5", fileMd5);
                form.append("size", size);//总大小
                form.append("total", splitCount);  //总片数
                form.append("index", i);        //当前是第几片
                form.append("md5", md5); 
                
                //判断分片是否上传
                 $.ajax({
                    url: "${basePath}/global/isFileSplitExist",
    
                    type: "POST",
    
                    data: form,
    
                    async: true,        //异步
    
                    processData: false,  //很重要,告诉jquery不要对form进行处理
    
                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                    
                    success: function(data){
                        if(data.code==1){//已上传
                            //处理上传进度
                            var process=Math.round(end/size*100)+"%";
                            $("#process").css("width",process);
                            $("#process").html(process);
                            i++;
                            uploadBySplit(file,fileMd5,i);
                        }else{//未上传
                            form.append("fileData", fileData);
                            //上传分片
                            $.ajax({
                                url: "${basePath}/global/fileUpload",
    
                                type: "POST",
                
                                data: form,
                
                                async: true,        //异步
                
                                processData: false,  //很重要,告诉jquery不要对form进行处理
                
                                contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                                
                                success: function(data){
                                    //处理上传进度
                                    var process=Math.round(end/size*100)+"%";
                                    $("#process").css("width",process);
                                    $("#process").html(process);
                                    i++;
                                    uploadBySplit(file,fileMd5,i);
                                },
                                 error: function(XMLHttpRequest, textStatus, errorThrown) {
                                    $("#upload").removeAttr("disabled");
                                    alert("服务器出错!");
                                }
                            
                            });
                        }
                    },
                    
                    error: function(XMLHttpRequest, textStatus, errorThrown) {
                        $("#upload").removeAttr("disabled");
                        alert("服务器出错!");
                    }
                 });
                
                
            });
            reader.readAsBinaryString(fileData);
           
        }
    
    三、后台代码
    package com.daotong.controllers.common;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.math.BigInteger;
    import java.security.MessageDigest;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.util.ClassUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import com.daotong.controllers.base.DaoTongBaseController;
    import com.daotong.core.conf.WebMappingTables;
    import com.shove190.core.plugins.data.ActionResult;
    
    /**
     * 视频上传类
     */
    @RestController
    public class FileUploadController extends DaoTongBaseController {
    
        @Value("${savePath}")
        private String savePath;
    
        /**
         * 视频分片上传公共接口,考虑到用户效率,采用MD5算法确认文件唯一性,理论上文件不同而摘要相同的概率几近于0,对于发生hash碰撞的两个文件,
         * 其得到的视频将会是与其发生hash碰撞的视频。
         *
         * @param request
         * @return
         * @throws IllegalStateException
         * @throws IOException
         */
        @SuppressWarnings("rawtypes")
        @RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER)
        public ActionResult upload(MultipartFile fileData) throws IllegalStateException, IOException, Exception {
            // 搜素目录下是否包含有相同的MD5文件?
    
            int total = Integer.valueOf(request.getParameter("total"));// 总片数
            int index = Integer.valueOf(request.getParameter("index"));// 当前是第几片,第一片index必须为0
            String fileMd5 = request.getParameter("fileMd5"); // 整个文件的md5
            // 文件夹位置
            File parent = new File(savePath + File.separator + fileMd5);
            if (!parent.exists()) {
                parent.mkdirs();
            }
            // 文件分片位置
            File file = new File(parent, fileMd5 + "_" + index);
            // 保存分片到本地
            if (file.exists()) {
                file.delete();
            }
            // 由前端控制上传进度
            fileData.transferTo(file);
    
            // 判断所有分片是否全部上传完成,完成则合并
            File dir = new File(savePath + File.separator + fileMd5);
            File[] files = dir.listFiles();
            if (files.length == total) {// 上传完成
                FileOutputStream fileOutputStream = null;
                FileInputStream temp = null;// 文件分片
                File newFile = new File(dir, fileMd5);
    
                fileOutputStream = new FileOutputStream(newFile, true);
                byte[] byt = new byte[10 * 1024 * 1024];
                int len;
    
                for (int i = 0; i < total; i++) {
                    temp = new FileInputStream(new File(dir, fileMd5 + "_" + i));
                    while ((len = temp.read(byt)) != -1) {
                        fileOutputStream.write(byt, 0, len);
                    }
                }
                fileOutputStream.close();
                temp.close();
                // 全部完成之后,删除分片
                for (int i = 0; i < total; i++) {
                    File splitFile = new File(dir, fileMd5 + "_" + i);
                    if (splitFile.exists()) {
                        splitFile.delete();
                    }
                }
            }
            return buildActionResult("操作成功");
        }
    
        /**
         * 获取文件的MD5值
         */
        private String getFileMD5(File file) {
            if (!file.exists() || !file.isFile()) {
                return null;
            }
            MessageDigest digest = null;
            FileInputStream in = null;
            byte buffer[] = new byte[1024];
            int len;
            try {
                digest = MessageDigest.getInstance("MD5");
                in = new FileInputStream(file);
                while ((len = in.read(buffer, 0, 1024)) != -1) {
                    digest.update(buffer, 0, len);
                }
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
            BigInteger bigInt = new BigInteger(1, digest.digest());
            // 16进制
            return bigInt.toString(16);
        }
    
        /**
         * 检测视频是否已上传
         */
        @RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER_ISFILEEXIST)
        public ActionResult<Object> isFileExist() {
            File pdir = new File(savePath);
            if (!pdir.exists()) {
                pdir.mkdirs();
            }
    
            ActionResult<Object> actionResult = new ActionResult<Object>();
    
            String fileMd5 = request.getParameter("fileMd5");
    
            File dir = new File(savePath + File.separator + fileMd5);
    
            if (dir.exists()) {// 目录存在
                File file = new File(dir, fileMd5);
                if (file.exists()) {// 文件存在
                    actionResult.setCode(1);
                    actionResult.setMsg("文件已上传,秒传");
                    return actionResult;
                } else {// 文件不存在,计算分片大小
                    actionResult.setCode(-1);
                    actionResult.setMsg("文件已部分上传");
                    return actionResult;
                }
            } else {
                actionResult.setCode(-1);
                actionResult.setMsg("文件未上传");
                return actionResult;
            }
        }
    
        /**
         * 检测视频分片是否已上传
         */
        @RequestMapping(value = WebMappingTables.MAPPING_WEB_COMMON_FIELUPLOADCONTROLLER_ISFILESPLITEXIST)
        public ActionResult<Object> isFileSplitExist() {
            ActionResult<Object> actionResult = new ActionResult<>();
    
            String splitMd5 = getReqStringParams("md5");// 分片Md5
            String fileMd5 = getReqStringParams("fileMd5");// 文件md5
            int index = getReqIntegerParams("index");// 第几片
    
            File file = new File(savePath + File.separator + fileMd5, fileMd5 + "_" + index);
            if (file.exists()) {
                if (splitMd5.equals(getFileMD5(file))) {
                    actionResult.setCode(1);
                    actionResult.setMsg("视频分片已上传");
                    return actionResult;
                } else {// 视频分片损坏,删除重传
                    file.delete();
    
                    actionResult.setCode(-1);
                    actionResult.setMsg("视频分片已损坏");
                    return actionResult;
                }
            } else {
                actionResult.setCode(-1);
                actionResult.setMsg("视频分片未上传");
                return actionResult;
            }
    
        }
        public static void main(String[] args) {
            File file=new File("E:\\saveFileDir\\f18215e2014b70b78e30ce2af989643a\\f18215e2014b70b78e30ce2af989643a");
            System.out.println(new FileUploadController().getFileMD5(file));
            //f18215e2014b70b78e30ce2af989643a
            //f18215e2014b70b78e30ce2af989643a
            String path = ClassUtils.getDefaultClassLoader().getResource("").getPath();
            System.out.println(path);
        }
    }
    

    相关文章

      网友评论

        本文标题:HTML5结合springboot带进度条大文件分段上传

        本文链接:https://www.haomeiwen.com/subject/uhkagftx.html