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

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

原文地址:https://www.cnblogs.com/demodashi/p/8509828.html