You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:33:19