• 如何让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日--

  • 相关阅读:
    Greedy Gift Givers 贪婪的送礼者
    USACO 1.1.3 Friday the Thirteenth 黑色星期五
    Prime Matrix(暴力出奇迹)
    博弈论
    好车牌
    C语言中动态内存的分配(malloc,realloc)
    Saruman's Army(贪心)
    Python练习——循环2
    Fox and Number Game
    Repair the Wall (贪心)
  • 原文地址:https://www.cnblogs.com/heyang78/p/12837015.html
Copyright © 2020-2023  润新知