Silverstripe 4.11 QueuedJobs分批次定时发邮件方案咨询
问题描述
我正在使用Silverstripe 4.11和silverstripe-queuedjobs模块,想要按固定时间间隔分批次发送邮件,每批次给4个收件人(共100个):先给A、B、C、D发,等指定时间后再给E、F、G、H发,以此类推。
我试过用sleep()函数设置2分钟的批次间隔,但这会导致整个网站无响应,队列里还显示Silverstripe的加载动画。
请问silverstripe-queuedjobs模块有没有内置功能支持按$chunk_size=4分批次并设置间隔?或者是不是需要结合Cron Job来实现?请给实现示例。
当前代码:
class SendEmailsJob extends AbstractQueuedJob implements QueuedJob { private static $chunk_size = 4; public function getTitle() { return 'Send Emails in Chunks'; } public function process() { $recipients = Recipient::get(); // 假设Recipient是你的数据对象 // 计算总批次数量 $totalChunks = ceil($recipients->count() / self::$chunk_size); for ($chunk = 0; $chunk < $totalChunks; $chunk++) { $start = $chunk * self::$chunk_size; $end = ($chunk + 1) * self::$chunk_size; $chunkRecipients = $recipients->limit(self::$chunk_size, $start); foreach ($chunkRecipients as $recipient) { // 给收件人发送邮件 $email = Email::create() ->setTo($recipient->Email) ->setSubject('你的邮件主题') ->setBody('你的邮件内容') ->send(); } // 休眠一段时间以遵守主机的邮件发送限制 sleep(120); // 2分钟 // 3600表示休眠1小时(按需调整) // 更新任务进度 $this->currentStep = $chunk + 1; $this->totalSteps = $totalChunks; $this->addMessage("已处理批次 {$this->currentStep}/{$this->totalSteps}"); } $this->isComplete = true; } }
解决方案
一、用QueuedJobs内置的延迟/重试机制(推荐)
QueuedJobs原生支持任务延迟执行,完全不需要用sleep()阻塞进程。核心逻辑是:每次只处理一个批次,处理完后创建新任务并设置延迟,让队列自动在指定时间后执行下一批次。
实现代码:
use SilverStripe\Core\Config\Config; use SilverStripe\QueuedJobs\Services\AbstractQueuedJob; use SilverStripe\QueuedJobs\Services\QueuedJob; use SilverStripe\Control\Email\Email; class SendEmailsChunkJob extends AbstractQueuedJob implements QueuedJob { // 批次大小,可通过配置文件覆盖 private static $chunk_size = 4; // 批次间隔(秒),这里设为2分钟=120秒 private static $delay_between_chunks = 120; // 当前处理的起始偏移量 protected $startOffset = 0; public function __construct($startOffset = 0) { $this->startOffset = $startOffset; } public function getTitle() { return "发送邮件批次(起始偏移量:{$this->startOffset})"; } public function process() { $chunkSize = Config::inst()->get(self::class, 'chunk_size'); $delay = Config::inst()->get(self::class, 'delay_between_chunks'); $recipients = Recipient::get(); $totalRecipients = $recipients->count(); // 处理当前批次的收件人 $chunkRecipients = $recipients->limit($chunkSize, $this->startOffset); foreach ($chunkRecipients as $recipient) { $email = Email::create() ->setTo($recipient->Email) ->setSubject('你的邮件主题') ->setBody('你的邮件内容') ->send(); $this->addMessage("已发送邮件给 {$recipient->Email}"); } // 计算下一批次的起始偏移量 $nextOffset = $this->startOffset + $chunkSize; // 如果还有未处理的收件人,创建延迟任务 if ($nextOffset < $totalRecipients) { $nextJob = new self($nextOffset); // 设置延迟执行时间(当前时间+延迟秒数) $nextJob->setStartTimestamp(time() + $delay); $this->queueJob($nextJob); $this->addMessage("已创建下一批次任务,将在{$delay}秒后执行"); } else { // 所有批次处理完成 $this->isComplete = true; $this->addMessage("所有邮件发送完成,共处理{$totalRecipients}个收件人"); } // 更新进度 $totalChunks = ceil($totalRecipients / $chunkSize); $currentChunk = ceil(($this->startOffset + 1) / $chunkSize); $this->currentStep = $currentChunk; $this->totalSteps = $totalChunks; } }
使用方式:
在后台或代码中启动第一个任务即可:
// 启动第一个批次(起始偏移量0) $job = new SendEmailsChunkJob(); QueuedJobService::singleton()->queueJob($job);
二、结合Cron Job实现
如果需要更精确的定时控制(比如固定每天某个时段执行批次),可以结合Cron和QueuedJobs:创建一个仅处理单个批次的任务,用Cron定时触发,同时给收件人添加标记避免重复发送。
实现代码:
use SilverStripe\Core\Config\Config; use SilverStripe\QueuedJobs\Services\AbstractQueuedJob; use SilverStripe\QueuedJobs\Services\QueuedJob; use SilverStripe\Control\Email\Email; class CronEmailChunkJob extends AbstractQueuedJob implements QueuedJob { private static $chunk_size = 4; public function getTitle() { return 'Cron触发的邮件批次发送'; } public function process() { $chunkSize = Config::inst()->get(self::class, 'chunk_size'); // 获取未发送邮件的收件人(假设Recipient有Sent字段标记是否已发送) $recipients = Recipient::get()->filter('Sent', false)->limit($chunkSize); if (!$recipients->count()) { $this->isComplete = true; $this->addMessage('没有待发送的收件人,任务结束'); return; } foreach ($recipients as $recipient) { $email = Email::create() ->setTo($recipient->Email) ->setSubject('你的邮件主题') ->setBody('你的邮件内容') ->send(); // 标记为已发送 $recipient->Sent = true; $recipient->write(); $this->addMessage("已发送邮件给 {$recipient->Email}"); } $this->isComplete = true; $this->addMessage("本次批次处理完成,共发送{$recipients->count()}封邮件"); } }
Cron配置示例:
添加到服务器Cron任务中,每2分钟执行一次队列任务:
*/2 * * * * php /path/to/your/silverstripe/project/vendor/bin/sake dev/tasks/QueuedJobRunnerTask run
注:确保
QueuedJobRunnerTask能正常执行,也可以自定义Task直接触发CronEmailChunkJob。
为什么不要用sleep()?
sleep()会阻塞当前队列进程,导致队列无法处理其他任务,甚至拖慢网站响应- QueuedJobs的进程是长期运行的,阻塞会破坏队列的调度逻辑
- 内置的延迟任务机制是异步的,完全不会影响其他任务执行
内容的提问来源于stack exchange,提问作者seppzzz
相关产品推荐
相关产品推荐

