Facade模式实现文件上传(Flash+HTML5)

一、前言

确定了渐进式增强的上传方式,接下来我们需要将上传功能从具体的业务逻辑中剥离出来,作为公共组件供业务层调用。这就要求我们必须对业务层隐藏上传细节,只暴露统一的上传API。这时候大家是不是跟我一样想到了Facade模式?
 
二、Facade模式实现文件上传,代码示例:
/*
上传组件,IE浏览器默认flash上传,其它浏览器html5
示例:
    var fileUpload = new FileUpload({
        container: document.getElementById("uploadBtn"),
        onselect: function (files) {
            var self = this;
            $(files).each(function (i, n) {
                updateUI(n);
            });
            setTimeout(function () { //异步,等待onselect函数return后才能调用upload
                self.upload();
            }, 10);
        },
        onprogress: function (fileInfo) {
            updateUI(fileInfo);
        },
        oncomplete: function (fileInfo, responseText) {
            updateUI(fileInfo);
           
        }
    });
*/
function FileUpload(options) {
    var uploader=null;
    if (options) {
        //为什么要多创建一级div容器?flash 的activex创建后,再改变位置会引起activex对象失效,所以要在创建前就定好位
        var div = document.createElement("div");
        div.id = "flashUploadDiv";
        document.body.appendChild(div);
        var c = $(options.container);
        //绝对定位到上传按钮的坐标,flash本身为透明遮罩
        $(div).css({
            position: "absolute",
            left: c.offset().left + "px",
            opacity:0,
            top: c.offset().top + "px"
        });

        if ($.browser.msie || options.uploadType == "flash") {

            //flash上传方式
            var url = "Richinfo_annex_upload.swf";
            var so = new SWFObject(url, "flashupload", c.width(), c.height());
            so.addParam("wmode", "transparent");
            so.write("flashUploadDiv");

            options.activexObj = document.getElementById("flashupload");

            window.JSForFlashUpload = new FlashUpload(options);
            uploader = JSForFlashUpload;

          
        } else {

            $(div).html(['<form style="" enctype="multipart/form-data" id="fromAttach" method="post" action="" target="frmAttachTarget">',
                 '<input style="height: ', c.height(), 'px;', c.width(), 'px" type="file" name="uploadInput" id="uploadInput" multiple="true">',
                 '</form>',
                 '<iframe id="frmAttachTarget" style="display: none" name="frmAttachTarget"></iframe>'].join(""));
            options.uploadInput = document.getElementById("uploadInput");
            uploader = new Html5Upload(options);
           
        }
    }

    this.upload = function () {//触发上传请求
        //alert("uploader.load");
        uploader.upload();
    },
    this.cancel = function () {//取消上传
        uploader.cancel();
    }
    this.getUploadFiles = function () {//获取上传队列
        uploader.getUploadFiles();
    }
    
    $.extend(options, this);//继承FileUpload的能力
}
var FlashUpload = function(options){
    
    var resultObject = {
        activexObj: options.activexObj,
        upload:function(){
            this.activexObj.uploadAll();
        },
        cancel: function () {
            this.activexObj.cancel();
        },
        getUploadUrl: function () {
            return this.agent.getUploadUrl();
        },
        getUploadFiles: function () {
            return this.uploadFiles;
        },
        onload: function (param) {
            this.agent = {};
            if (options) {
                this.agent = options;
            }
            param["filter"] = ["images图片(*.jpg;*.png;*.bmp)", "video(*.flv;*.avi;*.rmvb)"];
            param["uploadFieldName"] = "filedata";
            //options["filter"] = ["eml邮件(*.eml)"];
            //options["filter"] = ["所有文件(*.*)"];
            return param;
        },
        onselect: function (xmlFileList, jsonFileList) {

            for (var i = 0; i < jsonFileList.length; i++) {
                jsonFileList[i].fileName = decodeURIComponent(jsonFileList[i].fileName);
                jsonFileList[i].state = "waiting";
                /*if (jsonFileList[i].fileSize > 100000) { //大于100K不上传
                    jsonFileList.splice(i, 1);
                    i--;
                }*/
            }
            //uploadView.onselect(jsonFileList);
            this.agent.onselect && this.agent.onselect(jsonFileList);

            this.uploadFiles = jsonFileList;
            return jsonFileList;
        },
        onprogress: function (taskId, sendedSize, uploadSpeed, fileInfo) {
            fileInfo.taskId = taskId;
            fileInfo.sendedSize = sendedSize;
            fileInfo.percent = Math.round((sendedSize / fileInfo.fileSize) * 100);
            fileInfo.state = "uploading";
            fileInfo.fileName = decodeURIComponent(fileInfo.fileName);//防止乱码,flash里面做了encode
            //alert(fileInfo.percent);
            this.agent.onprogress && this.agent.onprogress(fileInfo);
        },
        oncomplete: function (taskId, responseText, fileInfo) {
            fileInfo.taskId = taskId;
            fileInfo.state = "complete";
            fileInfo.fileName = decodeURIComponent(fileInfo.fileName);//防止乱码,flash里面做了encode
            this.agent.oncomplete && this.agent.oncomplete(fileInfo, responseText);
        },
        onerror: function (taskId, errorCode, errorMsg) {
            alert("文件上传失败:" + errorMsg);
            this.agent.onerror && this.agent.onerror(errorMsg);
        },
        onmouseover: function () {

        },
        onmouseout: function () {

        },
        onclick: function () {
            return true;//返回false不会弹出文件选择框
            //alert("onclick");
        }

    }
    return resultObject;
}
var Html5Upload = function (options) {
    var resultObject = {
        uploadInput: null,
        currentFile: null,
        uploadFiles:[],//待上传的文件
        completeFiles:[],//已完成的文件
        init: function () {
            var self = this;
            this.agent = options;
            this.uploadInput = options.uploadInput;
            this.uploadInput.onclick = this.onclick;
            this.uploadInput.onchange = function () {
                var files = this.files;
                var result = [];
                for (var i = 0; i < files.length; i++) {
                    console.log(files[i]);
                    result.push({
                        fileName: files[i].name,
                        fileSize: files[i].size,
                        fileData: files[i],
                        state : "waiting",
                        taskId: Math.random().toString().substr(2)
                    });
                }
                self.uploadFiles = result;
                self.onselect(result);
            }
        },
        getFileUploadXHR: function () { //单例
            if (!window.fileUploadXHR) {
                fileUploadXHR = new XMLHttpRequest();
            }
            this.xhr = window.fileUploadXHR;
            return fileUploadXHR;
        },
        getUploadUrl: function () { //获取上传地址
            return this.agent.getUploadUrl();
        },
        getUploadFiles:function(){ //获取上传队列
            return this.uploadFiles.concat(this.completeFiles);
        },
        upload: function () {//开始上传请求
            this.uploadNextFile();
        },
        cancel:function(){  //取消上传
            this.xhr.abort();
        },
        uploadNextFile: function () { //每个上传文件会触发
            var fileInfo = this.uploadFiles.shift();
            this.completeFiles.push(fileInfo); //存入已完成列表
            this.currentFile = fileInfo;
            if (fileInfo) {
                var self = this;
                var xhr = this.getFileUploadXHR();

                xhr.upload.onabort = function (oEvent) { };
                xhr.upload.onerror = function (oEvent) { self.onerror(oEvent); };
                xhr.upload.onload = function (oEvent) { self.onload(oEvent); };
                xhr.upload.onloadend = function (oEvent) { };
                xhr.upload.onloadstart = function (oEvent) { };
                xhr.upload.onprogress = function (oEvent) {
                    console.log(oEvent);
                    fileInfo.state = "uploading";
                    fileInfo.sendedSize = oEvent.position;
                    fileInfo.percent = Math.round((oEvent.position / oEvent.total) * 100);
                    self.onprogress(fileInfo);
                };
                //xhr.ontimeout = function(oEvent){This.ontimeout(oEvent);};
                xhr.onreadystatechange = function (oEvent) {
                    if (xhr.readyState == 4) {
                        if (xhr.status == 200) {
                            var responseText = xhr.responseText;
                            self.oncomplete(fileInfo);
                        }
                    }
                };


                var url = this.getUploadUrl();
                xhr.open("POST", url, true);

                //xhr.timeout = this.timeout; //timeout
                function getFormData(fileInfo) {
                    var formData = new FormData();
                    formData.append("filedata", fileInfo.fileData);
                    return formData;
                }
                var fd = getFormData(fileInfo);
                xhr.send(fd);
            }
        },
        onclick: function () {

        },
        onselect:function(files){
            this.agent.onselect && this.agent.onselect(files);
        },
        onload:function(e){
        },
        onprogress: function (fileInfo) {
            this.agent.onprogress && this.agent.onprogress(fileInfo);
        },
        oncomplete: function (fileInfo) {
            fileInfo.state = "complete";
            this.agent.oncomplete && this.agent.oncomplete(fileInfo);
            this.uploadNextFile();
        }
    }

    resultObject.init();
    return resultObject;
}

