如何讓Web程序在點擊按鈕後出現如執行批處理程序般的效果

在cli程序中,輸入命令得到連續的輸出已經是一種進度而美觀的頁面交互形式,好比下圖:

而web程序裏也有類似的場景,比如執行一個耗時任務,除了顯示出等待圖標外,用戶還希望把執行的狀態及時顯示出來.如下圖:

這樣的界面如何設計呢?我的思路如下:

1.點擊按鈕後,產生一個新ID,後臺運行的線程拿到id後開始運行並及時往數據庫中插入記錄,同時id被送回到前臺;

2.前臺拿到id後,開始以此id輪詢後臺數據表,並將取得的數據顯示出來,而取得的數據是後臺運行的線程不斷寫入的;

3.後臺線程寫入狀態1後,即認爲任務完成,前臺取得此數據後不再輪詢並恢復成初始狀態.

下面請看具體實現:

前臺點擊按鈕觸發Ajax:

    $("#startPhonexCrawl").click(function(){
        var taskId=$("#taskIdTxt").val();
        
        if(taskId!="0"){
            // 有任務啓動則取狀態
            alert("有任務在執行,請稍等...");
        }else{
            // 沒有任務則啓動任務
            $.get("/startCrawl/phonex",{},function(data,textStatus){
                var taskId=data;
                $("#taskIdTxt").val(taskId);
                $("#crawlsDiv").html("");
                $("#loadingImg").show();
                showTask();
            });
        }
    });

後臺接到請求後一邊啓動線程,一邊將產生的taskid回傳:

    /**
     * Start crawl and return crawltask id
     * @param crawlName
     * @return
     */
    @RequestMapping("/startCrawl/{crawlName}")
    String startCrawl(@PathVariable("crawlName") String crawlName) {
        logger.info("準備啓動爬蟲:"+crawlName);
        long taskId=crawlMapper.getNextTaskId();
        
        BaseCTH cth=null;
        
        if("phonex".equalsIgnoreCase(crawlName)) {
            cth=new PhonexCTH();
            logger.info("Phonex crawl thread is ready.");
        }else if("163".equalsIgnoreCase(crawlName) || "Netease".equalsIgnoreCase(crawlName)) {
            cth=new NeteaseCTH();
            logger.info("Netease crawl thread is ready.");
        }else if("snowball".equalsIgnoreCase(crawlName)) {
            cth=new SnowballCTH();
            logger.info("Snowball crawl thread is ready.");
        }else {
            logger.warn("Error crawlName:"+crawlName+",so no crawl thread started.");
            taskId=0;
        }
        
        if(cth!=null) {
            cth.setTaskId(taskId);
            cth.setStockMapper(stockMapper);
            cth.setCrawlMapper(crawlMapper);
            cth.start();
            logger.info("Crawl thread started.");
        }
        
        return String.valueOf(taskId);
    }

從上面的程序也可看出,前臺按鈕和後臺具體爬蟲聯繫的紐帶是crawlName,這樣處理後,如果要增加新爬蟲,只要前臺做個鏈接,然後在分支中與具體爬蟲聯繫上即可.

 

前臺的ajax會在得到返回id後調用showTasks函數:

function showTask(){
    var taskId=$("#taskIdTxt").val();
    
    if(taskId!="0"){
        $.get("/getCrawlTasks/"+taskId,{},function(data,textStatus){
            var message="";
            var state="";
            var percent="";
            
            for(var i=0,l=data.length;i<l;i++){
                message+=data[i].ctime+" "+data[i].msg+"<br/>";
                state=data[i].state;
                percent=data[i].percent;
             }

             $("#crawlsDiv").html(message);
             $("#percentSpan").html(percent+"%");
             
             if(state=="1"){
                 //alert("爬蟲任務"+taskId+"結束");
                 // 如果任務結束則可啓動下一任務
                 clearTimeout(timerHandler);
                 $("#taskIdTxt").val("0");
                 $("#loadingImg").hide();
                 $("#percentSpan").html("");
             }else{
                 timerHandler=setTimeout("showTask()",3000);
             }
        });
    }
}

showTasks函數會在結束前查看數據狀態,如果狀態不是1則會以三秒爲間隔不斷調用自己,從而達到輪詢的目的,而輪詢取狀態的後臺函數是

    @RequestMapping("/getCrawlTasks/{taskId}")
    List<CrawlTask> getCrawlTasks(@PathVariable("taskId") String taskId) {
        logger.info("取得taskId="+taskId+"的爬蟲狀態:");
        
        return crawlMapper.getCrawlTasks(taskId);
    }
    @Select("select id,taskid,state,msg,DATE_FORMAT(ctime,'%Y-%m-%d %T') as ctime,percent from crawltask where taskid=#{taskId} order by id ")
    List<CrawlTask> getCrawlTasks(@Param("taskId") String taskId);

這樣,每過三秒就會從crawltask表裏取得信息顯示在頁面上.

整套設計裏,taskid是前臺從db取值和後臺線程往數據庫寫值的聯繫紐帶,有了它的出現,前後臺可以在互不知情的情況下良好配合.

當從後臺取得狀態爲1時,下面語句便會發揮作用:

clearTimeout(timerHandler);

timerHandler是啓動時的句柄,而一旦clear掉,timeout便不會再起作用,從而結束輪詢.

這就是全部設計過程.

--2020年5月6日--

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章