hyperf框架使用rabbitMQ生產訊息至laravel/lumen進行消費

wd731869706發表於2021-11-05

需要做專案遷移時,例如laravel遷移至hyperf時,因為基本上都是一步一步遷移的,仍有例如支付回撥等依舊在laravel框架中進行消費的情況。需要接管處理訊息的queue進行資料格式改造,利用構造同樣名稱空間的job去進行投遞,他會序列化資料,可以debug一下內容哦,然後投遞至rabbitMQ後,laravel進行消費就好啦。其中hyperf的版本背景為2.1

  1. 在app下建立Job目錄為例,大家可以根據情況來
  2. 在Job目錄下建立Job.php,複製以下程式碼
<?php

declare(strict_types=1);

namespace AppJob;

/**
 * Class Job
 * @package AppJob
 */
class Job
{
    protected $job;
    public $connection;
    public $queue;
    public $delay;

    /**
     * Job constructor.
     */
    public function __invoke()
    {
        $this->job = null;
        $this->connection = null;
        $this->queue = null;
        $this->delay = null;
    }

    /**
     * Set the desired delay for the job.
     *
     * @param  DateTime|int|null  $delay
     * @return $this
     */
    public function delay($delay)
    {
        $this->delay = $delay;

        return $this;
    }

    /**
     * Set the desired queue for the job.
     *
     * @param  string|null  $queue
     * @return $this
     */
    public function onQueue($queue)
    {
        $this->queue = $queue;

        return $this;
    }


    /**
     * Set the desired connection for the job.
     *
     * @param  string|null  $connection
     * @return $this
     */
    public function onConnection($connection)
    {
        $this->connection = $connection;

        return $this;
    }

}
  1. 接管Producer.php,繼續建立Producer.php,複製以下程式碼進去
<?php

declare(strict_types=1);

namespace AppJob;

use HyperfAmqpConnection;
use HyperfAmqpMessageProducerMessageInterface;
use PhpAmqpLibMessageAMQPMessage;
use PhpAmqpLibWireAMQPTable;
use HyperfAmqpBuilder;

/**
 * 生產者
 * Class Producer
 * @package AppJob
 */
class Producer extends Builder
{
    public $exchange_type;
    public $exchange_passive;
    public $exchange_durable;
    public $exchange_auto_delete;

    public $queue_passive;
    public $queue_durable;
    public $queue_exclusive;
    public $queue_auto_delete;
    public $queue_nowait;

    public function checkExchange($channel, $producerMessage)
    {
        $exchange   = $producerMessage->getExchange();
        $queue      = $producerMessage->getQueue();
        $routingKey = $producerMessage->getRoutingKey();
        $ttl        = $producerMessage->getTtl();

        $this->exchange_type = env('RABBITMQ_EXCHANGE_TYPE', 'direct');
        $this->exchange_passive = env('RABBITMQ_EXCHANGE_PASSIVE', false);
        $this->exchange_durable = env('RABBITMQ_EXCHANGE_DURABLE', true);
        $this->exchange_auto_delete = env('RABBITMQ_EXCHANGE_PASSIVE', false);

        $this->queue_passive = env('RABBITMQ_QUEUE_PASSIVE', false);
        $this->queue_durable = env('RABBITMQ_QUEUE_DURABLE', true);
        $this->queue_exclusive = env('RABBITMQ_QUEUE_EXCLUSIVE', false);
        $this->queue_auto_delete = env('RABBITMQ_QUEUE_AUTODELETE', false);

        //定義交換器
        $channel->exchange_declare($exchange, $this->exchange_type, $this->exchange_passive, $this->exchange_durable, $this->exchange_auto_delete);

        //定義佇列
        $channel->queue_declare($queue, $this->queue_passive, $this->queue_durable, $this->queue_exclusive, $this->queue_auto_delete);
        //繫結佇列到交換器上
        $channel->queue_bind($queue, $exchange, $routingKey);

        if ($ttl > 0) {
            // $delayExchange   = 'delayed_exchange_' . $exchange;
            // $delayQueue      = 'delayed_queue_' . $queue . '_' . $ttl;
            // $delayRoutingKey = $routingKey . $ttl;
            $delayExchange   = $exchange;
            $delayQueue      = $queue . '_deferred_' . $ttl;
            $delayRoutingKey = $delayQueue;
            //定義延遲交換器
            $channel->exchange_declare($delayExchange, $this->exchange_type, $this->exchange_passive, $this->exchange_durable, $this->exchange_auto_delete);

            //定義延遲佇列
            $channel->queue_declare($delayQueue, $this->queue_passive, $this->queue_durable, $this->queue_exclusive, $this->queue_auto_delete, false, new AMQPTable(array(
                "x-dead-letter-exchange"    => $exchange,
                "x-dead-letter-routing-key" => $routingKey,
                "x-message-ttl"             => $ttl * 1000,
            )));
            //繫結延遲佇列到交換器上
            $channel->queue_bind($delayQueue, $delayExchange, $delayRoutingKey);

            $producerMessage->setExchange($delayExchange);
            $producerMessage->setRoutingKey($delayRoutingKey);
        }
    }

