├── README.md ├── sample.php ├── shard.js └── shard.php /README.md: -------------------------------------------------------------------------------- 1 | # shardUpload 2 | HTML5 大文件分片上传 3 | 4 | #快速使用 5 | ```html 6 | 7 | 8 | ``` 9 | ```javascript 10 | 11 | 14 | ``` 15 | 16 | #使用说明 17 | 18 | ``` 19 | shardUpload.init(elementID,uploadUrl,config) 20 | ``` 21 | 22 | - elementID 绑定的元素ID,绑定后点击这个元素会出现选择文件框 23 | 24 | - uploadUrl 上传的服务端地址 25 | 26 | - config 配置参数 27 | 28 | 在绑定的元素上设置 data-shardUpload属性,则在上传成功后,会将对应元素的value设置为服务返回的数据,如上面的例子中,上传成功后,会吧video的value设置为服务器返回的数据(shard.php返回的数据为合并后的文件的路劲) 29 | 30 | 31 | #config说明 32 | 33 | - exts 允许上传的文件类型,多种类型用 | 分割,如:mp4|flv|avi 34 | 35 | - chunk 每个碎片分割的大小,默认值2M。如:设置1024*1024表示每个碎片为1M(注:以B为单位,不是以M为单位) 36 | 37 | - async 同时并行上传的碎片数,默认值1个 38 | 39 | - token 上传令牌,将token传给服务端,服务端可以验证后再上传 40 | 41 | - parame 其他需要传给服务端的参数,如:{id:5,category_id:16} 42 | 43 | - callback 回调函数,下面有详细说明, 44 | 45 | #callback回调函数 46 | 47 | ``` 48 | callback(file,message,status) 49 | ``` 50 | 51 | - file 文件选择框内的原始文件对象,可以通过file.size获取文件大小 file.name获取文件名称等等 52 | 53 | - message 传入的提示信息/结果,如:上传错误或上传成功后服务器返回的信息 54 | 55 | - status 状态/信息类型,通过判断status来执行不同的操作 56 | 57 | ```javascript 58 | function myCallback(file,message,status){ 59 | //当设置了回调函数后,默认的上传进度条将不会显示,如果要让进度条显示,则调用shardUpload.tipDisplay(message,status) 60 | shardUpload.tipDisplay(message,status) //显示默认的进度条 61 | if(status == 'success') { 62 | //当status为success的时候,message返回的是服务端合并文件后返回的信息,一般为合并的文件名称 63 | document.getElementById('output').innerHTML = message 64 | }else if(status == 'process'){ 65 | //当status为process的时候,返回当前上传进度,返回0-100 66 | document.getElementById('output').innerHTML = message + '%'; 67 | }else if(status == 'merge'){ 68 | //当status为merge时,文件上传完成,通知服务端开始合并文件,message为正在合并文件 69 | document.getElementById('output').innerHTML = message; 70 | }else if(status == 'failed'){ 71 | //文件上传失败 72 | }else if(status == 'cancel'){ 73 | //文件上传取消 74 | }else if(status == 'error'){ 75 | //文件格式不正确 76 | } 77 | } 78 | 79 | //全部参数都设置的使用方法 80 | 81 | shardUpload.init('upload-btn','shard.php',{ 82 | exts:'mp4|flv', //只允许上传mp4或flv格式 83 | chunk:4*1024*1024, //按4M分割 84 | async:5,//允许同时上传5个碎片 85 | token:'', //上传token为session内的token,服务器端判断 $_POST['shard-token'] == $_SESSION['uptoken'] 86 | param:{id:5,category_id:10}, //其他参数,服务器端获取 $_POST['id']/$_POST['category_id'] 87 | callback:myCallback, //上面设置的回调方法 88 | 89 | }) 90 | 91 | ``` 92 | #服务器端说明 93 | 94 | 服务器端可以通过request获取到传入的参数,如php的为 $_POST['shard-name'] 95 | 96 | - shard-data 传入的文件对象 97 | - shard-name 上传的文件名,此文件名加入了随机数,防止同时上传相同文件名文件的时候冲突 98 | - shard-total 总共碎片数 99 | - shard-index 当前碎片编号 从1开始 100 | - shard-token 如果设置了token则会传给服务端 101 | - shard-merge 当传入为yes的时候,则表示上传完成,需要服务器端合并文件 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /sample.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sample 6 | 13 | 14 | 15 |
16 |

