小滕的博客

小滕的技术点滴

ThinkPHP3.2的定时任务的使用与改进(CronRunBehavior.CLASS)

1 year ago · 2 MIN READ
#PHP 

下面是 ThinkPHP3.2 实现定时任务的代码(ThinkPHP/Library/Behavior/CronRunBehavior.class.php):

<?php
namespace Behavior;

class CronRunBehavior {

    public function run(&$params) {
        // 锁定自动执行
        $lockfile    =   RUNTIME_PATH.'cron.lock';
        if(is_writable($lockfile) && filemtime($lockfile) > $_SERVER['REQUEST_TIME'] - C('CRON_MAX_TIME',null,60)) {
            return ;
        } else {
            touch($lockfile);
        }
        set_time_limit(1000);
        ignore_user_abort(true);

        // 载入cron配置文件
        // 格式 return array(
        // 'cronname'=>array('filename',intervals,nextruntime),...
        // );
        if(is_file(RUNTIME_PATH.'~crons.php')) {
            $crons  =   include RUNTIME_PATH.'~crons.php';
        }elseif(is_file(COMMON_PATH.'Conf/crons.php')){
            $crons  =   include COMMON_PATH.'Conf/crons.php';
        }
        if(isset($crons) && is_array($crons)) {
            $update  =   false;
            $log    =   array();
            foreach ($crons as $key=>$cron){
                if(empty($cron[2]) || $_SERVER['REQUEST_TIME']>=$cron[2]) {
                    // 到达时间 执行cron文件
                    G('cronStart');
                    include COMMON_PATH.'Cron/'.$cron[0].'.php';
                    G('cronEnd');
                    $_useTime    =   G('cronStart','cronEnd', 6);
                    // 更新cron记录
                    $cron[2]    =   $_SERVER['REQUEST_TIME']+$cron[1];
                    $crons[$key]    =   $cron;
                    $log[] = "Cron:$key Runat ".date('Y-m-d H:i:s')." Use $_useTime s\n";
                    $update  =   true;
                }
            }
            if($update) {
                // 记录Cron执行日志
                \Think\Log::write(implode('',$log));
                // 更新cron文件
                $content  = "<?php\nreturn ".var_export($crons,true).";\n?>";
                file_put_contents(RUNTIME_PATH.'~crons.php',$content);
            }
        }
        // 解除锁定
        unlink($lockfile);
        return ;
    }
}

其主要实现原理是通过:访问触发 + 文件锁。其主要流程是:
信号量(文件锁)获取 =》 开始读取配置文件或分析已经存在生成的 ~cron.php 文件(格式下面给出)=》 执行当前满足条件任务 =》 记录执行日志(执行消耗时间) =》 重新生成 cron 文件 => 释放信号量 =》 结束。

~cron.php 文件格式

<?php
retrun array(
    'taskName' => array(
        '文件名', '周期,second', '下次执行时间,时间戳'
    ),
);

使用需要配置如下:

第一、在 Application/Common/Conf 目录下添加 crons.php ,内容如下:

return array(
    // 'taskName' => array(
    //     '文件名', '周期,second', '下次执行时间,时间戳'
    // ),
    'task1' => array(
        'task1', 5
    ),
);

第二、在 Application/Common/Cron (任务文件都存在在这个目录) 目录下创建需要执行的定时任务文件 task1.php

<?php
echo 'Task runing.Now:'.date('Y-m-d H:i:s');

第三、挂上钩子。在 Application/Common/Conf 目录下面创建 tags.php

return array(
    'app_begin' => array(
        'Behavior\CronRunBehavior',
    ),
);

OK,到这里配置完毕,可以访问首页看看:

Task runing.Now:2017-06-27 14:18:56

ThinkPHP3.2 提供了一个非常简单的定时任务实现方式。但是这个定时任务只能执行PHP文件,而这个单文件和框架整合在一起使用非常繁琐,在实际项目中应用效果不大。所以,这里我做了部分优化,弃用 include ,引入 OOP 直接与框架结合一起使用。

优点:

  • 完美融合框架,可以使用框架所有功能。
  • 自定义存放目录,无限制,全局 Autoload
  • 扩展更加方便。

主要变动内容如下:

  • 第一,修改了配置文件的格式,具体如下:
