在cli程序中,輸入命令獲得連續的輸出已是一種進度而美觀的頁面交互形式,比如下圖:html
而web程序裏也有相似的場景,好比執行一個耗時任務,除了顯示出等待圖標外,用戶還但願把執行的狀態及時顯示出來.以下圖:web
這樣的界面如何設計呢?個人思路以下:ajax
1.點擊按鈕後,產生一個新ID,後臺運行的線程拿到id後開始運行並及時往數據庫中插入記錄,同時id被送回到前臺;數據庫
2.前臺拿到id後,開始以此id輪詢後臺數據表,並將取得的數據顯示出來,而取得的數據是後臺運行的線程不斷寫入的;app
3.後臺線程寫入狀態1後,即認爲任務完成,前臺取得此數據後再也不輪詢並恢復成初始狀態.ide
下面請看具體實現:函數
前臺點擊按鈕觸發Ajax:spa
$("#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日--