yqsphp 发表于 2019-3-20 13:21

【PHP】基于thinkphp3.2的一个防阻塞定时类

本帖最后由 yqsphp 于 2019-3-20 13:30 编辑

很早之前自己写了一个php定时任务的类,不必用到windows或linux的定时计划任务,基于thinkphp3.2
我就不采用原生的PHP了,你们可以自己动手改成原生的,前端页面任务列表如何实现大家自己去做,我只提供后端的
原理:根据设置的最大执行时间默认30s,在程序执行到超过零界点时在重新请求_sock()这个方法,如此循环,永久执行(这里用到一个配置文件控制定时开关),已经跟浏览器无关了
问题:在非安全模式下即php.ini配置中:safe_mode= Off时设置 set_time_limit(0),测试n次,在window非安全模式下这个函数设置为0没什么用,在linux下可以。{:301_1008:}
因为反复调用自己,所有这个类中要不要set_time_limit(0)都无所谓了,但是睡眠时间不能超过最大执行时间
前端页面直接传值改变配置文件的值来控制定时开关,比如传 off = 1,以下代码中的定时任务的url做的是死的,前端定时任务列表可以直接传URL必须带host配置文件就是一个return 1或0源码如下:<?php
namespace Admin\Controller;
use Think\Controller;
/**
* 事件定时任务
* start(),stop()这两个方法用来测试
*/
class TimerController extends Controller{
    private static $url; //定时任务的url
    public function _initialize(){
      self::$url = 'http://'.$_SERVER['HTTP_HOST'].U('timer/app');
    }
    /**
   * 定时器任务,通过前端传值控制,
         * 启动后就跟浏览器无关了,除非服务重启
   * 增加定时配置文件timer.php
   */
    public function index(){
      if(IS_POST){
            $off = I('post.off');
            //启动定时
            if($off){
                file_put_contents(CONF_PATH.'timer.php', '<?php '.PHP_EOL.'return 1;');
                $this->_sock(self::$url);
            }else{
                file_put_contents(CONF_PATH.'timer.php', '<?php '.PHP_EOL.'return 0;');
            }
      }else{
                        $flag = include CONF_PATH.'timer.php';
                        $this->assign('status',$flag);
                        $this->display();
                }
    }
    /**
   * 启动定时器
   */
    public function start(){
      $this->_sock(self::$url);
    }
    /**
   * 停止定时器
   * 控制定时器这里采用修改文件真假值(timer.php)
   */
    public function stop(){
      $flag = require_once CONF_PATH.'timer.php';
      file_put_contents(CONF_PATH.'timer.php', '<?php '.PHP_EOL.'return 0;');
    }
    /**
   * 执行定时任务操作
   */
    private function app(){
      ignore_user_abort(true);//忽略浏览器关闭
      set_time_limit(0);//设置超时 在window非安全模式下这个函数设置为0没什么用,在linux下可以。-_-
      $flag = include CONF_PATH.'timer.php';
      if(!$flag) die('stop');
      /**
         * 这里是逻辑代码区,由自己实现
         */
      sleep(60); //睡眠时间,但不要超过最大执行时间,可以设置set_time_limit的值,建议不要设置0,200,300都行
      $this->_sock(self::$url);//循环执行
    }
   