shardUpload HTML5+PHP 大文件分片上传

17 |
18 | 文档:https://github.com/bencolin4/shardUpload 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /shard.js: -------------------------------------------------------------------------------- 1 | ;!function(window){ 2 | var _btn, //按钮 3 | _showTip, //展示提示方法 4 | _file, //file控件 5 | _shardSize = 2 * 1024 * 1024, //每个碎片大小 6 | _shardCount, //总碎片数量 7 | _async = 1, //同时上传的碎片数 8 | _token = null, //上传Token 9 | _stats, //其他参数 10 | _succeed = 0; 11 | _errored = 0; 12 | _abort = false; 13 | 14 | var _fileName, //上传的文件名称 15 | _fileObj, //上传文件 16 | _fileSize, //文件大小 17 | _fileForms, //分片后要上传的数据包 18 | _allowExts = null, //允许上传的文件格式 19 | _upNum = 0; //已经进入上传队列的数目 20 | 21 | var _upUrl; 22 | var _this; 23 | 24 | var shardUpload = { 25 | 26 | init:function(btn,upUrl,config){ 27 | _this = this; 28 | _succeed = 0; 29 | _showTip = null; 30 | _shardSize = 2 * 1024 * 1024; 31 | _async = 1; 32 | _token = null; 33 | _stats = null; 34 | _upNum = 0; 35 | var that = this; 36 | _btn = document.getElementById(btn); 37 | _upUrl = upUrl; 38 | if(typeof(config) == 'object'){ 39 | if(typeof(config.callback) == 'function'){ 40 | _showTip = config.callback; 41 | } 42 | if(typeof(config.chunk) == 'number'){ 43 | _shardSize = config.chunk; 44 | } 45 | if(typeof(config.async) == 'number'){ 46 | _async = config.async; 47 | } 48 | if(typeof(config.token) == 'string'){ 49 | _token = config.token; 50 | } 51 | if(typeof(config.parame) == 'object'){ 52 | _stats = config.parame; 53 | } 54 | if(typeof(config.exts) == 'string'){ 55 | _allowExts = config.exts.toUpperCase(); 56 | } 57 | } 58 | 59 | var fileid = 'upfile-'+parseInt(Math.random()*100000); 60 | 61 | _file = document.createElement('input'); 62 | _file.type = 'file'; 63 | _file.id = fileid; 64 | _file.name = fileid; 65 | _file.style.display = 'none'; 66 | _btn.parentNode.insertBefore(_file,_btn); 67 | 68 | 69 | _btn.onclick=function(){_file.click()}; 70 | _file.onchange = function(){ that.sliceFile()}; 71 | 72 | 73 | }, 74 | 75 | sliceFile:function(){ 76 | _btn.disabled = true; 77 | _succeed = 0; 78 | _errored = 0; 79 | _abort = false; 80 | _upNum = 0; 81 | var fileName = _file.value; 82 | var ldot = fileName.lastIndexOf("."); 83 | var fileExt = fileName.substring(ldot + 1); 84 | fileExt = fileExt.toUpperCase(); 85 | if(_allowExts){ 86 | var exts = _allowExts.split('|'); 87 | var isValid = false; 88 | for (var i = 0; i < exts.length; i++) { 89 | if(exts[i] == fileExt){ 90 | isValid = true; 91 | } 92 | }; 93 | if(!isValid){ 94 | this.showTip('文件格式错误!','error'); 95 | return; 96 | } 97 | }; 98 | 99 | this.showTip('文件准备中...','notice'); 100 | 101 | _fileObj = _file.files[0], //文件对象 102 | _fileSize = _fileObj.size; 103 | _shardCount = Math.ceil(_fileSize / _shardSize); //总片数 104 | _fileName = parseInt(Math.random()*100000)+_fileObj.name; 105 | 106 | 107 | var forms = new Array(); 108 | 109 | for(var i = 0;i < _shardCount; ++i){ 110 | //计算每一片的起始与结束位置 111 | var form = this.setForm(i); 112 | var formObj = { 113 | status:0, 114 | form:form 115 | }; 116 | forms.push(formObj); 117 | } 118 | _fileForms = forms; 119 | 120 | this.showTip('文件准备上传','notice'); 121 | this.upFile(); 122 | }, 123 | 124 | setForm:function(num){ 125 | var start = num * _shardSize, 126 | end = Math.min(_fileSize, start + _shardSize); 127 | 128 | var form = new FormData(); 129 | form.append("shard-data", _fileObj.slice(start,end)); //slice方法用于切出文件的一部分 130 | form.append("shard-name", _fileName); 131 | form.append("shard-total", _shardCount); //总片数 132 | form.append("shard-index", num+1); //当前是第几片 133 | //如果传入了Token,则设置Token 134 | if(_token != null){ 135 | form.append('shard-token',_token); 136 | } 137 | //如果传入了其他参数 138 | if(_stats != null){ 139 | for(var stat in _stats){ 140 | form.append(stat,_stats[stat]); 141 | } 142 | } 143 | return form; 144 | }, 145 | 146 | upFile:function(){ 147 | if(_errored>0 || _abort) return; 148 | 149 | for(var j=0;j<_fileForms.length;j++){ 150 | if(_upNum < _async){ 151 | if(_fileForms[j].status == 0){ 152 | _fileForms[j].status = 1; 153 | _upNum ++; 154 | this.ajaxUp(_fileForms[j]); 155 | } 156 | }else{ 157 | break; 158 | } 159 | } 160 | 161 | }, 162 | 163 | ajaxUp:function(formObject){ 164 | var form = formObject.form; 165 | var that = this; 166 | var xhr = createXMLHttpRequest(); 167 | xhr.upload.addEventListener("progress", uploadProgress, false); 168 | xhr.addEventListener("load", uploadComplete, false); 169 | xhr.addEventListener("error", uploadFailed, false); 170 | xhr.addEventListener("abort", uploadCanceled, false); 171 | 172 | xhr.open("POST",_upUrl,true); 173 | xhr.send(form); 174 | 175 | function uploadComplete(){ 176 | _succeed++; 177 | _upNum--; 178 | if(_succeed == _shardCount){ 179 | //TODO 全部上传完成 180 | _this.showTip(100,'process'); 181 | _this.showTip('正在合并文件','merge'); 182 | var merge_xhr = createXMLHttpRequest(); 183 | merge_xhr.onreadystatechange = function(){ 184 | if(merge_xhr.readyState == 4){ 185 | if(merge_xhr.status == 200){ 186 | _this.showTip(merge_xhr.responseText,'success'); 187 | _file.value = ''; 188 | } 189 | } 190 | } 191 | merge_xhr.open('POST',_upUrl,true); 192 | var mergeForm = new FormData(); 193 | mergeForm.append('shard-merge','yes'); 194 | mergeForm.append("shard-name", _fileName); 195 | mergeForm.append("shard-total", _shardCount); 196 | merge_xhr.send(mergeForm); 197 | }else{ 198 | _this.showTip(parseInt(_succeed/_shardCount*100),'process'); 199 | _this.upFile(); 200 | } 201 | 202 | }; 203 | function uploadFailed(){ 204 | this.showTip('上传出错','failed'); 205 | _errored++; 206 | }; 207 | function uploadCanceled(){ 208 | this.showTip('上传终止','cancel'); 209 | _abort = true; 210 | }; 211 | function uploadProgress(ev){ 212 | /* 213 | var percent = 0; 214 | if(ev.lengthComputable) { 215 | percent = 100 * ev.loaded/ev.total; 216 | } 217 | */ 218 | }; 219 | 220 | }, 221 | 222 | 223 | 224 | showTip:function(tip,flag){ 225 | if(flag == 'success' || flag == 'failed' || flag == 'error' || flag == 'cancel'){ 226 | _btn.disabled = false; 227 | } 228 | if(_showTip != null){ 229 | _showTip(_fileObj,tip,flag); 230 | }else{ 231 | this.tipDisplay(tip,flag) 232 | } 233 | }, 234 | 235 | tipDisplay:function(tip,flag){ 236 | if(!document.getElementById(_file.id+'-tip')) { 237 | this.createTipDiv(); 238 | } 239 | var tipDiv = document.getElementById(_file.id+'-tip'); 240 | tipDiv.style.display = 'block'; 241 | if(flag == 'notice'){ 242 | tipDiv.firstChild.innerHTML = tip; 243 | }else if(flag == 'process'){ 244 | document.getElementById(_file.id+'-process-bg').style.display = 'block'; 245 | tipDiv.firstChild.innerHTML = '已完成:'+tip+'%'; 246 | document.getElementById(_file.id+'-process').style.width = tip+'%'; 247 | }else if(flag == 'success'){ 248 | document.getElementById(_file.id+'-process').style.width = '100%'; 249 | tipDiv.firstChild.innerHTML = '上传成功'; 250 | if(_btn.getAttribute('data-shardUpload')){ 251 | var eid = _btn.getAttribute('data-shardUpload'); 252 | document.getElementById(eid).value = tip; 253 | } 254 | setTimeout(function(){tipDiv.style.display = 'none';},2000); 255 | }else{ 256 | tipDiv.firstChild.innerHTML = tip; 257 | document.getElementById(_file.id+'-process-bg').style.display = 'none'; 258 | } 259 | }, 260 | createTipDiv:function(){ 261 | var tipDiv = document.createElement('DIV'); 262 | tipDiv.style.position = 'fixed'; 263 | tipDiv.style.right='0'; 264 | tipDiv.style.bottom = '0'; 265 | tipDiv.style.width = '200px'; 266 | tipDiv.style.padding = '10px'; 267 | tipDiv.style.backgroundColor = '#fff'; 268 | tipDiv.style.border = '1px solid #dedede'; 269 | tipDiv.id = _file.id+'-tip'; 270 | //TODO 设置className 271 | var tipHTML = '
'; 272 | tipDiv.innerHTML = tipHTML; 273 | document.body.appendChild(tipDiv); 274 | } 275 | 276 | } 277 | 278 | function createXMLHttpRequest() { 279 | var XMLHttpReq; 280 | try { 281 | XMLHttpReq = new ActiveXObject("Msxml2.XMLHTTP");//IE高版本创建XMLHTTP 282 | } 283 | catch(E) { 284 | try { 285 | XMLHttpReq = new ActiveXObject("Microsoft.XMLHTTP");//IE低版本创建XMLHTTP 286 | } 287 | catch(E) { 288 | XMLHttpReq = new XMLHttpRequest();//兼容非IE浏览器,直接创建XMLHTTP对象 289 | } 290 | } 291 | return XMLHttpReq; 292 | }; 293 | 294 | window.shardUpload = shardUpload; 295 | }(window); 296 | 297 | 298 | -------------------------------------------------------------------------------- /shard.php: -------------------------------------------------------------------------------- 1 | '.$index; 16 | } 17 | 18 | function merge(){ 19 | $name = $_POST['shard-name']; 20 | $total = $_POST['shard-total']; 21 | $dr = _UPLOAD_PATH; 22 | $exts = explode('.', $name); 23 | $newName = md5($name.time()).'.'.$exts[count($exts)-1]; 24 | $newFile = $dr.$newName; 25 | $fp = fopen($newFile,"ab"); 26 | for ($i=1; $i <= $total; $i++) { 27 | $thisfilepath = shardName($name,$i); 28 | $handle = fopen($thisfilepath,'r'); 29 | fwrite($fp,fread($handle,filesize($thisfilepath))); 30 | fclose($handle); 31 | unlink($thisfilepath); 32 | } 33 | fclose($fp); 34 | echo $newFile; 35 | } 36 | 37 | function shardName($name,$index){ 38 | //echo _UPLOAD_PATH.md5($name).'_'.$index.'.txt'; 39 | return _UPLOAD_PATH.md5($name).'_'.$index.'.txt'; 40 | } 41 | 42 | if($_POST['shard-merge'] == 'yes'){ 43 | merge(); 44 | }else{ 45 | shardUpload(); 46 | } 47 | 48 | 49 | ?> --------------------------------------------------------------------------------