return array(
    'demo1' => array(
        'class'  => '类的完整路径',
        'method' => '需要调用的方法,public',
        'cycle'  => 5, //周期,单位:秒
    ),
);
  • 第二、重写 CronRunBehavior

这里,我在 Application/Common/Behavior 下面创建了 CronRunBehavior.class.php 文件,代码如下:

<?php namespace Common\Behavior;

/**
 * 根据Thinkphp自带的CronRunBehavior修改
 * 主要修改内容:TP3.2自带的Cron默认只能加载php文件,但是
 * 这就导致无法使用TP框架本身的给你,所以这里改成 Autoload
 * 加载class,调用class的method执行。
 * @author 小滕
 * @date 2017/6/27
 */

use Think\Log;

class CronRunBehavior
{

    public function run(&$params)
    {
        // 锁定自动执行
        $lockfile    =   RUNTIME_PATH.'cron.lock';
        if(is_writable($lockfile) && filemtime($lockfile) > $_SERVER['REQUEST_TIME'] - C('CRON_MAX_TIME',null,60)) {
            return ;
        } else {
            touch($lockfile);
        }
        set_time_limit(1000);
        ignore_user_abort(true);

        // 载入cron配置文件
        // 格式 return array(
        // 'TaskName' => array(
        //     'class' => 'Home\Task\DemoTask',
        //     'method' => 'run',
        //     'cycle'      => 1, // 单位:秒
        // ),
        // );
        if(is_file(RUNTIME_PATH.'~crons.php')) {
            $crons  =   include RUNTIME_PATH.'~crons.php';
        }elseif(is_file(COMMON_PATH.'Conf/crons.php')){
            $crons  =   include COMMON_PATH.'Conf/crons.php';
        }
        if(isset($crons) && is_array($crons)) {
            $update  =   false;
            $log    =   array();
            foreach ($crons as $key=>$cron){
                if(empty($cron['next_runtime']) || $_SERVER['REQUEST_TIME']>=$cron['next_runtime']) {
                    // 到达时间 执行cron文件
                    G('cronStart');

                    /**  检测类是否存在 */
                    if (! class_exists($cron['class'])) {
                        $this->log('TaskName:'.$key.',ErrMsg:The class not exists.', $cron);
                        continue;
                    }
                    /** 检测方法是否存在 */
                    $object = new $cron['class'];
                    if (! method_exists($object, $cron['method'])) {
                        $this->log('TaskName:'.$key.',ErrMsg:The method not exists.', $cron);
                        continue;
                    }
                    /** 开始运行 */
                    try {
                        $object->$cron['method']();
                    } catch (\Exception $e) {
                        $this->log('TaskName:'.$key.',ErrMsg:' . $e->getMessage() . '.', $cron);
                    }

                    G('cronEnd');
                    $_useTime    =   G('cronStart','cronEnd', 6);
                    // 更新cron记录
                    $cron['next_runtime']   =   $_SERVER['REQUEST_TIME']+$cron['cycle'];
                    $crons[$key]    =   $cron;
                    $log[] = "Cron:$key Runat ".date('Y-m-d H:i:s')." Use $_useTime s\n";
                    $update  =   true;
                }
            }
            if($update) {
                // 记录Cron执行日志
                Log::write(implode('',$log));
                // 更新cron文件
                $content  = "<?php\nreturn ".var_export($crons,true).";\n?>";
                file_put_contents(RUNTIME_PATH.'~crons.php',$content);
            }
        }
        // 解除锁定
        unlink($lockfile);
        return ;
    }

    /**
     * 错误日志记录
     * @param string $message 错误信息
     * @return void
     */
    protected function log($message, $data = [])
    {
        Log::write('CronTask Error.' . $message.'Data:' . json_encode($data));
    }

}

挂上钩子

<?php
return array(
    'app_begin' => array(
        'Common\Behavior\CronRunBehavior',
    ),
);

任务文件Demo

存放路径:Application/Common/Tasks/TaskDemo.class.php:

<?php namespace Common\Tasks;

class TaskDemo 
{

    public function run()
    {
        echo __CLASS__.' runing.now:' . date('Y-m-d H:i:s');
    }

}

执行输出:

Common\Tasks\TaskDemo runing.now:2017-06-27 14:40:10

分析结束.That's All.^ - ^.

···

xiao teng



备案号:皖ICP备14012032号-5