    /**
         * 解析url,执行定时任务是防止阻塞
         *
         * @Param $url 执行任务的url
         * @Return array|boolean
         */
      protected function _sock($url){
            //获取url主机
                $host         = parse_url($url, PHP_URL_HOST);
                //获取url端口并判断是否80
                $port         = parse_url($url, PHP_URL_PORT) ?: 80;
                //获取URL协议http或https
                $scheme = parse_url($url, PHP_URL_SCHEME);
                //获取host之后的url
                $path         = parse_url($url, PHP_URL_PATH);
                //获取传递参数获取的是?后面的
                $query         = parse_url($url, PHP_URL_QUERY);
                //将获取的参数拼接到url后面
                if($query) $path .= '?' . $query;
                //如果是https请求就改ssl协议请求
                if($scheme == 'https'){
                        $host = 'ssl://' . $host;
                }
                //$a = ['host'=>$host,'port'=>$port,'$scheme'=>$scheme,'$path'=>$path,'$query'=>$query];
                //dump($a);
                //打开一个网络socket连接
                $fp = fsockopen($host, $port, $error_code, $error_msg, 1);
                if(! $fp){
                        return [
                              'error_code' => $error_code,
                              'error_msg'=> $error_msg
                        ];
                }else{
                  // 开启了手册上说的非阻塞模式
                        stream_set_blocking($fp, 0);
                        // 设置超时
                        stream_set_timeout($fp, 1);
                        $header= "GET $path HTTP/1.1\r\n";
                        $header .= "Host: $host\r\n";
                        $header .= "Connection: close\r\n\r\n"; // 长连接关闭
                        fwrite($fp, $header);
                        usleep(1000); // 如果没有这延时,可能在nginx服务器上就无法执行成功
                        fclose($fp);
                        return true;
                }
      }
   
}

下面的方法是自己现在用的仅供学习参考
/**
       * 任务提醒
       */
        private function app(){
                //每日早上8点清空S
                if(date('H') == 'O8') S('send_data',null);
                $old_data = S('send_data');
                //file_put_contents('data', $old_data.PHP_EOL,FILE_APPEND);
                $child        = M('userContact')->where('ccnexttime <> "" and isgood = 1 and ccremind <> 0 and ccissend = 0')->order('ccctime desc')->buildSql();
                $cont   = M('userContact')->field('a.*,u.mail')->table($child.' a')->join('left join '.C('DB_PREFIX').'admin u on u.truename = a.ccuser')->group('a.userid')->order('a.ccctime desc')->select();
                //事先存储查询的数据,当再次循环查询是比较存储数据与查询数据是否一样
                $new_data = md5(serialize($cont));
                S('send_data',$new_data,1800);
                if($old_data != $new_data){
                        foreach($cont as $val){
                                if((strtotime($val['ccnexttime']) - time()) <= $val['ccbefore']*60){
                                        switch ($val['ccremind']){
                                                case 1:
                                                        $data['wechat'][] = $val['ccuser']."你有待回访客户,请登录系统查看\n";
                                                        break;
                                                case 2:
                                                        $data['email'][$val['mail']][] = $val['ccuser'].'你有待回访客户,请登录系统查看<br/>';
                                                        break;
                                                case 3:
                                                        $data['mess'][] = $val['ccuser']."你有待回访客户,请登录系统查看\n";
                                                        break;
                                        }
                                }
                        }
               
                        //邮箱提醒
                        if(!empty($data['email'])){
                                foreach($data['email'] as $key=>$v){
                                        send_mail( $key, 'abcdef@qq.com', '任务提醒', implode(',', $v));
                                }
                        }
                        //微信提醒
                        if(!empty($data['wechat'])){
                                $chat = Wechat::app(1);
                                $data = [
                                        'touser'=>'关注微信号的原始id' ,//ocanwvquBsmKIabcvElB1KJpcNAEg-QM
                                        'msgtype'=>'text',
                                        'text'=>['content'=>implode(',', $data['wechat'])]
                                ];
                                //$chat->sendMassMessage($data);
                                $chat->sendCustomMessage($data);
                                vendor('Wechat.error');
                                file_put_contents('wx.txt', \Error::getError($chat->errCode).PHP_EOL,FILE_APPEND);
                        }
                        //短信提醒
                        if(!empty($data['mess'])){
                                $sms = new Sms(C('sms_require'), C('sms_port'), C('sms_version'));
                                $datas = $sms->sendTemplateSMS('你的电话号码', $data['mess'], 1);
                        }
                }
        }

limit81995 发表于 2019-8-2 15:13

终于找到了这种资源,收藏了
页: [1]
查看完整版本: 【PHP】基于thinkphp3.2的一个防阻塞定时类