审查视图

addons/crontab/controller/Autotask.php 6.6 KB
郭盛 authored
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
<?php

namespace addons\crontab\controller;

use addons\crontab\model\Crontab;
use Cron\CronExpression;
use fast\Http;
use think\Controller;
use think\Db;
use think\Exception;
use think\Log;

/**
 * 定时任务接口
 *
 * 以Crontab方式每分钟定时执行,且只可以Cli方式运行
 * @internal
 */
class Autotask extends Controller
{

    /**
     * 初始化方法,最前且始终执行
     */
    public function _initialize()
    {
        // 只可以以cli方式执行
        if (!$this->request->isCli()) {
            $this->error('Autotask script only work at client!');
        }

        parent::_initialize();

        // 清除错误
        error_reporting(0);

        // 设置永不超时
        set_time_limit(0);
    }

    /**
     * 执行定时任务
     */
    public function index()
    {
        $time = time();
        $logDir = LOG_PATH . 'crontab/';
        if (!is_dir($logDir)) {
            mkdir($logDir, 0755);
        }
        //筛选未过期且未完成的任务
        $crontabList = Crontab::where('status', '=', 'normal')->order('weigh desc,id desc')->select();
        $execTime = time();
        foreach ($crontabList as $crontab) {
            $update = [];
            $execute = false;
            if ($time < $crontab['begintime']) {
                //任务未开始
                continue;
            }
            if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
                //任务已超过最大执行次数
                $update['status'] = 'completed';
            } else {
                if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
                    //任务已过期
                    $update['status'] = 'expired';
                } else {
                    //重复执行
                    //如果未到执行时间则继续循环
                    $cron = CronExpression::factory($crontab['schedule']);
                    if (!$cron->isDue(date("YmdHi", $execTime)) || date("YmdHi", $execTime) === date("YmdHi", $crontab['executetime'])) {
                        continue;
                    }
                    $execute = true;
                }
            }

            // 如果允许执行
            if ($execute) {
                $update['executetime'] = $time;
                $update['executes'] = $crontab['executes'] + 1;
                $update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
            }

            // 如果需要更新状态
            if (!$update) {
                continue;
            }
            // 更新状态
            $crontab->save($update);

            // 将执行放在后面是为了避免超时导致多次执行
            if (!$execute) {
                continue;
            }
            $result = false;
            $message = '';

            try {
                if ($crontab['type'] == 'url') {
                    if (substr($crontab['content'], 0, 1) == "/") {
                        // 本地项目URL
                        $message = shell_exec('php ' . ROOT_PATH . 'public/index.php ' . $crontab['content']);
                        $result = $message ? true : false;
                    } else {
                        $arr = explode(" ", $crontab['content']);
                        $url = $arr[0];
                        $params = isset($arr[1]) ? $arr[1] : '';
                        $method = isset($arr[2]) ? $arr[2] : 'POST';
                        try {
                            // 远程异步调用URL
                            $ret = Http::sendRequest($url, $params, $method);
                            $result = $ret['ret'];
                            $message = $ret['msg'];
                        } catch (\Exception $e) {
                            $message = $e->getMessage();
                        }
                    }

                } elseif ($crontab['type'] == 'sql') {
                    $ret = $this->sql($crontab['content']);
                    $result = $ret['ret'];
                    $message = $ret['msg'];
                } elseif ($crontab['type'] == 'shell') {
                    // 执行Shell
                    $message = shell_exec($crontab['content']);
                    $result = $message ? true : false;
                }
            } catch (\Exception $e) {
                $message = $e->getMessage();
            }
            $log = [
                'crontab_id'   => $crontab['id'],
                'executetime'  => $time,
                'completetime' => time(),
                'content'      => $message,
                'status'       => $result ? 'success' : 'failure',
            ];
            Db::name("crontab_log")->insert($log);
        }
        return "Execute completed!\n";
    }

    /**
     * 执行SQL语句
     */
    protected function sql($sql)
    {
        //这里需要强制重连数据库,使用已有的连接会报2014错误
        $connect = Db::connect([], true);
        $connect->execute("select 1");

        // 执行SQL
        $sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sql);
        $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);

        $result = false;
        $message = '';
        $connect->startTrans();
        try {
            foreach ($sqls as $key => $val) {
                if (trim($val) == '' || substr($val, 0, 2) == '--' || substr($val, 0, 2) == '/*') {
                    continue;
                }
                $message .= "\nSQL:{$val}\n";
                $val = rtrim($val, ';');
                if (preg_match("/^(select|explain)(.*)/i ", $val)) {
                    $count = $connect->execute($val);
                    if ($count > 0) {
                        $resultlist = Db::query($val);
                    } else {
                        $resultlist = [];
                    }

                    $message .= "Total:{$count}\n";
                    $j = 1;
                    foreach ($resultlist as $m => $n) {
                        $message .= "\n";
                        $message .= "Row:{$j}\n";
                        foreach ($n as $k => $v) {
                            $message .= "{$k}{$v}\n";
                        }
                        $j++;
                    }
                } else {
                    $count = $connect->getPdo()->exec($val);
                    $message = "Affected rows:{$count}";
                }
            }
            $connect->commit();
            $result = true;
        } catch (\PDOException $e) {
            $message = $e->getMessage();
            $connect->rollback();
            $result = false;
        }
        return ['ret' => $result, 'msg' => $message];

    }
}