天天看点

手机端大文件上传和断点续传的实现

当前,我们正处于一个视频爆炸的时代,高清智能手机人人都有,各种场景下的视频应用越来越多,而且视频分辨率和视频尺寸越来越大(1080P高清已经普及,4K视频也即将普及),而且我们又需要和他人之间进行视频分享,这样就常常遇到大文件上传和断点续传的问题。因为文件过大(比如2GB以上),必须要考虑上传过程网络中断的情况,需要做到断点续传,否则我们就需要再次重新上传。所以我们在手机端上传内容的时候,需要将大文件进行分片,比如分成1024*1024B,即将大文件分成1M的片进行上传,服务器端接收后,再将这些分片文件合并成原始文件,这就是分片的基本原理。http的网络请求中已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件切片(分块),但这不是我们现在说的重点,我们要做的事是保证在网络中断后2G的文件已上传的那部分在下次网络连通时不必再重传。断点续传要求本地要记录每一片的上传的状态,我通过三个状态进行了标记(wait loading finish),当网络中断,再次连接后,从断点处进行上传。服务器通过文件名、总片数判断该文件是否已全部上传完成。

而且,对于大的并发应用而言,我们的上传服务器必须支持数百或者数千用户同时上传,这样才能满足运营级的需要。

为了通用性更强,采用安装特定的浏览器上传插件方式并不可取,最好的实现方式就是通过浏览器直接上传,当前已经有很多基于PHP或者Java程序来实现的上传服务器,然而在实际使用中你会会发现,这些基于脚本语言实现的上传功能模块性能很弱,一是不支持2GB以上的文件上传;二是无法支持断点续传;三是效率极低,单台服务器最多支持几十个并发上传连接,没有一个可以真正商用的系统。

因此,为了解决这个问题,我们花费了很大精力用C++语言自主实现了这一高性能上传服务器,在客户端只要调用一段 js 代码即可,这是一个通用的上传服务模块,可以快速集成到第三方应用平台中。

项目地址:

https://github.com/liufeihong/Hyper-Upload-Server

手机端大文件上传和断点续传的实现
手机端大文件上传和断点续传的实现
手机端大文件上传和断点续传的实现
手机端大文件上传和断点续传的实现

Hyper Upload Server 超级上传服务器简介:

这是一款超级文件上传服务器,采用异步I/O架构,采用C++语言编码实现。它支持4GB以上超大文件上传和断点续传,支持Windows和Linux服务器平台,支持任意格式的文件上传,尤其适合大的视频网站应用。单台服务器支持1000并发上传进程,支持PC端和智能手机端主流的浏览器。

主要特性

1. 服务器端采用异步I/O架设设计,具有高性能I/O处理能力,尤其适用于超大文件上传;

2. 服务器端采用高效内存分配技术确保在运行过程中服务器的内存开销最小化;

3. 完全采用标准协议实现,因此兼容几乎所有的PC端和移动端浏览器;

4. 服务器端采用C++语言自主实现,对上传文件的尺寸无限制,天生支持超大文件上传。

   而基于PHP、JAVA等技术实现的文件上传服务天生无法支持超大文件上传,无法逾越2GB的最大文件尺寸瓶颈;

5. 服务器端采用无缓冲即时写入方式,上传数据写入一步到位。不同于PHP、JAVA等技术实现方式需要两步写入;

6. 服务器端可跨平台编译运行,支持Windows和Linux平台;

7. 高性能,单台服务器支持1000个并发上传进程;

8. 支持4GB以上超大文件上传,文件大小不受限制;

9. 客户端支持采用HTTP标准协议上传;

10.支持断点续传,断网、关机重启均不受影响;

11.支持HTML5浏览器上传进度实时显示;

12.支持IE8及以上浏览器上传进度显示;

13.支持查看客户端在线连接, 查看方法: http://ip:port/lists

14.多浏览器兼容,包括Chrome,Firefox,Safari,IE,Opera,Edge;

 上传代码实现

;

(function($){

var rotateLeft = function(lValue, iShiftBits) {

return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));

}

var addUnsigned = function(lX, lY) {

    var lX4, lY4, lX8, lY8, lResult;

    lX8 = (lX & 0x80000000);

    lY8 = (lY & 0x80000000);

    lX4 = (lX & 0x40000000);

    lY4 = (lY & 0x40000000);

    lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);

    if (lX4 & lY4) return (lResult ^ 0x80000000 ^ lX8 ^ lY8);

    if (lX4 | lY4) {

    if (lResult & 0x40000000) return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);

    else return (lResult ^ 0x40000000 ^ lX8 ^ lY8);

    } else {

    return (lResult ^ lX8 ^ lY8);

    }

}

var F = function(x, y, z) {

    return (x & y) | ((~ x) & z);

}

var G = function(x, y, z) {

    return (x & z) | (y & (~ z));

}

var H = function(x, y, z) {

    return (x ^ y ^ z);

}

var I = function(x, y, z) {

    return (y ^ (x | (~ z)));

}

var FF = function(a, b, c, d, x, s, ac) {

    a = addUnsigned(a, addUnsigned(addUnsigned(F(b, c, d), x), ac));

    return addUnsigned(rotateLeft(a, s), b);

};

var GG = function(a, b, c, d, x, s, ac) {

    a = addUnsigned(a, addUnsigned(addUnsigned(G(b, c, d), x), ac));

    return addUnsigned(rotateLeft(a, s), b);

};

var HH = function(a, b, c, d, x, s, ac) {

    a = addUnsigned(a, addUnsigned(addUnsigned(H(b, c, d), x), ac));

    return addUnsigned(rotateLeft(a, s), b);

};

var II = function(a, b, c, d, x, s, ac) {

    a = addUnsigned(a, addUnsigned(addUnsigned(I(b, c, d), x), ac));

    return addUnsigned(rotateLeft(a, s), b);

};

