之前做定时任务,都是通过linux的crontab来实现,但是定时任务多了,就不好管理。一个是是定时的备注不好写,另一个是定时日志的结果没法存入数据库。
1、按crontab的格式设置定时任务
2、执行结果存入数据库,包括执行结果,执行时间
3、秒级的定时
composer require dragonmantank/cron-expression
1、添加一个自定义指令 Task
php think make:command Task task
2、在config/console.php中注册该指令
// 指令定义
'commands' => [
'task' => \app\command\Task::class,
],
3、完善指令代码
setName('task')
->setDescription('the task command');
}
protected function execute(Input $input, Output $output)
{
$output->writeln('Execute start!'.Date('Y-m-d H:i:s'));
run(function () use($output) {
$time = time();
//筛选未过期且未完成的任务
$crontabList = Db::name('system_task')->where('status', '=', '1')->order('weigh DESC,id DESC')->select();
// 准备需要执行的crontab
$out_crontab_arr = [];
$max_crontab_arr = [];
$exe_crontab_arr = [];
foreach ($crontabList as $key => $crontab) {
if ($time < $crontab['begintime']) {
$output->writeln('任务未开始');
continue;
}
if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
//任务已超过最大执行次数
$output->writeln('任务已超过最大执行次数');
array_push($max_crontab_arr,$crontab);
} else {
if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
//任务已过期
$output->writeln('任务已过期');
array_push($out_crontab_arr,$crontab);
} else {
//重复执行
//如果未到执行时间则继续循环
$cron = CronExpression::factory($crontab['schedule']);
if (!$cron->isDue(date("YmdHi", $time)) || date("YmdHi", $time) === date("YmdHi", $crontab['executetime'])) {
$output->writeln('未到执行时间则继续循环');
continue;
}else{
array_push($exe_crontab_arr,$crontab);
}
}
}
}
if($out_crontab_arr){
$out_id_arr = array_column($out_crontab_arr,'id');
Db::name('system_task')->where('id','in',$out_id_arr)->update(['status'=>3]);
}
if($max_crontab_arr){
$max_id_arr = array_column($max_crontab_arr,'id');
Db::name('system_task')->where('id','in',$max_id_arr)->update(['status'=>2]);
}
if($exe_crontab_arr){
$chan = new Coroutine\Channel(count($exe_crontab_arr));
$port = Config::get('swoole.server.port');
foreach($exe_crontab_arr as $key => $crontab){
$log = [
'crontab_id' => $crontab['id'],
'content' => '',
'status' => '', // 暂时不知道
'create_datetime' => Date('Y-m-d H:i:s')
];
$crontab['log_id'] = Db::name("system_task_log")->insertGetId($log);
//
go(function () use ($chan,$crontab,$port) {
$curl = new Curl();
$curl->setOpt(CURLOPT_TIMEOUT,50);
$curl->get('http://0.0.0.0:'.$port.$crontab['content']);
// $res = $curl->response;
$curl->close();
if(is_string($curl->response)){
$chan->push([$crontab['log_id'] => ['msg'=>'执行完毕','data'=>$curl->response,'update_time'=>time()]]);
}else{
$chan->push([$crontab['log_id'] => ['msg'=>'执行超时或异常','data'=>$curl->response,'update_time'=>time()]]);
}
});
}
$result = [];
$log_res_arr = [];
for ($i = 0; $i < count($exe_crontab_arr); $i++)
{
$result += $chan->pop();
}
foreach($result as $log_id => $res_data){
$update_datetime = Date('Y-m-d H:i:s',$res_data['update_time']);
if($res_data['msg'] == '执行完毕'){
$res = json_decode($res_data['data'],true);
if(isset($res['code'] ) && $res['code'] == 200){
array_push($log_res_arr,['id'=>$log_id,'status'=>'success','content'=>'成功','update_datetime'=>$update_datetime]);
}else{
$fail_msg = isset($res['msg']) ? $res['msg'] : '无msg返回,未知错误';
array_push($log_res_arr,['id'=>$log_id,'status'=>'failure','content'=>$fail_msg,'update_datetime'=>$update_datetime]);
}
}else{
array_push($log_res_arr,['id'=>$log_id,'status'=>'failure','content'=>$res_data['msg'],'update_datetime'=>$update_datetime]);
}
}
$model = new SystemTaskLog();
$model->saveAll($log_res_arr);
}
});
// 指令输出
$output->writeln('Execute completed!'.Date('Y-m-d H:i:s'));
}
}
crontab -e
* * * * * /www/server/php/73/bin/php /www/wwwroot/abc.com/think task >> /www/server/cron/abc.com.log 2>&1
因为我们用的 cron-expression 是分钟级的,如果执行到秒级,其实是做不到的。
目前是任务里做一分钟的分割,以下做了简单粗暴的处理,当然如果项目里面有很多秒级任务,建议自己优化,或者其他方案
public function time20s()
{
$this->doOnce();
//在指定的时间后执行函数,20秒后执行
\Swoole\Timer::after(20000, function() use ($url) {
$this->doOnce();
\Swoole\Timer::after(20000, function() use ($url) {
$this->doOnce();
});
});
}
1、设置50秒超时,防止任务执行过长,所有任务的执行结果都得不到