在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日--