    /**
     * @param ProducerMessageInterface $producerMessage
     * @param $routingKey
     * @param $exchange
     * @param bool $confirm
     * @param int $timeout
     * @return bool
     * @throws Exception
     * @throws Throwable
     */
    public function produce(ProducerMessageInterface $producerMessage, $routingKey, $exchange, bool $confirm = false, int $timeout = 5): bool
    {
        return retry(1, function () use ($exchange, $routingKey, $producerMessage, $confirm, $timeout) {
            return $this->produceMessage($producerMessage, $routingKey, $exchange, $confirm, $timeout);
        });
    }

    /**
     * @param ProducerMessageInterface $producerMessage
     * @param $routingKey
     * @param $exchange
     * @param bool $confirm
     * @param int $timeout
     * @return bool
     * @throws Throwable
     */
    private function produceMessage(ProducerMessageInterface $producerMessage, $routingKey, $exchange, bool $confirm = false, int $timeout = 5)
    {
        $result = false;

        $this->injectMessageProperty($producerMessage, $routingKey, $exchange);

        $delay = $producerMessage->getTtl();
        if ($delay > 0) {
            $message = new AMQPMessage($producerMessage->payload(), array_merge($producerMessage->getProperties(), [
                'expiration' => $delay * 1000,
            ]));
        } else {
            $message = new AMQPMessage($producerMessage->payload(), $producerMessage->getProperties());
        }
        // $message = new AMQPMessage($producerMessage->payload(), $producerMessage->getProperties());

        $pool = $this->getConnectionPool($producerMessage->getPoolName());
        /** @var Connection $connection */
        $connection = $pool->get();
        if ($confirm) {
            $channel = $connection->getConfirmChannel();
        } else {
            $channel = $connection->getChannel();
        }
        $channel->set_ack_handler(function () use (&$result) {
            $result = true;
        });

        try {
            // 檢測交換機和佇列
            $this->checkExchange($channel, $producerMessage);

            $channel->basic_publish($message, $producerMessage->getExchange(), $producerMessage->getRoutingKey());
            $channel->wait_for_pending_acks_returns($timeout);
        } catch (Throwable $exception) {
            // Reconnect the connection before release.
            $connection->reconnect();
            throw $exception;
        } finally {
            $connection->release();
        }

        return $confirm ? $result : true;
    }

    private function injectMessageProperty(ProducerMessageInterface $producerMessage, $routingKey, $exchange)
    {
        $producerMessage->setRoutingKey($routingKey);
        $producerMessage->setExchange($exchange);
    }
}
  1. 接管ProducerMessage.php,繼續建立ProducerMessage.php,複製以下程式碼進去
<?php

declare(strict_types=1);

namespace AppJob;

use HyperfAmqpConstants;
use HyperfAmqpMessageMessage;
use HyperfAmqpMessageProducerMessageInterface;

/**
 * 生產訊息
 * Class Job
 * @package AppJob
 */