var convertToWordArray = function(string) {

    var lWordCount;

    var lMessageLength = string.length;

    var lNumberOfWordsTempOne = lMessageLength + 8;

    var lNumberOfWordsTempTwo = (lNumberOfWordsTempOne - (lNumberOfWordsTempOne % 64)) / 64;

    var lNumberOfWords = (lNumberOfWordsTempTwo + 1) * 16;

    var lWordArray = Array(lNumberOfWords - 1);

    var lBytePosition = 0;

    var lByteCount = 0;

    while (lByteCount < lMessageLength) {

        lWordCount = (lByteCount - (lByteCount % 4)) / 4;

        lBytePosition = (lByteCount % 4) * 8;

        lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));

        lByteCount++;

    }

    lWordCount = (lByteCount - (lByteCount % 4)) / 4;

    lBytePosition = (lByteCount % 4) * 8;

    lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);

    lWordArray[lNumberOfWords - 2] = lMessageLength << 3;

    lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;

    return lWordArray;

};

var wordToHex = function(lValue) {

    var WordToHexValue = "", WordToHexValueTemp = "", lByte, lCount;

    for (lCount = 0; lCount <= 3; lCount++) {

    lByte = (lValue >>> (lCount * 8)) & 255;

    WordToHexValueTemp = "0" + lByte.toString(16);

    WordToHexValue = WordToHexValue + WordToHexValueTemp.substr(WordToHexValueTemp.length - 2, 2);

    }

    return WordToHexValue;

};

var uTF8Encode = function(string) {

    string = string.replace(/\x0d\x0a/g, "\x0a");

    var output = "";

    for (var n = 0; n < string.length; n++) {

    var c = string.charCodeAt(n);

    if (c < 128) {

    output += String.fromCharCode(c);

    } else if ((c > 127) && (c < 2048)) {

    output += String.fromCharCode((c >> 6) | 192);

    output += String.fromCharCode((c & 63) | 128);

    } else {

    output += String.fromCharCode((c >> 12) | 224);

    output += String.fromCharCode(((c >> 6) & 63) | 128);

    output += String.fromCharCode((c & 63) | 128);

    }

    }

    return output;

};

$.extend({

md5: function(string) {

var x = Array();

var k, AA, BB, CC, DD, a, b, c, d;

var S11=7, S12=12, S13=17, S14=22;

var S21=5, S22=9 , S23=14, S24=20;

var S31=4, S32=11, S33=16, S34=23;

var S41=6, S42=10, S43=15, S44=21;

string = uTF8Encode(string);

x = convertToWordArray(string);

a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;

for (k = 0; k < x.length; k += 16) {

AA = a; BB = b; CC = c; DD = d;

a = FF(a, b, c, d, x[k+0], S11, 0xD76AA478);

d = FF(d, a, b, c, x[k+1], S12, 0xE8C7B756);

c = FF(c, d, a, b, x[k+2], S13, 0x242070DB);

b = FF(b, c, d, a, x[k+3], S14, 0xC1BDCEEE);

a = FF(a, b, c, d, x[k+4], S11, 0xF57C0FAF);

d = FF(d, a, b, c, x[k+5], S12, 0x4787C62A);

c = FF(c, d, a, b, x[k+6], S13, 0xA8304613);

b = FF(b, c, d, a, x[k+7], S14, 0xFD469501);

a = FF(a, b, c, d, x[k+8], S11, 0x698098D8);

d = FF(d, a, b, c, x[k+9], S12, 0x8B44F7AF);

c = FF(c, d, a, b, x[k+10], S13, 0xFFFF5BB1);

b = FF(b, c, d, a, x[k+11], S14, 0x895CD7BE);

a = FF(a, b, c, d, x[k+12], S11, 0x6B901122);

d = FF(d, a, b, c, x[k+13], S12, 0xFD987193);

c = FF(c, d, a, b, x[k+14], S13, 0xA679438E);

b = FF(b, c, d, a, x[k+15], S14, 0x49B40821);

a = GG(a, b, c, d, x[k+1], S21, 0xF61E2562);

d = GG(d, a, b, c, x[k+6], S22, 0xC040B340);

c = GG(c, d, a, b, x[k+11], S23, 0x265E5A51);

b = GG(b, c, d, a, x[k+0], S24, 0xE9B6C7AA);

a = GG(a, b, c, d, x[k+5], S21, 0xD62F105D);

d = GG(d, a, b, c, x[k+10], S22, 0x2441453);

c = GG(c, d, a, b, x[k+15], S23, 0xD8A1E681);

b = GG(b, c, d, a, x[k+4], S24, 0xE7D3FBC8);

a = GG(a, b, c, d, x[k+9], S21, 0x21E1CDE6);

d = GG(d, a, b, c, x[k+14], S22, 0xC33707D6);

c = GG(c, d, a, b, x[k+3], S23, 0xF4D50D87);

b = GG(b, c, d, a, x[k+8], S24, 0x455A14ED);

a = GG(a, b, c, d, x[k+13], S21, 0xA9E3E905);

d = GG(d, a, b, c, x[k+2], S22, 0xFCEFA3F8);

c = GG(c, d, a, b, x[k+7], S23, 0x676F02D9);

b = GG(b, c, d, a, x[k+12], S24, 0x8D2A4C8A);

a = HH(a, b, c, d, x[k+5], S31, 0xFFFA3942);

d = HH(d, a, b, c, x[k+8], S32, 0x8771F681);

c = HH(c, d, a, b, x[k+11], S33, 0x6D9D6122);

b = HH(b, c, d, a, x[k+14], S34, 0xFDE5380C);

a = HH(a, b, c, d, x[k+1], S31, 0xA4BEEA44);

d = HH(d, a, b, c, x[k+4], S32, 0x4BDECFA9);

c = HH(c, d, a, b, x[k+7], S33, 0xF6BB4B60);

b = HH(b, c, d, a, x[k+10], S34, 0xBEBFBC70);

a = HH(a, b, c, d, x[k+13], S31, 0x289B7EC6);

d = HH(d, a, b, c, x[k+0], S32, 0xEAA127FA);

c = HH(c, d, a, b, x[k+3], S33, 0xD4EF3085);

b = HH(b, c, d, a, x[k+6], S34, 0x4881D05);

a = HH(a, b, c, d, x[k+9], S31, 0xD9D4D039);

d = HH(d, a, b, c, x[k+12], S32, 0xE6DB99E5);

c = HH(c, d, a, b, x[k+15], S33, 0x1FA27CF8);

b = HH(b, c, d, a, x[k+2], S34, 0xC4AC5665);

a = II(a, b, c, d, x[k+0], S41, 0xF4292244);

d = II(d, a, b, c, x[k+7], S42, 0x432AFF97);

c = II(c, d, a, b, x[k+14], S43, 0xAB9423A7);

b = II(b, c, d, a, x[k+5], S44, 0xFC93A039);

a = II(a, b, c, d, x[k+12], S41, 0x655B59C3);

d = II(d, a, b, c, x[k+3], S42, 0x8F0CCC92);

c = II(c, d, a, b, x[k+10], S43, 0xFFEFF47D);

b = II(b, c, d, a, x[k+1], S44, 0x85845DD1);

a = II(a, b, c, d, x[k+8], S41, 0x6FA87E4F);

d = II(d, a, b, c, x[k+15], S42, 0xFE2CE6E0);

c = II(c, d, a, b, x[k+6], S43, 0xA3014314);

b = II(b, c, d, a, x[k+13], S44, 0x4E0811A1);

a = II(a, b, c, d, x[k+4], S41, 0xF7537E82);

d = II(d, a, b, c, x[k+11], S42, 0xBD3AF235);

c = II(c, d, a, b, x[k+2], S43, 0x2AD7D2BB);

b = II(b, c, d, a, x[k+9], S44, 0xEB86D391);

a = addUnsigned(a, AA);

b = addUnsigned(b, BB);

c = addUnsigned(c, CC);

d = addUnsigned(d, DD);

}

var tempValue = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d);

return tempValue.toLowerCase();

}

});

})(jQuery); 

