如何让Web程序在点击按钮后出现如执行批处理程序般的效果

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

相关文章
相关标签/搜索