abstract class ProducerMessage extends Message implements ProducerMessageInterface
{
    /**
     * @var string
     */
    protected $payload = '';

    /**
     * @var string
     */
    protected $routingKey = '';

    /**
     * @var array
     */
    protected $properties
        = [
            'content_type' => 'text/plain',
            'delivery_mode' => Constants::DELIVERY_MODE_PERSISTENT,
        ];

    public function getProperties(): array
    {
        return $this->properties;
    }

    public function setPayload($data): self
    {
        $this->payload = $data;
        return $this;
    }

    public function payload(): string
    {
        return $this->serialize();
    }

    public function serialize(): string
    {
        return json_encode($this->payload);
    }

    /**
     * @var integer 延遲時間(秒)
     */
    protected $ttl = 0;

    public function setTtl($ttl)
    {
        $this->ttl = $ttl;
        return $this;
    }

    public function getTtl()
    {
        return $this->ttl;
    }

    protected $queue = 'default';

    public function setQueue($name)
    {
        $this->queue = $name;

        return $this;
    }

    public function getQueue()
    {
        return $this->queue;
    }
}
  1. 序列化資料,建立SerializeJobData.php,複製以下程式碼進去
<?php

declare(strict_types=1);


namespace AppJob;
use HyperfUtilsStr;

/**
 * 序列化佇列資料
 * Class SerializeJobData
 * @package AppJob
 */
class SerializeJobData extends ProducerMessage
{
    public function __construct($job)
    {
        // 設定不同 pool
        $this->poolName = 'default';
        /**
         * 當驅動為redis時
         * use IlluminateSupportStr;
         * 'id' => Str::random(32),'attempts' => 0,
         */
        if (env('QUEUE_DRIVER', 'rabbitmq') == 'rabbitmq') {
            $this->payload = [
                'displayName' => get_class($job),
                'job' => 'IlluminateQueueCallQueuedHandler@call',
                'maxTries' => isset($job->tries) ? $job->tries : null,
                'timeout' => isset($job->timeout) ? $job->timeout : null,
                'data' => [
                    'commandName' => get_class($job),
                    'command' => serialize(clone $job)
                ]
            ];
        } else {
            $this->payload = [
                'displayName' => get_class($job),
                'job' => 'IlluminateQueueCallQueuedHandler@call',
                'maxTries' => isset($job->tries) ? $job->tries : null,
                'timeout' => isset($job->timeout) ? $job->timeout : null,
                'data' => [
                    'commandName' => get_class($job),
                    'command' => serialize(clone $job)
                ],
                'id' => Str::random(32),
                'attempts' => 0
            ];
        }

    }
}
  1. 建立助手函式 注意我的內容哦 按需修改
if (!function_exists('producerPushData')) {
    /**
     * 投遞資訊
     * @param ProducerMessageInterface $message 訊息
     * @param string $routingKey 預設 default
     * @param string $exchange 所投入的queue
     * @param bool $confirm 是否需要確認
     * @param int $timeout 超時時間
     * @return bool
     * @throws Throwable
     */
    function producerPushData($message, $routingKey = 'default', $exchange = '', bool $confirm = false, int $timeout = 5)
    {
        $exchange = !empty($exchange) ? $exchange : env('RABBITMQ_EXCHANGE_NAME', 'sweetheart');
        return make(Producer::class)->produce($message, $routingKey, $exchange, $confirm, $timeout);
    }
}
  1. 使用方式 注意需要和laravel/lumen 保持同樣的名稱空間哦

建立的job需要繼承```AppJobJob``

<?php

declare(strict_types=1);

use AppJobJob;

/**
 * Class TestJob
 */
class TestJob extends Job
{
    /**
     * @var
     */
    protected $data;

    /**
     * TestJob constructor.
     * @param $data
     */
    public function __construct($data)
    {
        $this->data = $data;
    }

    /**
     * 處理邏輯
     */
    public function __handle()
    {

    }
}
use AppJobSerializeJobData;
use TestJob;
$data = [];
$job = new TestJob($data);
producerPushData((new SerializeJobData($job)));
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章