GWT使用HTML5实现批量上传和进度显示

来源:互联网 发布:淘宝追加评论哪里看 编辑:程序博客网 时间:2024/05/18 00:03

GWT使用HTML5实现批量上传和进度显示

目标效果

嘿嘿,先放个酸葡萄,看看有没有人来啃。

  1. 准备上传
    准备上传

  2. 批量选择上传文件
    批量选择上传文件

  3. 上传中并显示整体进度
    上传中并显示整体进度

  4. 上传完毕并按格式插入内容
    上传完毕并按格式插入内容

  5. 单文件上传效果
    这里写图片描述
    这里写图片描述
    这里写图片描述

方案

比较了2种方案:
方案1:其实HTML5方案GWT都已经给我们准备好了,就是Elemental.jar的支持代码。但是要使用它,付出的代价就是必须使用SuperDevMode来开发,想想使用Chrome devTool调试模式和全量编译带来的效率影响。做了一个艰难的决定,不用Elemental!
方案2:不用Elemental是否可以用纯javascript?用js写,是非常简单。但是怎样用GWT去干呢?有办法,绝对有办法,办法是折腾出来的。请看下面的实现步骤。

实现步骤

  1. 首先要实现一些HTML5的基础类例如支持多文件及本地文件大小检测的FileUpload及File对象等,我们可以选择lib-gwt-file来实现一些基础的功能(https://www.vectomatic.org/libs/lib-gwt-file),有别人的肩膀,就不用关门造轮子。

  2. 在做所有事情之前,先来看javascript是怎样实现一个典型的html5的上传的。

    function uploadFile(file){    var url = 'server/index.php';    var xhr = new XMLHttpRequest();    var fd = new FormData();    xhr.open("POST", url, true);    xhr.onreadystatechange = function() {        if (xhr.readyState == 4 && xhr.status == 200) {            // Every thing ok, file uploaded            console.log(xhr.responseText); // handle response.        }    };    fd.append("upload_file", file);    xhr.send(fd);}

    从这个代码能够看到AJAX上传一个文件的流程:
    a)需要XMLHttpRequest
    b)需要FormData
    c)需要将file对象添加到FormData
    d)send这个file对象

  3. 把js的流程弄明白后,接下来用GWT来打造它。FormData是没有这个的(Elemental有,但是方案2不使用它),没有类,自己造一个!由最基础的JavaScriptObject来扩展:

    public class CcUploadFormData extends JavaScriptObject {    protected CcUploadFormData(){}    public final native void append(String name, File file) /*-{        this.append(name, file);    }-*/;    public static native CcUploadFormData create() /*-{        return new FormData();    }-*/;    public final native void append(String name, String value) /*-{        this.append(name, value);    }-*/;}

    上面能够看到,其实也就是把javascript对象包装了下,这里我只扩展了几个需要的关键方法为java实现。

  4. 有了FormData,关联File的方法也有了。下一步要将对象通过XMLHttpRequest异步发送到服务器。蛋疼的是(不要担心GWT的蛋疼,因为GWT就是睾丸疼=蛋疼的缩写,疼多了就麻木了^_^)XMLHttpRequest是没有send这个FormData接口的。没关系,将js方法扩展一个即可。

    public class CcXMLHttpRequest extends XMLHttpRequest {    protected CcXMLHttpRequest(){}    public final native void send(CcUploadFormData data) /*-{        this.send(data);    }-*/;}
  5. 然后服务端怎样接收呢?其实和普通的上传没有两样,都是用multi-part的方式上传。例如spring mvc可以这样写:

    public FileInfDto upload(@RequestParam("file") MultipartFile file){ //your code}
  6. 处理多文件发送。多文件发送怎么办呢?lib-gwt-file很好的处理了这个问题FileUploadExt当设置为multi时,能够通过getFiles()获取到各个File对象。并且能在发送前处理文件大小的判断和文件类型的判断。看看lib-gwt-file的example,在此不再赘述。

  7. 处理进度条这玩意lib-gwt-file没有支持。又得自己打造了,老规矩,先看明白javascript的实现:

    xhr.upload.addEventListener("progress", function(e) {            var pc = parseInt(100 - (e.loaded / e.total * 100));            progress.style.backgroundPosition = pc + "% 0";        }, false);xhr.onreadystatechange = function(e) {            if (xhr.readyState == 4) {                progress.className = (xhr.status == 200 ? "success" : "failure");            }        };

    如上,其实也很简单,就是添加侦听而已。除了这两个,还有一个error的侦听。

  8. 使用JSNI给XMLHttpRequest 添加侦听事件:

        private native void addProgressHandler(ProgressCallback progressCallback)/*-{        var listener = function(event){            progressCallback.@org.ccframe.client.components.fileupload.ProgressCallback::onProcess(ZDD)(event.lengthComputable,event.loaded,event.total);        };        this.@org.ccframe.client.components.fileupload.CcBaseFileUpload::xmlHttpRequest.upload.addEventListener("progress", listener, false);    }-*/;    private native void addCompleteHandler(Runnable callback)/*-{        var listener = function(event){            callback.@java.lang.Runnable::run()();        };        this.@org.ccframe.client.components.fileupload.CcBaseFileUpload::xmlHttpRequest.upload.addEventListener("load", listener, false);    }-*/;    private native void addCanceledHandler()/*-{        var listener = function(event){            alert('abort');        };        this.@org.ccframe.client.components.fileupload.CcBaseFileUpload::xmlHttpRequest.upload.addEventListener("abort", listener, false);    }-*/;

    为了方便扩展,我定义了一个ProgressCallback,用来专门处理进度的回调,因此可以扩展自己的进度条事件。

    public interface ProgressCallback{    public abstract void onProcess(boolean lengthComputable, double loaded, double total);}
  9. 然后怎样触发上传呢,触发FileUploadExt的click即可:

            addButton.addSelectHandler(new SelectHandler() {            @Override            public void onSelect(SelectEvent event) {                getFileUploadExt().click();            }        });
  10. 然后就是编写单个上传以外的触发事件了。我的策略是定义一个fileQueue用来记录待传输的文件,传输完一个从队列剔除一个。

    private LinkedList<File> fileQueue = new LinkedList<File>();

    然后每个上传的循环里是这样的:

                Scheduler.get().scheduleFixedDelay(new RepeatingCommand(){                @Override                public boolean execute() {                    if(!CcBaseFileUpload.this.isAttached() || fileQueue.isEmpty()){ //终止传输条件,关闭或者队列传输完成                        return false;                    }                    if(workFile != null){ //retry条件:一个传输完成,等待下一个传输                        return true;                    }                    workFile = fileQueue.remove();                    onNextFileUpload((totalFileCount - fileQueue.size()), totalFileCount);                    try{                        CcUploadFormData ccUploadFormData = CcUploadFormData.create();                        ccUploadFormData.append("file", workFile);                        xmlHttpRequest.open("POST", UPLOAD_URL);                        final RequestCallback callback = new RequestCallback(){                            @Override                            public void onResponseReceived(Request request, Response response) {                                switch(response.getStatusCode()){                                    case 404:                                        break;                                    case 500:                                        JSONObject object500 = JSONParser.parseStrict(response.getText()).isObject();                                        if(object500 != null){                                            JSONValue errorObjectResp = object500.get("errorObjectResp");                                            if(errorObjectResp != null){                                                String errorText = errorObjectResp.isObject().get("errorText").isString().stringValue();                                                ViewUtil.error("错误", errorText.contains("SizeLimitExceededException") ? "上传文件超过限制" : errorText);                                            }                                            fileUploadCompleteHandler.onUploadError(workFile);                                        }                                        break;                                    case 200:                                        JSONObject object200 = JSONParser.parseStrict(response.getText()).isObject();                                        if(object200 != null){                                            FileInfDto fileInfBarDto = new FileInfDto();                                            fileInfBarDto.setFileNm(object200.get(FileInf.FILE_NM).isString().stringValue());                                            fileInfBarDto.setFilePath(object200.get(FileInf.FILE_PATH).isString().stringValue());                                            fileInfBarDto.setFileTypeNm(object200.get(FileInf.FILE_TYPE_NM).isString().stringValue());                                            fileInfBarDto.setFileUrl(object200.get(FileInfDto.FILE_URL).isString().stringValue());                                            if(fileUploadCompleteHandler != null){                                                fileUploadCompleteHandler.onUploadComplete(fileInfBarDto);                                            }                                        }                                        break;                                }                                if(fileQueue.isEmpty()){                                    onQueueUploadFinish();                                }                            }                            @Override                            public void onError(Request request, Throwable exception) {                                ViewUtil.error("上传失败", exception.toString());                            }                        };                        final CcRequest request = new CcRequest(xmlHttpRequest, 1800*1000, callback); //传输限制30分钟                        xmlHttpRequest.setOnReadyStateChange(new ReadyStateChangeHandler() {                          public void onReadyStateChange(XMLHttpRequest xhr) {                            if (xhr.getReadyState() == XMLHttpRequest.DONE) {                              xhr.clearOnReadyStateChange();                              request.fireOnResponseReceived(callback);                            }                          }                        });
  11. 主要的流程和核心思想讲解到这,离目标还有一段路,剩下的UI布局及交互代码就要靠各位自己来完成了。
0 0
原创粉丝点击