今天有个同事问我,昨天在后台建了一个newsletter的队列为什么到今天队列状态还是为未发送。询问了下cronjob的运行情况,发现 magento的cronjob是正常运行的,而邮件配置也是对的,其他邮件都能正常发送。百思不得其解,于是只好拿出杀手锏,翻源码追踪整个发送过程。一追踪下来马上发现了这根本就不是个问题,问题在我们对newsletter的发送不熟悉,对什么时候修改这个状态理解错误。下面让我们系统的学习下 magento的newsletter。
newsletter的订阅
magento的前台页面一般都会有一个小的表单用来输入邮箱号码,客户提交就可以订阅相关产品信息了。网站后台建立邮件队列,就可以批量的对订阅者发送邮件来进行营销。后台还可以开启邮件确认的功能,开启邮件确认功能后,用户提交订阅newsletter的表单时先把订阅状态设置为2,即未确认状态,然后生成一个随机字符串保存并发送确认邮件,邮件里面的链接带有生成的随机字符串。用户点链接确认的时候就把请求传入的字符串和保存在数据库的字符串对比,如果一致就把状态改成1,即订阅状态。newsletter订阅相关数据保存到newsletter_subscriber这个表。
newsletter邮件模板的创建
后台newsletter => newsletter templates =>add new template就可以创建newsletter的邮件模板了,和其他邮件模板差不多的写法,区别是这只有一个变量就是subscriber对象,而且不能像订单之类的邮件模板一样可以选文件模板来修改,不过还好有些默认的代码。照着改改就好了。这个邮件模板的数据保存到数据库表 newsletter_template中。
newsletter队列的创建
当你创建好newsletter模板的时候会回到newsletter模板列表页面,每行模板右侧都有一个action列,下拉选择Queue Newsletter就会跳到创建页面。这个页面和newsletter模板的表单差不多,就多了一个队列开始执行的时间以及选择对应店铺的地方。填好点保存就创建了一个newsletter队列。那么创建一个队列的时候都干了些什么呢?首先,肯定是把我们填写的信息都保存到数据库中,这里是保存到 newsletter_queue这个表中。然后,它还做了一个事情就是保存队列和店铺的关系,队列和店铺关联的信息保存在表 newsletter_queue_store_link。我们看看保存queue店铺关系相关的代码。
app/code/core/Mage/Adminhtml/controllers/Newsletter/QueueController.php文件saveAction 方法中
$queue->setStores($this->getRequest()->getParam('stores', array()))//这里保存了店铺信息
->setNewsletterSubject($this->getRequest()->getParam('subject'))
->setNewsletterSenderName($this->getRequest()->getParam('sender_name'))
->setNewsletterSenderEmail($this->getRequest()->getParam('sender_email'))
->setNewsletterText($this->getRequest()->getParam('text'))
->setNewsletterStyles($this->getRequest()->getParam('styles'));
//app/code/core/Mage/Newsletter/Model/Queue.php
public function setStores(array $storesIds)
{
$this->setSaveStoresFlag(true);//设置保存的标志
$this->_stores = $storesIds;
return $this;
}
//app/code/core/Mage/Newsletter/Model/Resource/Queue.php
//queue model保存的之后会执行该方法
protected function _afterSave(Mage_Core_Model_Abstract $queue)
{
if ($queue->getSaveStoresFlag()) {
$this->setStores($queue);
}
return $this;
}
public function setStores(Mage_Newsletter_Model_Queue $queue)
{
//先把对应的queue store关系在表里删除掉
$adapter = $this->_getWriteAdapter();
$adapter->delete(
$this->getTable('newsletter/queue_store_link'),
array('queue_id = ?' => $queue->getId())
);
$stores = $queue->getStores();
if (!is_array($stores)) {
$stores = array();
}
foreach ($stores as $storeId) {
$data = array();
$data['store_id'] = $storeId;
$data['queue_id'] = $queue->getId();
//插入queue和store关系到表newsletter_queue_store_link中
$adapter->insert($this->getTable('newsletter/queue_store_link'), $data);
}
$this->removeSubscribersFromQueue($queue);
if (count($stores) == 0) {
return $this;
}
//取所有订阅者的信息
$subscribers = Mage::getResourceSingleton('newsletter/subscriber_collection')
->addFieldToFilter('store_id', array('in'=>$stores))
->useOnlySubscribed()
->load();
$subscriberIds = array();
foreach ($subscribers as $subscriber) {
$subscriberIds[] = $subscriber->getId();
}
if (count($subscriberIds) > 0) {
//把队列和订阅者的关系插入到newsletter_queue_link表中
$this->addSubscribersToQueue($queue, $subscriberIds);
}
return $this;
}
newsletter邮件的发送
队列创建后并不会马上就会发送邮件。newsletter的邮件发送是通过magento的cronjob机制来执行的。模块配置里面配置好执行周期以及对应的执行方法,并服务器里面定时执行网站跟目录的cron.php或者cron.sh邮件才会发送。配置如下:
<!-- newsletter 模块配置文件config.xml -->
<crontab>
<jobs>
<newsletter_send_all>
<schedule>
<cron_expr>*/5 * * * *</cron_expr>
</schedule>
<run>
<model>newsletter/observer::scheduledSend</model>
</run>
</newsletter_send_all>
</jobs>
</crontab>
*/5 * * * *这个和linux下的crontab一样,表示每5分钟执行一次。这里是执行Mage::getModel(‘newsletter/observer’)->scheduledSend();我们看看这个方法:
//app/code/core/Mage/Newsletter/Model/Observer.php
public function scheduledSend($schedule)
{
$countOfQueue = 3;//一次只处理三个队列
$countOfSubscritions = 20;//一个队列只发其中的20封邮件
$collection = Mage::getModel('newsletter/queue')->getCollection()
->setPageSize($countOfQueue)
->setCurPage(1)
->addOnlyForSendingFilter()//只对队列里面没发过邮件的订阅者发邮件,判断是否发过是通过表newsletter_queue_link表中letter_sent_at这个字段是否为空来判断的
->load();
//对每一个队列model执行sendPerSubcritber方法
$collection->walk('sendPerSubscriber', array($countOfSubscritions));
}
发邮件的代码和magento其他模块发邮件没啥区别,就是取出我们设置好的邮件模板替换变量然后调用邮件发送类发送。
但是这里有个需要注意的地方,那就是 一次只是发20封邮件,当队列对应的订阅者都发完了之后才会修改队列状态为已发送。
//app/code/core/Mage/Newsletter/Model/Queue.php
public function sendPerSubscriber($count=20, array $additionalVariables=array())
{
// .....
if ($this->getSubscribersCollection()->getSize() == 0) {
$this->_finishQueue();
return $this;
}
//.....
//发送完毕,修改成已经发送状态
if(count($collection->getItems()) < $count-1 || count($collection->getItems()) == 0) {
$this->_finishQueue();
}
}
protected function _finishQueue()
{
$this->setQueueFinishAt(Mage::getSingleton('core/date')->gmtDate());
$this->setQueueStatus(self::STATUS_SENT);
$this->save();
return $this;
}
现在回到今天同事问的那个问题。队列是昨天创建的,而我们的订阅数有很多,假设就一万个吧,newsletter每五分钟才发20封邮件,要发完一万封就需要 2500分钟,差不多42个小时。邮件还没发完嘛,状态当然不会变成已经发送了。