Autotask.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. <?php
  2. namespace addons\crontab\controller;
  3. use addons\crontab\model\Crontab;
  4. use Cron\CronExpression;
  5. use fast\Http;
  6. use think\Controller;
  7. use think\Db;
  8. use think\Exception;
  9. use think\Log;
  10. /**
  11. * 定时任务接口
  12. *
  13. * 以Crontab方式每分钟定时执行,且只可以Cli方式运行
  14. * @internal
  15. */
  16. class Autotask extends Controller
  17. {
  18. /**
  19. * 初始化方法,最前且始终执行
  20. */
  21. public function _initialize()
  22. {
  23. // 只可以以cli方式执行
  24. if (!$this->request->isCli()) {
  25. $this->error('Autotask script only work at client!');
  26. }
  27. parent::_initialize();
  28. // 清除错误
  29. error_reporting(0);
  30. // 设置永不超时
  31. set_time_limit(0);
  32. }
  33. /**
  34. * 执行定时任务
  35. */
  36. public function index()
  37. {
  38. $time = time();
  39. $logDir = LOG_PATH . 'crontab' . DS;
  40. if (!is_dir($logDir)) {
  41. mkdir($logDir, 0755);
  42. }
  43. //筛选未过期且未完成的任务
  44. $crontabList = Crontab::where('status', '=', 'normal')->order('weigh DESC,id DESC')->select();
  45. $execTime = time();
  46. foreach ($crontabList as $crontab) {
  47. $update = [];
  48. $execute = false;
  49. if ($time < $crontab['begintime']) {
  50. //任务未开始
  51. continue;
  52. }
  53. if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
  54. //任务已超过最大执行次数
  55. $update['status'] = 'completed';
  56. } else {
  57. if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
  58. //任务已过期
  59. $update['status'] = 'expired';
  60. } else {
  61. //重复执行
  62. //如果未到执行时间则继续循环
  63. $cron = CronExpression::factory($crontab['schedule']);
  64. if (!$cron->isDue(date("YmdHi", $execTime)) || date("YmdHi", $execTime) === date("YmdHi", $crontab['executetime'])) {
  65. continue;
  66. }
  67. $execute = true;
  68. }
  69. }
  70. // 如果允许执行
  71. if ($execute) {
  72. $update['executetime'] = $time;
  73. $update['executes'] = $crontab['executes'] + 1;
  74. $update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
  75. }
  76. // 如果需要更新状态
  77. if (!$update) {
  78. continue;
  79. }
  80. // 更新状态
  81. $crontab->save($update);
  82. // 将执行放在后面是为了避免超时导致多次执行
  83. if (!$execute) {
  84. continue;
  85. }
  86. $result = false;
  87. $message = '';
  88. try {
  89. if ($crontab['type'] == 'url') {
  90. if (substr($crontab['content'], 0, 1) == "/") {
  91. // 本地项目URL
  92. $message = shell_exec('php ' . ROOT_PATH . 'public/index.php ' . $crontab['content']);
  93. $result = $message ? true : false;
  94. } else {
  95. $arr = explode(" ", $crontab['content']);
  96. $url = $arr[0];
  97. $params = isset($arr[1]) ? $arr[1] : '';
  98. $method = isset($arr[2]) ? $arr[2] : 'POST';
  99. try {
  100. // 远程异步调用URL
  101. $ret = Http::sendRequest($url, $params, $method);
  102. $result = $ret['ret'];
  103. $message = $ret['msg'];
  104. } catch (\Exception $e) {
  105. $message = $e->getMessage();
  106. }
  107. }
  108. } elseif ($crontab['type'] == 'sql') {
  109. $ret = $this->sql($crontab['content']);
  110. $result = $ret['ret'];
  111. $message = $ret['msg'];
  112. } elseif ($crontab['type'] == 'shell') {
  113. // 执行Shell
  114. $message = shell_exec($crontab['content']);
  115. $result = $message ? true : false;
  116. }
  117. } catch (\Exception $e) {
  118. $message = $e->getMessage();
  119. }
  120. $log = [
  121. 'crontab_id' => $crontab['id'],
  122. 'executetime' => $time,
  123. 'completetime' => time(),
  124. 'content' => $message,
  125. 'status' => $result ? 'success' : 'failure',
  126. ];
  127. Db::name("crontab_log")->insert($log);
  128. }
  129. return "Execute completed!\n";
  130. }
  131. /**
  132. * 执行SQL语句
  133. */
  134. protected function sql($sql)
  135. {
  136. //这里需要强制重连数据库,使用已有的连接会报2014错误
  137. $connect = Db::connect([], true);
  138. $connect->execute("select 1");
  139. // 执行SQL
  140. $sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sql);
  141. $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
  142. $result = false;
  143. $message = '';
  144. $connect->startTrans();
  145. try {
  146. foreach ($sqls as $key => $val) {
  147. if (trim($val) == '' || substr($val, 0, 2) == '--' || substr($val, 0, 2) == '/*') {
  148. continue;
  149. }
  150. $message .= "\nSQL:{$val}\n";
  151. $val = rtrim($val, ';');
  152. if (preg_match("/^(select|explain)(.*)/i ", $val)) {
  153. $count = $connect->execute($val);
  154. if ($count > 0) {
  155. $resultlist = Db::query($val);
  156. } else {
  157. $resultlist = [];
  158. }
  159. $message .= "Total:{$count}\n";
  160. $j = 1;
  161. foreach ($resultlist as $m => $n) {
  162. $message .= "\n";
  163. $message .= "Row:{$j}\n";
  164. foreach ($n as $k => $v) {
  165. $message .= "{$k}:{$v}\n";
  166. }
  167. $j++;
  168. }
  169. } else {
  170. $count = $connect->getPdo()->exec($val);
  171. $message = "Affected rows:{$count}";
  172. }
  173. }
  174. $connect->commit();
  175. $result = true;
  176. } catch (\PDOException $e) {
  177. $message = $e->getMessage();
  178. $connect->rollback();
  179. $result = false;
  180. }
  181. return ['ret' => $result, 'msg' => $message];
  182. }
  183. }