异步下载中心处理方式

来自技术开发小组内部wiki
跳转至: 导航搜索

由于现有功能的导出主要受限,导出数据量过于庞大后,会导致导出异常缓慢,有时候会出现浏览器崩溃,经过讨论,按照导出走一个页面的ajax异步生成一个任务id,生成后引导到一个下载中心的页面,服务器下载完成后,会吧文件路径更新出来,用户来下载附件。

需要用到的表: fmb_download_master

总结处理分三步走:

1、页面js增加导出交互绑定

2、js处理返回的回调(重点是处理crontab的请求接收)

3、处理具体业务实现的crontab书写(由js定义的模块决定)

具体的做法为:

1、在一个页面中引用js文件:

<script src="/static/js/export.js" type="text/javascript"></script>

2、在“导出”按钮中写一个js按钮的点击监听事件,写入以下代码,如:

 $(".data_export").click(function(){
        var searchURL = GetRequest(); //已经在export.js实现,不需要手动处理
        //取url中的参数
         var total="<?=$total?>"; //总记录数
         var page_size = 1000;

         var total_page = total>0 ? Math.ceil(total/page_size) : 0; //分页总条数,对应的总的需要导出的页数,比如:一万条数据,步长为:1000.则总分页数为:10
         var page_size = "<?=$step?>"; //,每页的分页数
         var type ="coupon_list"; //定义的业务类型:目前扩展的有:
         var settle_desc = "优惠券券号明细";
         var cate_name = "coupon"; //标识分类说明是那种类型的分类

      //异步需要统一定义的返回参数,字段不能变,后面的值可以动态调整
         var asyncData = {
                total:total,
                total_page:total_page,
                params:searchURL,
                type:type,
                file_desc:settle_desc,
                page_size: page_size,
               method: "coupon_list",//处理异步的核心方法
        };
        var url ="/common/download_center/write_anync_export"; //生成任务后跳转的url:
        //移除当前的类
        $(this).removeClass("data_export");
        $.ajax({
            type: 'GET' ,
            url : url,
            dataType:"JSON",
            data:asyncData, //需要传递处理的参数
            async:true,
            success: function(data){
                try{
                      if(data.errno!=0){
                            $(this).addClass("data_export");
                            alert(data.errmsg);
                            return;
                      }else{
                           //异步处理接口地址
                        accept_task(asyncData.method,data.data.master_id);
                        alert(data.errmsg);
                        //跳转到一个新的页面
                        location.href="/common/download_center/get_list?master_id="+data.data.master_id+'&is_refresh=1';
                        return true;
                      }
                }catch(e){}
            }
    });

上面的方法的核心是在拿到走一个异步会生成一个任务id(data.data.master_id),返回后,处理发起crontab:核心是调用accept_task,第一个参数为:具体执行的文件在cmdrun/async_export(该参数已扩展,如果需要单独定义,可单独调用),第二个为执行的处理函数,

下面为异步回调后发起crontab的回调js

异步完成后,调用实际需要执行的crontab的任务回调:(accept_task)
//接收任务
function accept_task(method,master_id){
   if(!method && !master_id){
      return false;
   }
  var url ="/common/download_center/send_topic";
  $.ajax({
      type: 'POST' ,
      url : url,
      dataType:"JSON",
      data:{method:method,master_id:master_id}, //需要传递处理的参数
      async:true,
      success: function(data){ 
      }
  });
}

实际上执行的脚本路径为:【测试机】

cd /home/www/fmb.admin/public_html;/usr/local/php5.3/bin/php index.php test cmdrun async_export coupon_list 309 

如果是线上为以下调用地址:【线上】

cd /home/www/fmb.admin/public_html;php index.php product cmdrun async_export coupon_list 3

coupon_list对应为js里定义回调的asyncData里的method 代码如下图(具体需要些的业务逻辑) cmdrun/async_export->coupon_list

GetRequest的代码实现:

//处理导出的常用的js,通用函数获取url中的参数,在异步钱通过该方法获取

function GetRequest() {   
  var url = location.search; //获取url中"?"符后的字串  
     // url = urldecode(url);
   if(url){
   url = url.substring(1);
   }
   return url;  

 
导出的具体业务代码:
//券组下载导出
public function coupon_list($master_id){
if(!$master_id){
exit("no master_id\n");
}
set_time_limit(500);
$master_id = intval($master_id);
echo "master_id:".$master_id."\n";
$info = $this->download_master_model->get_donwload_config_info($master_id);
 if(!$info){
exit("no master-data \r\n");
}
$use_status = array(-1=>'不限',0=>'未使用',1=>'使用中',2=>'已使用','3'=>'已冻结');
    $send_status = array(-1=>'不限',0=>'未派发',1=>'已派发');
$pageSize = intval($info['fenpi_num']); //步长
$this->load->model ( 'coupon/user_coupon_model', 'user_coupon' );
$this->load->model('tickets/goods_category_model', 'category');

 $params = trim($info['params']);
  //处理接收的参数
$where_data = $this->tools->product_params($params);

//查询搜索条件组合
$condition = $this->user_coupon->trans_coupon_condition($where_data);  //这个地方和列表一致,已经做了封装,以后改动只需要改着一个函数,不用重复定义。
//获取票种分类
$allCategoryList  = $this->category->get_category_list();
ini_set('memory_limit', '700M');
$num = $this->user_coupon->get_user_coupon_num($condition);
$export_coupon= array();
if($num>0){
$exportNum = ceil($num/$pageSize);
$d= range(1, $exportNum);
echo "total-num:".$num.",total-page:".$exportNum."\n";
foreach($d as $i){
 $limit = ($i-1)*$pageSize;
 //处理优惠券导出列表
$list = $this->user_coupon->get_coupon_list_for_ex($condition,$limit,$pageSize);
if($list && !empty($list)){
$data= $this->user_coupon->compare_user_coupon($list,$use_status,$send_status,$allCategoryList);
echo "this page is :".$i."\n";
$export_coupon =array_merge($data,$export_coupon);
}
}
}
$dataType = array(
'coupon_sn'=>'1','status_name'=>'1','coupon_name'=>'1','coupon_money'=>'1','min_amount'=>'1',
'start_end_time'=>'1','send_status_name'=>'1','send_username'=>'1','bind_status'=>'1','bind_username'=>'1',
'iphone' => '1','use_status_name'=>'1','use_username'=>'1','use_time'=>'1','order_sn'=>'1','aid'=>'1',
'catename'=>'1','activity_id' => '2','totalmoney'=>'1','settle_price'=>'1','distribute_time'=>'1',
);
$headTitle = array(
'券号','状态','所在券组','面额','消费限额','有效期'
,'发送状态','发送账户(用户名)','绑定状态','绑定账户(用户名)'
,'用户手机号','使用状态','使用账户(用户名)','使用时间'
,'消费订单号','产品ID','产品分类','限产品ID','消费订单总金额'
,'结算总金额','派发时间',
);
//数据转换
$coupon_data = Array_transdata($export_coupon,$dataType);
$datas = array(
'headeTitle' => $headTitle,
'headerType'=>array(1,1,1,3,3,1,1,1,1,1,1,1,1,1,1,1,1,1,3,3,1),
'data'=>$coupon_data,
);
 
//处理接口返回值和任务状态更新
$result =$this->tools->return_result($datas,$master_id); //说明下:这里调用后在crontab处理导出后,会根据返回的状态去吧当前的任务更新成已生成
}

回调后,会从后台发起一个crontab的回调定时,具体代码为:
 public function send_topic(){
       $method = trim($this->input->post('method')); //总记录行数,需要从后台传过来
       $module = $this->input->post('module') ? trim($this->input->post('module')) :'async_export';//调用的类的名称
       $master_id = intval($this->input->post('master_id')); //接管的任务id
       if(!$method || !$master_id){
          $result = array('errno'=>2,'errmsg'=>'操作异常','data'=>);
          echo json_encode($result);
          exit();
       }
        $this->tools->export_exec($module,$method,$master_id); //执行并下载导出更新定时
        $result = array('errno'=>1,'errmsg'=>'success','data'=>);
        echo json_encode($result);
        exit();
    }

tools.php中实现了export_exec的任务的接收;
/**
* 执行异步的导出操作,导出都指向cmdrun下的async_export控制器
* @param type string $method 方法名
* @param $section string $ 具体调用的类的名称
* @param type int $master_id fmb_download_master表中的master_id
* @param $is_exec bool true:执行对应的crontab地址 false:返回执行的命令行方便存储
* @return boolean 
*/
public function export_exec($section=,$method = ,$master_id = 0,$is_exec= true){
if(!$method || !$master_id){
return false;
}
$module_name  = $section ? $section  : 'async_export';
$public_html = "/home/www/fmb.admin/public_html;";
$ev = ENVIRONMENT;
if($ev!='product'){
$php_str = '/usr/local/php5.3/bin/php';
}else{
$php_str = 'php';
}
if($ev=='local'){
$ip = $_SERVER['REMOTE_ADDR'];
$public_html = "/data2/local-dev/".$ip."/fmb.admin/public_html;";
}
$exec_comm = "cd ".$public_html.$php_str." index.php ".$ev." cmdrun ".$module_name." ".$method." ".$master_id." >> /home/www/fmb.photo/newphplog/logs/async_export.log 2>&1";
if($is_exec){
exec($exec_comm,$output);
return true;
}else{
return $exec_comm;
}
}


注意点:

1、页面上只需要做异步的js事件监听,定义好回调函数。

2、写自己的单独的处理导出的流程,写完后调用accept_task函数,前期可先屏蔽这个函数,防止报错,调通后开启。

3、GetRequest和accept_task为回调已经实现的,不需要单独定义,值需要在引用钱导入export.js即可。