三、调用示例:

<html>
<head>
<script src="jquery-1.8.3.js"></script>
<script src="swfobject.js"></script>
<script src="upload.js"></script>
</head>
<body>
<div style="position:absolute">
</div>
<ul id="uploadList"></ul>
</body>
<script>
    function updateUI(fileInfo) {
        var ul = $("#uploadList");
        switch (fileInfo.state) {
            case "waiting":
                ul.append("<li taskId='" + fileInfo.taskId + "'>" + fileInfo.fileName + "(等待上传...)</li>");
                break;
            case "uploading":
                ul.find("li[taskId=" + fileInfo.taskId + "]").html(fileInfo.fileName + "(" + fileInfo.percent + "%)");
                break;
            case "complete":
                ul.find("li[taskId=" + fileInfo.taskId + "]").html(fileInfo.fileName + "(完成)");
                break;
        }
    }

    var fileUpload = new FileUpload({
        container: document.getElementById("uploadBtn"),
        //uploadType:"flash",
        getUploadUrl: function () {
            return "upload.ashx";
        },
        onselect: function (files) {
            var self = this;
            $(files).each(function (i, n) {
                updateUI(n);
            });
            setTimeout(function () { //异步,等待onselect函数return后才能调用upload
                self.upload();
            }, 10);
        },
        onprogress: function (fileInfo) {
            updateUI(fileInfo);
        },
        oncomplete: function (fileInfo, responseText) {
            updateUI(fileInfo);
            console.log(this.getUploadFiles());
        }
    });
</script>
</html>

四、结束语

以上源码仅提供上传组件化思路,实际应用中要考虑更多,比如:弱网络环境下需要分块上传,断点续传,异常情况下的日志上报等等。

以上源码非本人原创,代码来源于139邮箱前端团队内部分享。希望对大家有所帮助。

五、参考资料
原文地址:https://www.cnblogs.com/hellohuman/p/3946603.html