Yii框架设计计划任务脚本+linux下crontab执行
基于Yii框架设计计划任务脚本
首先,当您接触yii框架时,您会发现,它已经精心设计好了一套命令行应用程序,那就是yiic命令原有的一些命令,我们可以创建web应用/控制器/模型/模块等等。
我们可以按yiic的风格,写出基于yii框架的命令行应用,这里的命令行应用基本上是配合Web应用来做的,什么时候会用到呢,最明显的例子是,crontab的应用,
例如:我们设计一个发送邮件系统,由于用户,或发送的信件很多,通过web方式发送非常站用http服务器资源,我们准备通过数据表模拟发送队列,通过crontab定时执行这个命令,读取数据库表中的部分数据,发送邮件,这个命令只占用系统中的一个进程,不会影响http的响应。当然,大型网站,会有专门的job服务器。
费话不多说了,先搭建环境
1.当你通过yiic创建一个webapp应用后,会在webapp/protected/下生成yiic.php, 由于是命令行应用,所以这里的yiic.php其实就是与webapp下的index.php一样的,命令行入口文件。
2.打开yiic文件,添加一行设置,将commands目录的路径添加到yiic中,这样,yiic就能够找到commands目录下的命令文件了,修改后的代码如下,红色为新加入代码:
<?php // change the following paths if necessary $yiic=dirname(__FILE__).’/../../yii-read-only/framework/yiic.php"; $config=dirname(__FILE__).’/config/console.php"; @putenv(‘YII_CONSOLE_COMMANDS=’. dirname(__FILE__).’/commands’ ); require_once($yiic);3.我们在commands目录下创建一个文件,由于我们要做一个发邮件的命令,所以命名为MailCommand.php
代码如下:
<?php /** * Description of MailCommand * * @author syang */ class MailCommand extends CConsoleCommand { public $defaultAction=’cron"; //这是一个缺省action名称,默认是index,这里我修改成了cron /** * cron action * @param int $limit */ public function actionCron($limit=5) { if (!$this->checkMon(__METHOD__)) { $this->runError(‘The command is running. Please try again later.’); } if ($limit >= 100 or $limit <= 0) { $this->usageError(‘The limit parameter greater than 0 and less than 100′); } MailQueue::model()->send($limit); } public function usageError($message) { echo(“Error: $message ”.$this->getHelp().” ”); exit(1); } public function runError($message) { echo(“Error: $message ”); exit(1); } protected function checkMon($action=”, $process_num=1) { if ($action==”) $action = $this->defaultAction; $commandname = get_class($this); $commandname = str_replace(‘command’, ”, strtolower($commandname)); if (strpos($action, “::”)!== false) { $action = substr(strtolower(end(explode(“::”, $action))), strlen(‘action’)); } $cmd = “ps -ef | grep -v grep | grep yiic | grep ”{$commandname}” “; if ($action!=$this->defaultAction) { $cmd .= “| grep ”{$action}” “; } $cmd .= “| wc -l “; $current_process_num = shell_exec($cmd); if ( intval($current_process_num) > $process_num ) { return false; } else { return true; } } }
ok,让我先讲解一下MailCommand类,这个扩展了CConsoleCommand,主要方法actionCron,与我们在Web应用中的控制器写方类似,actionCron方法有一个参数叫limit,它有一个缺省值,
注意:
如果actionXXX方法有参数,且没有缺省值,则在调用命令时,则必须指定参数(否则会报用法错误)。
如果actionXXX方法没有参数,则在调用命令时,不能指定参数 (否则会报用法错误)。
如果actionXXX有参数且有缺省值,则调用命令时,可以指定也可以不指定参数
usageError方法是继承自CConsoleCommand,由于CConsoleCommand类中定义的usageError仅仅只是die掉了,shell下返回的值还是0。所以,这里我修改了一下。
注意:通过在shell下执行一条命令后,可以通过$?获得刚才那条命令的执行情况。通常它与普通的程序语言的真假正好相反,即,$?为0表示上一条命令执行成功,如果$?非0则表示上条命令存在错误,执行不成功。
runError方法与usageError类似,只是不显示用法信息。
checkMon方法是我自己写的,主要是通过shell命令(仅限类nix系统)获得当前命令的进程数,举个例子,我们创建的mail命令,是通过crontab定义跑的,如果命令执行时间超过了下一次定时启动,则可能会出现2个或2个以上的进程。通过checkMon,我们基本上可以限制,这条命令可以启动几个进程,默认只有一个进程。通过checkMon方法,我们判断了如果有一个进程存在,就退出。不再运行。
4.ok,下面我们创建一个表mail_queue,sql如下:
CREATE TABLE IF NOT EXISTS `mail_queue` (
`queue_id` int(11) unsigned NOT NULL auto_increment,
`mail_to` varchar(150) NOT NULL default ”,
`mail_encoding` varchar(50) NOT NULL default ”,
`mail_subject` varchar(255) NOT NULL default ”,
`mail_body` text NOT NULL,
`priority` tinyint(1) unsigned NOT NULL default ‘2’,
`err_num` tinyint(1) unsigned NOT NULL default ‘0’,
`add_time` int(11) NOT NULL default ‘0’,
`lock_expiry` int(11) NOT NULL default ‘0’,
PRIMARY KEY (`queue_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
注:为了演示,上面的表我直接使用了ecmall的邮件队列表,以下程序思路基本参考ecmall的程序
5.配置main/console.php,设置import路径,以及db连接,这部份与main.php类似。
并且在params部分添加了mailConfig的配置参数
‘params’=>array( // this is used in contact page ‘mailConfig’=>array( ‘sender’=> ‘YiiBook Test Email’, ‘from’=> ‘webmaster@yiibook.com’, ‘protocol’ => ‘0’, //1 is smtp ‘host’ => ”, ‘port’ => ”, ‘username’ => ”, ‘password’ => ”, ) ),6.通过yiic或gii创建mail_queue表的模型类MailQueue.php
7. 在 MailQueue.php中添加以下两个方法
/** * 清除发送N次错误和过期的邮件 */ function clear() { $criteria=new CDbCriteria(array( ‘condition’=>” err_num > 3 or add_time < :addtime”, ‘params’=>array( ‘addtime’=> time() – 259200 ) )); //这里使用了CDbCriteria创建查询条件,并通过指定占位符,进行变量绑定。 return $this->deleteAll($criteria); } /** * 发送邮件 */ function send($limit = 5) { /* 清除不能发送的邮件 */ $this->clear(); $time = time(); /* 取出所有未锁定的 */ $mails = $this->findAll(array( ‘condition’ => “lock_expiry < ?”, ‘order’ => ‘add_time DESC, priority DESC, err_num ASC’, ‘limit’ => $limit, ‘params’ => array($time) )); //这里直接使用数据代替CDbCriteria类作为条件参数,并使用?作为占位符, //条件中可以有多个?占位符,参数数组params中值的先后顺序会对应到条件中占位符的顺序。 if (!$mails) { /* 没有邮件,不需要发送 */ return 0; } /* 锁定待发送邮件 */ $queueIds = $this->getQueueIds($mails); $lock_expiry = $time + 30; //锁定30秒 $this->updateAll( array( “err_num”=>new CDbExpression(‘err_num + 1′), ‘lock_expiry’ => $lock_expiry ), “queue_id in ( {$queueIds} ) ” ); /* 获取邮件发送接口 */ $mailer = new Mailer(Yii::app()->params->mailConfig); $mail_count = count($mails); $error_count= 0; $error = ”; /* 逐条发送 */ for ($i = 0; $i < $mail_count; $i++) { $mail = $mails[$i]; $result = $mailer->send($mail->mail_to, $mail->mail_subject, $mail->mail_body, $mail->mail_encoding, 1); if ($result) { /* 发送成功,从队列中删除 */ $mail->delete(); } else { $error_count++; } } } protected function getQueueIds($mails) { $queueIds = array(); foreach($mails as $mail) { $queueIds[]=$mail->queue_id; } return implode(“,”, $queueIds); }
8.创建Mailer类
在components目录下,创建一个Mailer.php类,代码如下
<?php /** * Description of Mail * * @author syang */ //注意,这里我们要使用一个第三方类库phpmailer,所以我们先将class.phpmailer.php放到vendors下, //并通过import将路径加入到include_path中, Yii::import(‘application.vendors.*’); //引用class.phpmailer.php,由于第三方类库可能不符合yii的加载规则,把以需要手动加载。 require_once ‘class.phpmailer.php"; define(‘MAIL_PROTOCOL_LOCAL’, ‘1’); define(‘CHARSET’, ‘UTF-8′); class Mailer { var $timeout = 30; var $errors = array(); var $priority = 3; // 1 = High, 3 = Normal, 5 = low var $debug = false; var $mailer; function __construct($config) { @extract($config); $this->mailer = new phpmailer(); $this->mailer->From = $from; $this->mailer->FromName = $this->_base64_encode($sender); if ($protocol == MAIL_PROTOCOL_LOCAL) { /* mail */ $this->mailer->IsMail(); } else { /* smtp */ $this->mailer->IsSMTP(); $this->mailer->Host = $host; $this->mailer->Port = $port; $this->mailer->SMTPAuth = !empty($pass); $this->mailer->Username = $user; $this->mailer->Password = $pass; } } function send($mailto, $subject, $content, $charset, $is_html, $receipt = false) { $this->mailer->Priority = $this->priority; $this->mailer->CharSet = $charset; $this->mailer->IsHTML($is_html); $this->mailer->Subject = $this->_base64_encode($subject); $this->mailer->Body = $content; $this->mailer->Timeout = $this->timeout; $this->mailer->SMTPDebug = $this->debug; $this->mailer->ClearAddresses(); $this->mailer->AddAddress($mailto); $res = $this->mailer->Send(); if (!$res) { $this->errors[] = $this->mailer->ErrorInfo; } return $res; } function _base64_encode($str = ”) { return ‘=?’ . CHARSET . ‘?B?’ . base64_encode($str) . ‘?="; } }
9.ok,基本完成了,
在命令行下,如我们当前在protected目录下,执行
./yiic mail
或
./yiic mail cron
或
./yiic mail cron –limit=10
总结,本篇仅通过一个具体的例子,演示了yii框架的命令行应用程序开发,仅做抛砖引玉。
涉及了shell的一些应用常识,yii框架的模型操作以及第三方类库的调用。
- 上一篇: yii2.0文件如何在php命令行中运行
- 下一篇: curl的超时时间设置