ThinkPHP 8 队列系统封装

// app/Command/QueueWorker.php 队列工作器
<?php
namespace app\Command;

use app\Service\QueueService;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Log;

class QueueWorker extends Command
{
    protected function configure()
    {
        $this
            ->setName('queue:work')
            ->setDescription('Process the queue jobs')
            ->addOption('queue', null, Option::VALUE_REQUIRED, 'The queue to listen on', 'default')
            ->addOption('daemon', null, Option::VALUE_NONE, 'Run the worker in daemon mode')
            ->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0)
            ->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128)
            ->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3)
            ->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 3);

    }

    protected function execute(Input $input, Output $output)
    {
        $queueName = $input->getOption('queue');
        $isDaemon = $input->getOption('daemon');

        $output->writeln("队列工作进程已启动,监听队列: {$queueName}");
        Log::info("队列工作进程已启动,监听队列: {$queueName}");

        $queueService = new QueueService($queueName);

        if ($isDaemon) {
            $this->runAsDaemon($queueService, $output);
        } else {
            $this->runOnce($queueService, $output);
        }
    }

    protected function runOnce(QueueService $queueService, Output $output)
    {
        $jobData = $queueService->pop();

        if ($jobData) {
            $output->writeln("处理任务: " . json_encode($jobData['job']));
            $this->processJob($jobData);
        } else {
            $output->writeln("队列为空");
        }
    }

    protected function runAsDaemon(QueueService $queueService, Output $output)
    {
        while (true) {
            try {
                $jobData = $queueService->pop();

                if ($jobData) {
                    $output->writeln("处理任务: " . json_encode($jobData['job']));
                    $this->processJob($jobData);
                } else {
                    // 队列为空,休眠一段时间避免CPU占用过高
                    sleep(1);
                }
            } catch (\Exception $e) {
                $output->writeln("处理任务时发生错误: " . $e->getMessage());
                Log::error("队列处理错误: " . $e->getMessage());
                // 发生错误时适当休眠
                sleep(5);
            }
        }
    }

    protected function processJob($jobData)
    {
        try {
            $jobClass = $jobData['job']['class'];
            $jobParams = $jobData['job']['params'];

            $jobInstance = new $jobClass(...array_values($jobParams));
            $jobInstance->handle();
        } catch (\Exception $e) {
            Log::error("执行任务失败: " . $e->getMessage());
            // 将任务放回队列重试
            (new QueueService())->retry($jobData);
        }
    }
}
// app/Jobs/BatchDownloadJob.php 不同的业务对应不同的job文件
<?php

namespace app\Jobs;

use app\Exception\ServiceException;
use app\Model\System\Attachment;
use app\Model\System\SysDownloadTask;
use app\Service\Compress\ZipService;
use app\Service\QueueService;
use think\facade\Log;
use think\facade\Cache;

class BatchDownloadJob
{
    protected $queueService;

    public function __construct($data)
    {
        $this->queueService = new QueueService('batchDownload');
        $this->data = $data;
    }

    public function handle()
    {
        try {
            Log::info("开始下载: {$this->data}");

            // 业务逻辑
            $this->process(json_decode($this->data, true));

        } catch (\Exception $e) {
            Log::error("下载任务失败: {$e->getMessage()}");
            // 将任务放回队列重试 todo 考虑加重试限制 
            // return $this->queueService->retry([
            //     'job' => [
            //         'class' => self::class,
            //         'params' => [
            //             'data' => $this->data,
            //         ]
            //     ],
            //     'attempts' => 0
            // ]);
        }
    }

    private function process(array $data)
    {
        // 业务逻辑方法 coding
    }
}
// config/console.php.  这部分命令行执行用,新增业务不需要修改这部分
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------

use app\Command\QueueWorker;

return [
    // 指令定义
    'commands' => [
        'queue:work' => QueueWorker::class,
    ],
];

// app/Service/QueueService.php 队列处理,新增业务不需要修改这部分
<?php
namespace app\Service;

use think\facade\Cache;
use think\facade\Log;

class QueueService
{
    protected $queueName;
    protected $driver;
    protected $retryTimes = 3;
    protected $retryDelay = 60;

    public function __construct($queueName = 'default')
    {
        $this->queueName = $queueName;
        $this->driver = $this->getQueueDriver();
    }

    protected function getQueueDriver()
    {
        // 根据配置获取队列驱动,这里简化为直接返回Redis驱动
        return Cache::store('redis');
    }

    public function push($job)
    {
        $payload = json_encode([
            'job' => $job,
            'attempts' => 0,
            'created_at' => time()
        ]);

        return $this->driver->rPush($this->getQueueKey(), $payload);
    }

    public function pop()
    {
        $payload = $this->driver->lPop($this->getQueueKey());

        if ($payload) {
            return json_decode($payload, true);
        }

        return null;
    }

    public function size()
    {
        return $this->driver->lLen($this->getQueueKey());
    }

    public function retry($jobData)
    {
        $jobData['attempts']++;

        if ($jobData['attempts'] <= $this->retryTimes) {
            // 延迟重试
            sleep($this->retryDelay);
            return $this->push($jobData['job']);
        } else {
            // 重试次数耗尽,记录失败日志
            Log::error('Job failed after retries', [
                'job' => $jobData['job'],
                'attempts' => $jobData['attempts']
            ]);
            return false;
        }
    }

    protected function getQueueKey()
    {
        return 'queue:' . $this->queueName;
    }
}
// 使用示例
$job = [
    'class' => BatchDownloadJob::class,
    'params' => [
        'data' => json_encode(['task_id' => $taskId], true),
    ]
];

// 将任务推送到队列
$queueService = new QueueService('batchDownload');
$sendResult = $queueService->push($job);

// 然后新增 Jobs 下的业务job文件,该文件中 必须要有 handle 方法
// 使用命令 php think queue:work --queue=batchDownload --daemon [ 其中batchDownload属于业务的队列名称 ]
posted @ 2025-06-21 18:42  露娜喵喵  阅读(102)  评论(0)    收藏  举报