function toast(msg,duration)

{

    var toastDiv = document.getElementById('hyupload-toast');

    if (msg) {

        toastDiv.style.display = 'block';

        toastDiv.innerHTML = msg;

        duration = parseInt(duration);

        if (!duration) duration = 3000;

        if (!window.uploadtoast) {

            window.clearTimeout(window.uploadtoast);

        }

        window.uploadtoast = window.setTimeout(function() {

            toastDiv.style.display = 'none';

            delete window.uploadtoast;

        },duration);

    }

    else {

        toastDiv.style.display = 'none';

        toastDiv.innerHTML = '';

    }

}

Array.prototype.in_array=function(e){

    var S=String.fromCharCode(2);

    var r=new RegExp(S+e+S);

    return (r.test(S+this.join(S)+S));

};

var HYFileUploader = function (options) {

    var _options = {

        filelist:'hyupload-filelist',

        notice_url:'',

        savepaths:{'video':'.mp4,.mkv,.avi,.wmv','document':'.zip,.rar','image':'.jpg,.jpeg,.png,.gif','audio':'.aac,.mp3'},

        ontaskfinish:function(taskobj) {

        },

        ontaskprogress:function(taskobj) {

        },

        ontaskprogress:function(progress) {

        },

        ontaskstart:function(taskobj) {

            return true;

        }

    };

    $.extend(_options, options);

    //简单的Cookie帮助函数

    var setCookie = function(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;

    };

    var getCookie = function(cname) {

        var name = cname + "=";

        var ca = document.cookie.split(';');

        for(var i=0; i<ca.length; i++) 

        {

            var c = ca[i].trim();

            if (c.indexOf(name)==0) return c.substring(name.length,c.length);

        }

        return "";

    };

    //

    //简单的文件HASH值计算,如果您不是十分考究,应该可以用于产品。

    //由于计算文件HASH值用到了多种数据,因此在HYFileUploader系统范围内发生HASH冲突的可能性应该非常小,应该可以放心使用。

    //获取文件的ID可以用任何算法来实现,只要保证做到同一文件的ID是相同的即可,获取的ID长度不要超过32字节

    //

    var getFileId = function (file) {

        //给浏览器授予一个唯一的ID用于区分不同的浏览器实例

        var clientid = getCookie("HUAYIUPLOAD");

        if (clientid == "") {

            //用一个随机值来做浏览器的ID,将作为文件HASH值的一部分

            var rand = parseInt(Math.random() * 1000);

            var t = (new Date()).getTime();

            clientid =rand+'T'+t;

            setCookie("HUAYIUPLOAD",clientid,365);

        }

        var info = clientid;

        if (file.lastModified)

            info += file.lastModified;

        if (file.name)

            info += file.name;

        if (file.size)

            info += file.size;

        //https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js

        var fileid = $.md5(info);

        return fileid;

    };

    var get_file_mimetype = function(filename) {

        var mimeType;

        var extPos = filename.lastIndexOf('.');

        if (extPos != -1) {

            var extName = filename.substr(extPos).toLowerCase();

            if (extName == '.rm' || extName == '.rmvb') {

                mimeType = 'video/realvideo';

            }

            else if (extName == '.dat') {

                mimeType = 'video/vcd';

            }

            else if (extName == '.ra') {

                mimeType = 'audio/realaudio';

            }

        }

        if (!mimeType) mimeType = 'bin/unknown';

        return mimeType;

    };

    //savepaths:{'video':'.mp4,*.mkv,.avi,.wmv','document':'.zip,*.rar','image':'.jpg,.jpeg,.png,.gif','audio':'.aac,.mp3'}

    var get_save_path = function(filename,savepaths) {

        var extPos = filename.lastIndexOf('.');

        if (extPos != -1) {

            var extName = filename.substr(extPos).toLowerCase();

            for (path in savepaths) {

                var extList = savepaths[path].split(',');

                if (extList.indexOf(extName) != -1) {

                    return path;

                }

            }

        }

        return '';

    }

    var bytesToSize = function (bytes) {

        if (bytes === 0) return '0 B';

        if (bytes < 1024) return (bytes + 'B');

        var k = 1024,sizes = ['B','KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

        var i = Math.floor(Math.log(bytes) / Math.log(k));

        return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];

    }

    var _addFileToList = function (listViewer,fileobj) {

        var fileSources = '';

        var fileid = '';

        var filename = '',filesize = 0;

        var fileitemId = '';

        var $listViewer = $(listViewer);

        if (fileobj instanceof Array) {

            fileSources = '<filesources>';

            for(var i = 0; i < fileobj.length; i ++) {

                if (filename != '') filename += '|';

                var file = fileobj[i];

                filename += file.name;

                filesize += file.size;

                fileid = getFileId(file);

                fileSources += '<file id="'+fileid+'" fname="'+file.name+'" fsize="'+file.size+'"></file>';

                fileitemId += fileid;

                file.fileid = fileid;

            }

            fileSources += '</filesources>';

            fileitemId = $.md5(fileitemId);

        }

        else {

            var typename = typeof fileobj;

            if (typename != 'object') {

                return false;

            }

            if (fileobj.size == 0) {

                toast('文件 “'+fileobj.name+'” 长度为0无法上传,忽略此文件。');

                return false;

            }

            fileid = getFileId(fileobj);

            filename = fileobj.name;

            filesize = fileobj.size;

            fileSources = '<filesources><file id="'+fileid+'" fname="'+fileobj.name+'" fsize="'+fileobj.size+'"></file></filesources>';

            fileitemId = fileid;

            fileobj.fileid = fileid;

        }

        var file_existed = false;

        $listViewer.children('div.upload-fileitem').each(function(index,obj) {

            if (obj.id == fileitemId) {

                file_existed = true;

                return false;

            }

        });

        if (file_existed) {

            toast('添加的文件或文件组合 “'+filename+'” 已经存在,不重复添加');

            return false;

        }

        if (!listViewer.uploadOptions) {

            toast('添加的文件 “'+filename+'” 失败,文件列表未绑定上传对象。');

            return false;

        }

        //移除提示元素

        $listViewer.children('div.fileupload-help').remove();

        $listViewer.children('.notice').remove();

        var server = listViewer.uploadOptions.server;

        var cid = listViewer.uploadOptions.cid;

        var serverid = listViewer.uploadOptions.serverid;

        var userid = listViewer.uploadOptions.userid;

        var html = '<div class="upload-fileitem" id="'+fileitemId+'" data-server="'+server+'" data-cid="'+cid+'" data-serverid="'+serverid+'" data-userid="'+userid+'">';

        html += fileSources;

        html += '<a class="remove_icon enabled" href="javascript:void(0);" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" title="移除"></a><a class="edit_icon enabled" href="javascript:void(0);" target="_blank" rel="external nofollow" target="_blank" rel="external nofollow" title="编辑"><i class="icon-edit"></i></a><i class="icon-ok-circle"></i>';

        html += '<div class="fileinfo"><div class="filename"><span title="'+filename+'">'+filename+'</span></div>';

        html += '<div class="upload-status"><div class="item"><span class="title">长度:</span><span class="value filesize" data-id="filesize">';

        html += bytesToSize(filesize);

        html += '</span></div>';

        html += '<div class="item"><span class="title">已传:</span><span class="value finish" data-id="finish" title="实际已经上传的文件数据长度">0</span></div>';

        html += '<div class="item"><span class="title">速率:</span><span class="value bitrate" data-id="bitrate">0</span></div>';

        html += '</div></div>';

        html += '<div class="progressbar"><div class="xprogressbar progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">';

        html += '<div class="progress-track progress-success" style="width: 0%;"></div></div><span class="percent-label text" data-id="percent-label">0%</span></div></div>';

        var $fileItem = $(html);

        $fileItem.appendTo($listViewer);

        //$('#upload-filelistview').mCustomScrollbar('update');

        $fileItem.find('a.remove_icon').click(function(evt) {

            evt.preventDefault();

            if (!$(this).hasClass('enabled')) {

                return;

            }

            var $item = $(this).parent();

            if ($item.attr('data-upload')=='finished') {

                toast('文件已经上传完成,不必移除,如要清空队列,请强制刷新浏览器。');

                return;

            }

            //remove_icon 的外层 upload-fileitem

            var fileItemDomObj = $item.get(0);

            var fileViewer = $item.get(0).fileViewer;

            if (fileViewer.uploader) {

                var uploader = fileViewer.uploader;

                if ($item.attr('data-upload')=='uploading') {

                    if (!uploader.options.forceremove) {

                        if (!confirm('视频文件正在上传,确定要放弃吗?')) {

                            return;

                        }

                    }

                    //再次检查是否还在上传,如果没有启动上传uploader 属性无值

                    if (uploader.curFileitemElement && uploader.curFileitemElement == fileItemDomObj) {

                        //仅仅执行取消上传操作

                        if (uploader.currentFile) {

                            if (uploader.currentXHR && typeof uploader.currentXHR.abort == 'function') {

                                if (uploader.options.forceremove) {

                                    uploader.taskIsForceRemove = true;

                                }

                                uploader.currentXHR.abort();

                                return ;

                            }

                        }

                    }

                }

            }

            if (fileItemDomObj.fileObject) {

                fileItemDomObj.fileObject = null;

                delete fileItemDomObj.fileObject;

            }

            $item.remove();

            var $fileitems = $(fileViewer).children('.upload-fileitem');

            if ($fileitems.length == 0) {

                $(fileViewer).html('<p class="notice">暂无上传任务</p>');

            }

            //$('#upload-filelistview').mCustomScrollbar('update');

        });

        $fileItem.find('a.edit_icon').click(function(evt) {

            evt.preventDefault();

        });

        var fileItemDom = $fileItem.get(0);

        var fileObject = {

            taskid:fileitemId,

            files:fileobj,

            totalfilesize:filesize,

            uploadedsize:0,

            finished:0

        }

        fileItemDom.fileObject = fileObject;

        fileItemDom.fileViewer = listViewer;

        return fileid;

    };

    //fileInfo 是上传服务器返回的

    var _onUploadedFileFinished = function(fileInfo) {

        var uploader = this;

        //fileInfo 对象的值可以用于保存到CMS里,而不仅仅是用于显示

        if (uploader.options.show_output && uploader.uploadResult) {

            var html = '<div class="file-object" >';

                html += '<div class="info-row"><span class="rlabel">文件名:</span><span class="rvalue">'+fileInfo.name+'</span></div>';

                html += '<div class="info-row"><span class="rlabel">保存路径:</span><span class="rvalue">'+fileInfo.path+'</span></div>';

                html += '<div class="info-row"><span class="rlabel">文件尺寸:</span><span class="rvalue">'+fileInfo.filesize+'</span></div>';

                html += '<div class="info-row"><span class="rlabel">访问URL:</span><span class="rvalue">'+fileInfo.url+'</span></div>';

            html += '</div>';

            //将文件信息显示在列表里

            uploader.uploadResult.innerHTML +=html;

        }

        uploader.currentXHR = null;

        if (uploader.curFileitemElement) {

            var $fileNode = $(uploader.curFileitemElement);

            var cid = $fileNode.attr('data-cid');

            var serverid = $fileNode.attr('data-serverid');

            var userid = $fileNode.attr('data-userid');

            uploader.currentFile.uploadInfo = fileInfo;

            var taskid = uploader.curFileitemElement.id;

            var mimeType=mimeType = uploader.currentFile.type;

            if (!mimeType) {

                mimeType = get_file_mimetype(fileInfo.name);

            }

            var title = uploader.curFileitemElement.videotitle ? curFileitemElement.videotitle:'';

            var desc = uploader.curFileitemElement.videodesc ? curFileitemElement.videodesc:'';

            //记录文件完成上传

            var uploadEvent = {

                "event":"filefinished",

                "taskid":uploader.curFileitemElement.id,

                "fileid":uploader.currentFile.fileid,

                "cid":cid,

                "serverid":serverid,

                "userid":userid,

                "mimetype":mimeType,

                "filepath":fileInfo.path,

                "filename":fileInfo.name,

                "filesize":fileInfo.filesize,

                "url":fileInfo.url,

                "title":title,

                "desc":desc

            };

            if (uploader.options.notice_url) {

                $.post(uploader.options.notice_url,uploadEvent,function(data) {

                    if (data.code != 1) {

                        toast(data.msg);

                    }

                },'json');

            }

            if (typeof uploader.options.onevent == 'function') {

                uploader.options.onevent.call(uploader, uploadEvent);

            }

            var isTaskfinish = 1;

            if (uploader.curFileitemElement.fileObject.files instanceof Array) {

                for(var i = 0; i < uploader.curFileitemElement.fileObject.files.length; i ++) {

                    if (!uploader.curFileitemElement.fileObject.files[i].uploadInfo) {

                        isTaskfinish = 0;

                        break;

                    }

                }

            }

            else {

                isTaskfinish = 1;

            }

            uploader.currentFile = null;

            uploader.upload_start = false;

            if (isTaskfinish) {

                $fileNode.attr('data-upload','finished').addClass('finished');

                $fileNode.find('.remove_icon').removeClass('enabled');

                uploader.curFileitemElement.fileObject.finished = 1;

                if (typeof uploader.options.ontaskfinish == 'function') {

                    uploader.options.ontaskfinish.call(uploader,uploader.curFileitemElement.fileObject);

                }

                uploader.curFileitemElement = null;

                //接着启动下一个任务

                if (!_startNextTaskupload.call(uploader)) {

                    if (uploader.startbutton) {

                        uploader.startbutton.disabled=false;

                        $(uploader.startbutton).removeClass('disabled');

                        uploader.startbutton = null;

                    }

                }

            }

            else {

                //继续上传本任务的下一个文件

                _startFileupload.call(uploader,false);

            }                

        }

    }

    var _do_upload_file = function (fileObj,start_offset,fileid) {

        var uploader = this;

        var xhr = new XMLHttpRequest();

        var formData = new FormData();

        var blobfile;

        if(start_offset >= fileObj.size){

            return false;

        }

        var bitrateDiv = null;

        var finishDiv = null;

        var progressBar = null;

        var progressDiv = null;

        if (uploader.curFileitemElement) {

            var $fileItem = $(uploader.curFileitemElement);

            bitrateDiv = $fileItem.find('.bitrate').get(0);

            finishDiv = $fileItem.find('.finish').get(0);

            progressBar = $fileItem.find('.progress-track').get(0);

            progressDiv = $fileItem.find('.percent-label').get(0);

        }

        else {

            bitrateDiv = document.getElementById("bitrate");

            finishDiv = document.getElementById("finish");

            progressBar = document.getElementById('progressbar');

            progressDiv = document.getElementById('percent-label');

        }

        var oldTimestamp = 0;

        var oldLoadsize = 0;

        var totalFilesize = uploader.curFileitemElement.fileObject.totalfilesize;

        if (totalFilesize == 0) {

            return false;

        }

        //将fileObj 设置为当前正在上传的文件

        uploader.currentFile = fileObj;

        //已经完成了的文件的总尺寸

        var totalUploadedsize = uploader.curFileitemElement.fileObject.uploadedsize;

        var uploadProgress = function (evt) {

            if (evt.lengthComputable) {

                var uploadedSize = totalUploadedsize + 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 = totalUploadedsize + 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.style.width = percentComplete+'%';

                        progressDiv.innerHTML = percentComplete.toString() + '%';

                        bitrateDiv.innerHTML = bitrate;

                        finishDiv.innerHTML = finish;

                        oldTimestamp = timestamp;

                        oldLoadsize = evt.loaded;

                        if (typeof uploader.options.ontaskprogress == 'function') {

                            var progress = {

                                'bitrate':bitrate,

                                'finish':finish,

                                'percent':percentComplete

                            };

                            uploader.options.ontaskprogress.call(uploader,progress);

                        }

                    }

                }

            }

            else {

                progressDiv.innerHTML = 'N/A';

            }

        }

        xhr.onreadystatechange = function(){

            if (xhr.readyState == 4 && xhr.status == 200) {

                if (console)

                    console.log( xhr.responseText );

            }

            else if (xhr.status == 400) {

            }

        };

        var uploadComplete = function (evt) {

            progressDiv.innerHTML = '100%';

            uploader.curFileitemElement.fileObject.uploadedsize += uploader.currentFile.size;

            var result = JSON.parse(evt.target.responseText);

            if (result.result == 'success') {

                _onUploadedFileFinished.call(uploader,result.files[0]);

            }

            else {

                uploader.currentFile = null;

                uploader.upload_start = false;

                toast(result.msg);

            }

        }

        var uploadFailed = function (evt) {

            if (!uploader.currentFile) return;

            if (uploader.reconnectId) return;

            uploader.upload_start = false;

            toast("检测到网络故障!两秒后尝试重连...");

            uploader.reconnectId = window.setTimeout(function() {

                _startFileupload.call(uploader,true);

            },2000);

            if (typeof uploader.options.ontaskfail == 'function') {

                uploader.options.ontaskfail.call(uploader,uploader.curFileitemElement.fileObject);

            }

        }

        var uploadCanceled = function (evt) {

            if (uploader.curFileitemElement) {

                $(uploader.curFileitemElement).attr('data-upload','aborted');

                var fileid = uploader.currentFile.fileid;

                //记录文件取消上传

                var uploadEvent = {

                    "event":'abortupload',

                    "taskid":uploader.curFileitemElement.id,

                    "fileid":fileid

                };

                if (uploader.options.notice_url) {

                    $.post(uploader.options.notice_url,uploadEvent,function(data) {

                        if (data.code != 1) {

                            toast(data.msg);

                        }

                    },'json');

                }

                if (typeof uploader.options.onevent == 'function') {

                    uploader.options.onevent.call(uploader, uploadEvent);

                }

                if (uploader.taskIsForceRemove) {

                    delete uploader.curFileitemElement.fileObject;

                    $(uploader.curFileitemElement).remove();

                    uploader.curFileitemElement = null;

                    var $fileitems = $(uploader.fileViewer).children('.upload-fileitem');

                    if ($fileitems.length == 0) {

                        $(uploader.fileViewer).html('<p class="notice">暂无上传任务</p>');

                    }

                }

            }

            uploader.currentFile = null;

            uploader.upload_start = false;

            if (uploader.startbutton) {

                uploader.startbutton.disabled=false;

                $(uploader.startbutton).removeClass('disabled');

                uploader.startbutton = null;

            }

            if (uploader.taskIsForceRemove) {

                uploader.currentXHR = null;

                delete uploader.taskIsForceRemove;

                //在可以强制删除的情况下继续执行下一个任务,但不提示toast

                _startNextTaskupload.call(uploader,true);

            }

            else {

                toast("上传已经被暂停取消或者浏览器断开了连接!");

                if (typeof uploader.options.onpause == 'function') {

                    uploader.options.onpause.call(uploader);

                }

            }

        }

        //设置超时时间,由于是上传大文件,因此千万不要设置超时

        //xhr.timeout = 20000;

        //xhr.ontimeout = function(event){

        //        alert('文件上传时间太长,服务器在规定的时间内没有响应!');

        //}         

        xhr.overrideMimeType("application/octet-stream"); 

        var mimeType = fileObj.type;

        if (!mimeType) {

            mimeType = get_file_mimetype(fileObj.name);

        }

        //附加的文件数据应该放在请求的前面

        var cid = uploader.curFileitemElement.getAttribute('data-cid');

        var serverid = uploader.curFileitemElement.getAttribute('data-serverid');

        formData.append('channelid', cid);

        formData.append('filename', fileObj.name);

        //必须将fileid信息传送给服务器,服务器只有在获得了fileid信息后才对文件做断点续传处理

        formData.append('fileid', fileid);

        //请将文件数据放在最后的域

        var filesize = fileObj.size;

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

        if('msSaveOrOpenBlob' in navigator){

            formData.append("file",blob, fileObj.name);

        }

        else {

            var fileOfBlob = new 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);

        var upload_url_full = uploader.upload_file_url + get_save_path(fileObj.name,uploader.options.savepaths);

        xhr.open('POST', upload_url_full);

        //

        xhr.send(formData);

        uploader.currentXHR = xhr;

        uploader.upload_start = true;

        toast('');

        if (start_offset == 0) {

            var uploadEvent = {

                "event":'startupload',

                "fileid":fileid,

                "cid":cid,

                "serverid":serverid,

                "filename":fileObj.name,

                "mimetype":mimeType

            };

            //开始文件上传

            if (uploader.options.notice_url) {

                $.post(uploader.options.notice_url,uploadEvent,function(data) {

                    if (data.code != 1) {

                        toast(data.msg);

                    }

                },'json');

            }

            if (typeof uploader.options.onevent == 'function') {

                uploader.options.onevent.call(uploader, uploadEvent);

            }

        }

        else {

            //记录文件断点续传

            var uploadEvent = {

                "event":'resumeupload',

                "fileid":fileid,

                "cid":cid,

                "serverid":serverid,

                "filename":fileObj.name,

                "mimetype":mimeType

            };

            if (uploader.options.notice_url) {

                $.post(uploader.options.notice_url,uploadEvent,function(data) {

                    if (data.code != 1) {

                        toast(data.msg);

                    }

                },'json');

            }

            if (typeof uploader.options.onevent == 'function') {

                uploader.options.onevent.call(uploader, uploadEvent);

            }

        }

    }

    var _do_finish_file = function (fileObj,fileInfo) {

        var uploader = this;

        var bitrateDiv = null;

        var finishDiv = null;

        var progressBar = null;

        var progressDiv = null;

        if (!uploader.curFileitemElement) {

            return;

        }

        var $fileItem = $(uploader.curFileitemElement);

        bitrateDiv = $fileItem.find('.bitrate').get(0);

        finishDiv = $fileItem.find('.finish').get(0);

        progressBar = $fileItem.find('.progress-track').get(0);

        progressDiv = $fileItem.find('.percent-label').get(0);

        //模拟进度完成

        var totalFilesize = uploader.curFileitemElement.fileObject.totalfilesize;

        if (totalFilesize == 0) {

            return false;

        }

        //将fileObj 设置为当前正在上传的文件

        uploader.currentFile = fileObj;

        //已经完成了的文件的总尺寸

        var totalUploadedsize = uploader.curFileitemElement.fileObject.uploadedsize;

        var uploadedSize = totalUploadedsize + fileObj.size; 

        var percentComplete = Math.round(uploadedSize * 100 / totalFilesize);

        var finish = uploadedSize;

        if (finish > 1048576)

            finish = (Math.round(finish / (1048576/100)) / 100).toString() + 'MB';

        else

            finish = (Math.round(finish / (1024/100) ) / 100).toString() + 'KB';

        progressBar.style.width = percentComplete+'%';

        progressDiv.innerHTML = percentComplete.toString() + '%';

        bitrateDiv.innerHTML = '';

        finishDiv.innerHTML = finish;

        uploader.curFileitemElement.fileObject.uploadedsize += fileObj.size;

        _onUploadedFileFinished.call(uploader,fileInfo);

    }

    //doupload

    var _startFileupload = function (bReconnect) {

        var uploader = this;

        if (!this.options) {

            alert("发生未知错误,没有正确绑定上传对象。")

            return false;

        }

        if (!this.curFileitemElement) {

            alert("请选择文件后再试!")

            return false;

        }

        if (!bReconnect) {

            if (uploader.currentFile) {

                alert("上传对象已经有文件在上传了,不要重复调用!")

                return false;

            }

        }

        var fileObject = this.curFileitemElement.fileObject;

        if (fileObject.finished) {

            return false;

        }

        var fileObj = null;

        if (!this.currentFile) {

            if (this.curFileitemElement.fileObject.files instanceof Array) {

                for(var i = 0; i < this.curFileitemElement.fileObject.files.length; i ++) {

                    if (!this.curFileitemElement.fileObject.files[i].uploadInfo) {

                        fileObj = this.curFileitemElement.fileObject.files[i];

                        break;

                    }

                }

            }

            else {

                if (!this.curFileitemElement.fileObject.files.uploadInfo) {

                    fileObj = this.curFileitemElement.fileObject.files;

                }

            }

            if (!fileObj) {

                alert("任务文件已经上传完毕!")

                return false;

            }

            //this.currentFile = fileObj;

        }

        else {

            fileObj = this.currentFile;

        }

        var fileid = getFileId(fileObj);

        var t = (new Date()).getTime();

        //通过以下URL获取文件的断点续传信息,必须的参数为fileid,后面追加t参数是避免浏览器缓存

        var url = this.resume_info_url + '?fileid='+fileid + '&t='+t;

        var ajax = new XMLHttpRequest();

        ajax.onerror = function (e) {

            if (uploader.reconnectId) 

                return;

            uploader.reconnectId = window.setTimeout(function() {

                uploader.currentXHR = null;

                _startFileupload.call(uploader,true);

            },2000);

        };

        ajax.onabort = function (evt) {

            uploader.currentFile = null;

            uploader.upload_start = false;

            if (uploader.curFileitemElement) {

                $(uploader.curFileitemElement).attr('data-upload','aborted');

                var fileid = uploader.curFileitemElement.id;

                uploader.curFileitemElement = null;

            }

            if (uploader.startbutton) {

                uploader.startbutton.disabled=false;

                $(uploader.startbutton).removeClass('disabled');

                uploader.startbutton = null;

            }

            toast("上传被取消或者浏览器断开了连接!");

        }

        ajax.onreadystatechange = function () {

            if(this.readyState == 4){

                if (bReconnect) {

                    //目前是重连状态,清除重连标志

                    uploader.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) {

                        _do_upload_file.call(uploader,fileObj,uploadedBytes,fileid);

                    }

                    else {

                        //文件已经上传完成了,就不要再上传了,直接返回结果就可以了

                        _do_finish_file.call(uploader,fileObj,result.file);

                    }

                }else {

                    uploader.currentFile = null;

                    uploader.currentXHR = false;

                    toast('获取文件断点续传信息失败,服务器错误码:'+this.status+', 上传无法继续!');

                }  

            }

        }

        if (typeof this.options.ontaskstart == 'function') {

            if (false === this.options.ontaskstart.call(this,this.curFileitemElement.fileObject)) {

                return false;

            }

        }

        ajax.open('get',url,true);

        ajax.send(null);

        this.currentXHR = ajax;

        return true;

    }

    //任务上传完成后,开启下一个任务

    function _startNextTaskupload(isAfterAbort) {

        var uploader = this;

        var $fileitems = $(this.fileViewer).children('.upload-fileitem');

        $fileitems.each(function(index,elm) {

            if ($(elm).attr('data-upload') != 'finished') {

                if (elm.fileObject) {

                    $(elm).attr('data-upload','uploading');

                    uploader.curFileitemElement = elm;

                    return false;

                }

            }

        });

        if (!uploader.curFileitemElement) {

            if (typeof this.options.onend == 'function') {

                this.options.onend.call(this);

            }

            if (!isAfterAbort) {            

                toast('所有文件已经上传完毕!');

            }

            return false;

        }

        _startFileupload.call(uploader,false);

        return true;

    }

    var fileViewer = document.getElementById(_options.filelist);

    if (!fileViewer) {

        return null;

    }

    fileViewer.uploadOptions = _options;

    if (_options.enabledrop) {

        //禁止浏览器打开文件的默认行为

        document.addEventListener("drop",function(e){//拖离 

            e.preventDefault();

        });

        document.addEventListener("dragleave",function(e){//拖后放 

            e.preventDefault();

        });

        document.addEventListener("dragenter",function(e){//拖进

            e.preventDefault();

        });

        document.addEventListener("dragover",function(e){//拖来拖去  

            e.preventDefault();

        });

        fileViewer.addEventListener("drop",function (e) {

            var fileList = e.dataTransfer.files; //获取文件对象

            if (console) console.log(fileList)

            //检测是否是拖拽文件到页面的操作

            if(fileList.length == 0){

                return false;

            }

            for(var i = 0; i < fileList.length; i++) {

                _addFileToList(this,fileList[i]);

            }

        },false);

    }

    if (_options.fileselector) {

        var fileSelector = document.getElementById(_options.fileselector);

        if (fileSelector) {

            fileSelector.fileViewer = fileViewer;

            fileSelector.addEventListener("change",function (e) {

                var fileViewer = this.fileViewer;

                if (window.File && window.FileList) {

                    if (this.files.length) {

                        for(var i = 0; i < this.files.length; i++) {

                            _addFileToList(fileViewer,this.files[i]);

                        }

                        this.value = '';

                    }

                }

                else {

                    window.alert('抱歉,你的浏览器不支持FileAPI,请升级浏览器!');

                }

            },false);

        }

    }

    return {

        options:_options,

        fileViewer: fileViewer,

        resume_info_url:'',

        upload_file_url:'',

        upload_start:false,

        startbutton:null,

        currentFile: null,  //当前正在上传的文件对象

        curFileitemElement : null, //当前正在上传的 文件 DOM元素

        start:function(btnObj) {

            if (this.upload_start) {

                alert("文件上传正在进行中,请稍候再点击重复长传!")

                return false;

            }

            var $fileitems = $(this.fileViewer).children('.upload-fileitem');

            if ($fileitems.length == 0) {

                toast('没有任何可以上传的文件,请先添加文件再试!');

                return false;

            }

            var uploader = this;

            this.fileViewer.uploader = this;

            $fileitems.each(function(index,elm) {

                if ($(elm).attr('data-upload') != 'finished') {

                    if (elm.fileObject) {

                        $(elm).attr('data-upload','uploading');

                        uploader.curFileitemElement = elm;

                        var uploadsrv_addr = 'http://'+uploader.options.server;

                        uploader.resume_info_url = uploadsrv_addr+'/resume/';

                        uploader.upload_file_url = uploadsrv_addr+'/upload/';

                        return false;

                    }

                }

            });

            if (!this.curFileitemElement) {

                toast('所有文件已经上传完毕,请添加新文件后再试!');

                return false;

            }

            this.startbutton = btnObj;

            if (this.options.authize_url) {

                //如果提供了授权验证URL,则向CMS平台进行验证

                $.get(this.options.authize_url,{"t":(new Date).getTime()},function(result) {

                    if (result.code != 1) {

                        if (typeof this.options.onend == 'function') {

                            this.options.onend.call(this);

                        }

                        toast(result.msg);

                        return;

                    }

                    if (_startFileupload.call(uploader,false)) {

                        if (uploader.startbutton) {

                            uploader.startbutton.disabled=true;

                            $(uploader.startbutton).addClass('disabled');

                        }

                    }

                    else {

                        if (typeof this.options.onend == 'function') {

                            this.options.onend.call(this);

                        }

                    }

                },'json');

                return true;

            }

            else {

                if (_startFileupload.call(uploader,false)) {

                    if (uploader.startbutton) {

                        uploader.startbutton.disabled=true;

                        $(uploader.startbutton).addClass('disabled');

                    }

                    return true;

                }

                return false;

            }

        },

        pause:function () {

            if (this.currentFile) {

                if (this.currentXHR && typeof this.currentXHR.abort == 'function') {

                    this.currentXHR.abort();

                }

            }

        },

        addtask:function(fileobjs) {

            _addFileToList(this.fileViewer,fileobjs);

        },

        removetask:function(taskid) {

            var $fileItemElm = $('#'+taskid);

            if ($fileItemElm.length == 1 && $fileItemElm.hasClass('upload-fileitem')) {

                if ($fileItemElm.attr('data-upload')=='uploading') {

                    alert('任务正在进行中,请停止任务后再删除!');

                    return;

                }

                var itemDom = $fileItemElm.get(0);

                if (itemDom.fileObject) {

                    delete itemDom.fileObject;

                }

                $fileItemElm.remove();

            }

        },

        clear:function() {

            var $fileitems = $(this.fileViewer).children('.upload-fileitem');

            if ($fileitems.length == 0) {

                return false;

            }

            if (!confirm('确实要清除任务列表吗?')) {

                return false;

            }

            if (this.currentFile) {

                if (this.currentXHR && typeof this.currentXHR.abort == 'function') {

                    this.currentXHR.abort();

                }

                if (typeof this.options.onend == 'function') {

                    this.options.onend.call(this);

                }

            }

            $fileitems.each(function(index,elm) {

                if (elm.fileObject) delete elm.fileObject;

            });

            $fileitems.remove();

            this.curFileitemElement = null;

            this.currentFile = null;

            this.currentXHR = null;

            $(this.fileViewer).html('<p class="notice">暂无上传任务</p>');

        },

        version: "2.0"

    };

};

技术交流

- QQ:1918098288

- Mail: [email protected]

继续阅读