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


    代码地址如下:
    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大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    Asp.net导出Excel文件
    Ext.Net 使用总结之GridPanel中的选中行
    Ext.Net 使用总结之查询条件中的起始日期
    使用python crontab设置linux定时任务
    JSON Serialization/Deserialization in C#
    redis 基本数据类型-列表(List)
    redis 基本数据类型-字符串(String)
    使同一个server上不同port的django应用可在同一个浏览器上打开
    django server之间通过remote user 相互调用
    使用python实现短信PDU编码
  • 原文地址:https://www.cnblogs.com/demodashi/p/8509828.html
Copyright © 2020-2023  润新知