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