zoukankan      html  css  js  c++  java
  • 无插件实现大文件分片上传,断点续传

    代码地址如下:
    http://www.demodashi.com/demo/11888.html

    1. 简介:

    本篇文章基于实际项目的开发,将介绍项目中关于大文件分片上传、文件验证、断点续传、手动重试上传等需求的使用场景及实现;

    2. 项目需求

    1. 在一个音视频的添加中,既要有音视频的简介(如音视频内容文字介绍、自定义主题名称等一些基本的信息),又要有音视频所需要的多个文件(就像电视剧,一部电视剧有多集一样)。在数据库中具体表现为一对多的关系,即一个视频对应多个文件。下文就以电视剧为例
    2. 如果一个电视剧中,既有上百兆的,也有几十兆的视频,但是如果在不稳定的一个网络环境中,c传输大文件时,客户想先把小的视频上传了,之后再来继续传未传完的大文件声誉部分,这就需要断点续传的功能;
    3. 电视剧中至少有一集(至少有一个文件),无文件的电视剧基本信息无效;

    3. 需求分析

    1. 确定电视剧基本信息(自定义名称,内容简介、演员简介、播出时间等)及文件的上传方式
    - 基本信息和音视频文件分开上传(因为在原有的数据库表设计中,文件表是关联于基本信息,所以必须要有音视频主键,才能在数据库添加对应的文件信息),取得基本信息主键之后再去上传文件;
    
    2. 文件断点续传中,如何分片;文件接收方式;服务器端如何判断是哪个文件的分片;如何拼接各个分片;上传过程中发生意外情况(如断网,关闭浏览器),如何处理?
    - 分片方式: 在客户端进行分片;
    - 服务器端接收方式:使用MultipartFile接收文件
    - 服务器端确定是哪个文件的分片: 在客户端按照一定规则(UUID或其他方式)生成唯一名称,在服务器端直接找到与该名称相同的文件片段;
    -  拼接文件分片: 使用NIO的方式,将分片追加到已有分片的后面;
    - 上传中发生意外: 
    	A.  断网: 该情况类似于暂停上传,上传到文件处于暂停状态,网络恢复,即可点击继续上传按钮,继续上传;
    	B.  关闭浏览器: 在关闭时,给用户提示框,询问是否继续保存,若不保存,则根据视频基本信息表的主键的删除脏数据;
    	C.  第一个文件在上传时候,被用户取消或者断网,则服务器端未修改基本信息为有效,并且也未标记该文件为有效记录,可以理解为脏数据,但不需要清理这些数据(在查询的时候,不能查出这些无效记录,可以在更新视频基本信息记录的时候,查找这些脏数据,并清理磁盘上及数据表中的记录);
    

    4. 实现

    1. 基于以上分析,搭建一个简单的web项目工程,如下图:

    2. 前端主要功能实现
    // 文件分割上传
    		// 文件大小和分割起点
    		// 注释的是本地存储实现
    		var size = file.size, start = localStorage[fileid] * 1 || 0;
    		//start = $("filelist_" + fileid).filesize;
    		console.log("start1:"+start);
    		if (size == start) {
    			// 已经传过了
    			fileArray.shift();
    			if (delete fileArray[fileid]) console.log(fileArray.join() + "---上传成功");
    			objStateElement.success(fileid, now);
    			// 回调
    			onsuccess.call(fileid, {});
    			localStorage.clear();
    			return;
    		}
    		
    		var funFileSize = function() {
    			if (file.flagPause == true) {
    				onpause.call(fileid);
    				return;
    			}
    			var data = new FormData();
    			data.append("name", encodeURIComponent(file.name));
    			data.append("fileid", fileid);
    			data.append("file", file.slice(start, start + fileSplitSize));
    			data.append("start", start + "");
    			var p = "?name="+encodeURIComponent(file.name)+"&fileid"+fileid+"&start"+start;
    			// XMLHttpRequest 2.0 请求
    			var xhr = new XMLHttpRequest();
    			xhr.open("post", eleForm.action, true);				
    			// 上传进度中
    			xhr.upload.addEventListener("progress", function(e) {
    				objStateElement.backgroundSize(fileid, (e.loaded + start) / size * 100);
    			}, false);
    			// ajax成功后
    			xhr.onreadystatechange = function(e) {
    				if (xhr.readyState == 4) {
    					if (xhr.status == 200) {
    						try {
    							var json = JSON.parse(xhr.responseText);
    						} catch (e) {
    							objStateElement.error(fileid);
    							return;
    						} 
    						//var json = JSON.parse(xhr.responseText);
    						if (!json || !json.succ) {
    							objStateElement.error(fileid);
    							onerror.call(fileid, json);
    							return;
    						}
    						
    						if (start + fileSplitSize >= size) {
    							// 超出,说明全部分割上传完毕
    							// 上传队列中清除者一项
    							fileArray.shift();
    							if (delete fileArray[fileid]) console.log(fileArray.join() + "---上传成功");
    							objStateElement.success(fileid, now);
    							// 回调
    							onsuccess.call(fileid, json);
    							localStorage.clear();
    						} else {
    							// 尚未完全上传完毕						
    							// 改变下一部分文件的起点位置
    							start += fileSplitSize;
    							// 存储上传成功的文件点,以便出现意外的时候,下次可以断点续传
    							localStorage.setItem(fileid, start + "");							
    							// 上传下一个分割文件
    							funFileSize();
    							
    						}		
    					} else {
    						objStateElement.error(fileid);
    					}
    				}
    			};
    

    前端向后台提交文件

    	var xhr_filesize = new XMLHttpRequest();
    			xhr_filesize.open("GET", "/BigFileUpload/ajaxFilesUploadServlet?filename=" + nameArray.join(), true);
    			xhr_filesize.onreadystatechange = function(e) {
    				if (xhr_filesize.readyState == 4) {
    					if (xhr_filesize.status == 200 && xhr_filesize.responseText) {
    						var json = JSON.parse(xhr_filesize.responseText);
    						if (json.succ && json.data) {
    							for (var key in json.data) {
    								if (json.data[key] > 0 && json.data[key] < fileArray[key].size) {									
    									objStateElement.backgroundSize(key, json.data[key] / fileArray[key].size * 100);
    									objStateElement.keep(key);
    								} 
    								$("filelist_" + key).filesize = json.data[key];
    							}
    						}
    					}
    				}
    			};
    			xhr_filesize.send();
    		}
    		
    
    3.后台接收文件
     /***
        * @Description: 上传流文件并保存
        * @param request
        * @param response
        * @throws ServletException
        * @throws IOException
        * @version: v1.1.0
        * @author: xiangdong.she
        * @date: Nov 9, 2017 
        *
        * Modification History:
        * Date         Author          Version            Description
        *-------------------------------------------------------------
        * Nov 9, 2017    xiangdong.she     v1.1.0               修改原因
        */
        public void doPost(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException
        {
            
            request.setCharacterEncoding("utf-8"); //设置编码
            
            JSONObject json = new JSONObject(); //返回的json串
            
            String filename = ""; //文件名称
            String path = request.getRealPath("/upload"); //获取文件需要上传到的路径
            
            try
            {
                List<FileItem> items = new ServletFileUpload(
                        new DiskFileItemFactory()).parseRequest(request);
                for (FileItem item : items)
                {
                    if (item.isFormField())
                    {
    
                        String fieldname = item.getFieldName();
                        String fieldvalue = "";
                        if (fieldname.equals("name"))
                        {
                            filename = fieldvalue = URLDecoder.decode(item.getString(),
                                    "UTF-8");
                            
                        }
                        else
                        {
                            fieldvalue = item.getString();
                        }
                        
                        System.out.println("fieldname:" + fieldname
                                + "--fieldvalue:" + fieldvalue);
                        // to do list
                    }
                    else
                    {
                        String fieldname = item.getFieldName();
                        InputStream filecontent = item.getInputStream();
                        System.out.println("fieldname:" + fieldname + "--filename:"
                                + filename + "---filecontent:" + filecontent
                                + "---path:" + path);
                        //手动写入硬盘
                        if (makeDir(path))
                        {
                            createFile(path, filename);
                        }
                        
                        File file = new File(path + File.separator + filename);
                        FileOutputStream fos = new FileOutputStream(file, true);
                        InputStream is = item.getInputStream();
                        IOUtils.copy(is, fos);
                        is.close();
                        fos.close();
                        
                        System.out.println("获取上传文件的总共的容量:" + item.getSize());
                    }
                }
            }
            catch (FileUploadException e)
            {
                e.printStackTrace();
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            json.put("succ", true);
            response.setContentType("text/plain");
            response.getWriter().write(json.toString());
        }
    

    5.结果展现

    无插件实现大文件分片上传,断点续传

    代码地址如下:
    http://www.demodashi.com/demo/11888.html

    注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    Java实现 LeetCode 69 x的平方根
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 68 文本左右对齐
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 67 二进制求和
    Java实现 LeetCode 66 加一
    Java实现 LeetCode 66 加一
    CxSkinButton按钮皮肤类
  • 原文地址:https://www.cnblogs.com/demodashi/p/8509828.html
Copyright © 2011-2022 走看看