博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
HTML5文件上传,断点续传的开发实现笔记
阅读量:2222 次
发布时间:2019-05-08

本文共 7306 字,大约阅读时间需要 24 分钟。

      由于集团业务需要,需要将大文件,通常是几个G的文件通过浏览器上传到平台里,但目前上大多数的网站的断点上传都是需要安装浏览器插件,例如需要安装FLASH、ActiveX控件、Java Applet插件。有时候服务器端也得自己实现,这样实现一个文件上传功能变得非常麻烦。由于当今的HTML5技术非常成熟了,通过HTML5 File api实现断点是一件非常容易的事情了,因此应该坚决采用HTML5的API来实现文件上传,笔者认为绝对不要在使用安装插件的方式了,安装插件有如下弊端:

  1. 不能跨浏览器支持,例如ActiveX控件就只能用于IE
  2. 新的浏览器已经都不支持Flash、java Applet了
  3. 不能跨平台,例如Windows平台能用,iOS与Android平台就不一定能用

      总之,用插件上传文件的方式已经过期了,放着HTML5 File api这么好的API不用,这不是暴殄天物呀,笔者经过短暂的资料查找后,发现只要自己实现一个HTTP服务器,即可以借助HTML5 File api本身的强大功能来实现大文件断点续传功能,借助于强大的HTML5 File api,结合笔者自己实现了一个HTTP上传服务器,实现的HTTP文件上传系统具备如下特色:

  • 支持超大文件HTTP上传,文件大小不受限制
  • HTTP上传断点续传支持
  • 支持HTML5浏览器上传进度显示
  • 无需浏览器端安装任何插件
  • 支持IE8及以下浏览器上传进度显示
  • 支持1000个实时并发连接
  • 无缓冲即时写入
  • 服务器端极少的CPU、内存占用
  • 支持查看客户在线连接 用 
  • Compatible with Chrome, FireFox, Safari, IE,Opera and Edge

以下记录如何在浏览器端实现支持大文件断点续传,作为以后的资料备查。

一、制作一个简单的网页,并在网页里添加file字段,代码如下:

H5大文件断点续传,失败重连演示
点击选择文件或者将文件拖放到此处。
0%
已经上传:
0
上传位率:
0

二、计算文件的HASH值

在上传文件之前,先得获得一个文件ID,这个ID是由浏览器端提供,而不是由服务器端提供,这样便于扩充,以后真要实现HTML5秒传文件的时候,改一下浏览器端的代码即可。

计算文件HASH值的思路就是:MD5(文件名称+文件长度+文件修改时间+自定义的浏览器ID)

javascript代码如下。

//简单的Cookie帮助函数function setCookie(cname,cvalue,exdays){  var d = new Date();  d.setTime(d.getTime()+(exdays*24*60*60*1000));  var expires = "expires="+d.toGMTString();  document.cookie = cname + "=" + cvalue + "; " + expires;}function getCookie(cname){  var name = cname + "=";  var ca = document.cookie.split(';');  for(var i=0; i

三、向服务器端查询文件断点续传信息

在开始上传之前,首先从服务器端查询文件的断点续传信息,以便决定从文件的什么位置读取要上传的数据,代码如下:

