├── 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 |
20 |
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 | ?>
--------------------------------------------------------------------------------