由于现有功能的导出主要受限,导出数据量过于庞大后,会导致导出异常缓慢,有时候会出现浏览器崩溃,经过讨论,按照导出走一个页面的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即可。