function doupload(bReconnect) {	if (!currentfile) {		alert("请选择文件后再试!")		return false;	}		if (upload_start) {		alert("文件上传正在进行中,请稍候再点击重复长传!")		return false;	}		var fileObj = currentfile;	var fileid = getFileId(fileObj);	var t = (new Date()).getTime();	//通过以下URL获取文件的断点续传信息,必须的参数为fileid,后面追加t参数是避免浏览器缓存	var url = resume_info_url + '?fileid='+fileid + '&t='+t;		var ajax = new XMLHttpRequest();		ajax.onerror = function (e) {		if (window.reconnectId) 			return;		window.reconnectId = window.setTimeout(function() {			doupload(true);		},2000);	};		ajax.onreadystatechange = function () {				if(this.readyState == 4){			if (bReconnect) {				//目前是重连状态,清除重连标志				window.reconnectId = 0;			}					if (this.status == 200){				var response = this.responseText;								var result = JSON.parse(response);				if (!result) {					alert('服务器返回的数据不正确,可能是不兼容的服务器');					return;				}				//断点续传信息返回的文件对象包含已经上传的尺寸				var uploadedBytes = result.file && result.file.size;				if (!result.file.finished && uploadedBytes < fileObj.size) {					upload_file(fileObj,uploadedBytes,fileid);				}				else {					//文件已经上传完成了,就不要再上传了,直接返回结果就可以了					showUploadedFile(result.file);					//模拟进度完成					//var progressBar = document.getElementById('progressbar');					//progressBar.value = 100;				}							}else {				toast('获取文件断点续传信息失败,正在尝试重连...');			}  		}	}	ajax.open('get',url,true);	ajax.send(null);	currentRequest = ajax;}

四、执行文件分段上传

如果文件已经上传过一部分了,则从文件上传长度的位置上传。以下是Javascript代码实现,关键技术点是:

var blob = fileObj.slice(start_offset,filesize);

       以上代码行表示从start_offset偏移位置读取到filesize为结尾位置的数据,放心吧,浏览是不会真正把这些数据一次预读到内存中去的,以上仅仅表示通知浏览器对文件对象的start_offse开始到filesize结尾的数据感兴趣。

以下是文件断点续传的代码,应该非常简单。

/*文件上传处理代码fileObj : html5 File 对象start_offset: 上传的数据相对于文件头的起始位置fileid: 文件的ID,这个是上面的getFileId 函数获取的,*/function upload_file(fileObj,start_offset,fileid){	var xhr = new XMLHttpRequest();	var formData = new FormData();		var blobfile;		if(start_offset >= fileObj.size){		return false;	}		var bitrateDiv = document.getElementById("bitrate");	var finishDiv = document.getElementById("finish");	var progressBar = document.getElementById('progressbar');	var progressDiv = document.getElementById('percent-label');	var oldTimestamp = 0;	var oldLoadsize = 0;	var totalFilesize = fileObj.size;	if (totalFilesize == 0) return;		var uploadProgress = function (evt) {		if (evt.lengthComputable) {			var uploadedSize = evt.loaded + start_offset; 			var percentComplete = Math.round(uploadedSize * 100 / totalFilesize);			var timestamp = (new Date()).valueOf();			var isFinish = evt.loaded == evt.total;			if (timestamp > oldTimestamp || isFinish) {				var duration = timestamp - oldTimestamp;				if (duration > 500 || isFinish) {					var size = evt.loaded - oldLoadsize;					var bitrate = (size * 8 / duration /1024) * 1000; //kbps					if (bitrate > 1000)						bitrate = Math.round(bitrate / 1000) + 'Mbps';					else						bitrate = Math.round(bitrate) + 'Kbps';					var finish = evt.loaded + start_offset;					if (finish > 1048576)						finish = (Math.round(finish / (1048576/100)) / 100).toString() + 'MB';					else						finish = (Math.round(finish / (1024/100) ) / 100).toString() + 'KB';					progressBar.value = percentComplete;					progressDiv.innerHTML = percentComplete.toString() + '%';					bitrateDiv.innerHTML = bitrate;					finishDiv.innerHTML = finish;					oldTimestamp = timestamp;					oldLoadsize = evt.loaded;				}			}		}		else {			progressDiv.innerHTML = 'N/A';		}	}		xhr.onreadystatechange = function(){    if ( xhr.readyState == 4 && xhr.status == 200 ) {      console.log( xhr.responseText );			       }		else if (xhr.status == 400) {					}  };	var uploadComplete = function (evt) {		progressDiv.innerHTML = '100%';		currentfile = null;		upload_start = false;		var result = JSON.parse(evt.target.responseText);		if (result.result == 'success') {			showUploadedFile(result.files[0]);		}		else {			alert(result.msg);		}	}	var uploadFailed = function (evt) {		if (!currentfile) return;		if (window.reconnectId) return;		upload_start = false;				toast("检测到网络故障!两秒后尝试重连...");						window.reconnectId = window.setTimeout(function() {			doupload(true);		},2000);	}	var uploadCanceled = function (evt) {		alert("上传被取消或者浏览器断开了连接!");	}			//设置超时时间,由于是上传大文件,因此千万不要设置超时	//xhr.timeout = 20000;	//xhr.ontimeout = function(event){  //  alert('文件上传时间太长,服务器在规定的时间内没有响应!');  //}         	xhr.overrideMimeType("application/octet-stream"); 	var filesize = fileObj.size;	var blob = fileObj.slice(start_offset,filesize);	var fileOfBlob = new File([blob], fileObj.name);	//附加的文件数据应该放在请求的前面	formData.append('filename', fileObj.name);	//必须将fileid信息传送给服务器,服务器只有在获得了fileid信息后才对文件做断点续传处理	formData.append('fileid', fileid);	//请将文件数据放在最后的域	//formData.append("file",blob, fileObj.name);	formData.append('file', fileOfBlob);		xhr.upload.addEventListener("progress", uploadProgress, false);		xhr.addEventListener("load", uploadComplete, false);	xhr.addEventListener("error", uploadFailed, false);	xhr.addEventListener("abort", uploadCanceled, false);	xhr.open('POST', upload_file_url);	//	xhr.send(formData);	currentRequest = xhr;	upload_start = true;	toast('');}

五、断点续传实现

通过监视XMLHttpRequest的error时间来判断文件上传是否报错,如果发生错误,重新上传即可。

这样在客户端掉线,宕机,或者服务器停机等各种情况下,均可实现点断续传。不用担心大文件上传到一半,突然故障发生又得重新传送。

 
 
var uploadFailed = function (evt) {		if (!currentfile) return;		if (window.reconnectId) return;		upload_start = false;				toast("检测到网络故障!两秒后尝试重连...");						window.reconnectId = window.setTimeout(function() {			doupload(true);		},2000);	}
xhr.addEventListener("error", uploadFailed, false);

为了用户体验好一点,以上代码对上传进度、上传速率进行了计算,当然还可以加入更多的显示,例如剩余时间。

      只要在服务器端正确处理浏览器的POST数据,实现HTTP文件上传断点续传确实不难,笔者不得不为当今浏览器的强大功能所折服。

      所有的HTML5断点续传代码已经上传到 

https://github.com/wenshui2008/UploadServer

位于 html/demos/h5resume.html 文件里。

      

转载地址:http://iuvfb.baihongyu.com/

你可能感兴趣的文章
帧框架frameset的用法总结
查看>>
java1.8中创建hashmap的初始化大小设置标准
查看>>
mark一下,service的实现层没有加@service注解。
查看>>
jq对象转换成js对象。已经jq的复合选择器。
查看>>
(一)alin‘s mysql学习笔记----概述
查看>>
(二)alin’s mysql学习笔记----mysql的存储引擎
查看>>
(三)alin’s mysql学习笔记----常用的join连接查询
查看>>
(四)alin’s mysql学习笔记----索引简介
查看>>
分布式系统中的幂等性的理解
查看>>
spring的注解开发中的常用注解(一)------@bean @Configuration @ComponentScan @Import @Scope @Lazy
查看>>
(五)alin’s mysql学习笔记----索引性能分析
查看>>
Spring中使用@Transactional注解进行事务管理的时候只有应用到 public 方法才有效
查看>>
springboot整合rabbitmq及rabbitmq的简单入门
查看>>
mysql事务和隔离级别笔记
查看>>
事务的传播属性(有坑点)自调用失效学习笔记
查看>>
REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案
查看>>
动态代理实现AOP
查看>>
23种常见的java设计模式
查看>>
关于被final修饰的基本数据类型一些注意事项
查看>>
java Thread中,run方法和start方法的区别
查看>>