废话不多说:
新增app/Amqp/DelayProducer.php
<?php
declare(strict_types=1);
namespace App\Amqp;
use Hyperf\Amqp\Builder;
use Hyperf\Di\Annotation\AnnotationCollector;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Wire\AMQPTable;
class DelayProducer extends Builder
{
public function produce(DelayProducerMessage $producerMessage, bool $confirm = false, int $timeout = 5): bool
{
return retry(1, function () use ($producerMessage, $confirm, $timeout) {
return $this->produceMessage($producerMessage, $confirm, $timeout);
});
}
/**
* @param DelayProducerMessage $producerMessage
* @param bool $confirm
* @param int $timeout
* @param int $delayTime
* @return bool
* @throws \Throwable
*/
private function produceMessage(DelayProducerMessage $producerMessage, bool $confirm = false, int $timeout = 5)
{
$result = false;
$this->injectMessageProperty($producerMessage);
$message = new AMQPMessage($producerMessage->payload(), $producerMessage->getProperties());
$pool = $this->getConnectionPool($producerMessage->getPoolName());
/** @var \Hyperf\Amqp\Connection $connection */
$connection = $pool->get();
if ($confirm) {
$channel = $connection->getConfirmChannel();
} else {
$channel = $connection->getChannel();
}
$channel->set_ack_handler(function () use (&$result) {
$result = true;
});
try {
$delayExchange = 'dead_letter_ex_' . $producerMessage->getExchange();
$delayQueue = 'dead_letter_queue_' . $producerMessage->getExchange() . $producerMessage->getTtl();
$delayRoutingKey = 'dead_letter_rk_'. $producerMessage->getRoutingKey();
//定义延迟交换器
$channel->exchange_declare($delayExchange, 'topic', false, true, false);
//定义延迟队列
$channel->queue_declare($delayQueue, false, true, false, false, false, new AMQPTable(array(
"x-dead-letter-exchange" => $producerMessage->getExchange(),
"x-dead-letter-routing-key" => $producerMessage->getRoutingKey(),
"x-message-ttl" => $producerMessage->getTtl() * 1000,
)));
//绑定延迟队列到交换器上
$channel->queue_bind($delayQueue, $delayExchange, $delayRoutingKey);
$channel->basic_publish($message, $delayExchange, $delayRoutingKey);
$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(DelayProducerMessage $producerMessage)
{
if (class_exists(AnnotationCollector::class)) {
/** @var \App\Annotations\DelayProducer $annotation */
$annotation = AnnotationCollector::getClassAnnotation(get_class($producerMessage), \App\Annotations\DelayProducer::class);
if ($annotation) {
$annotation->routingKey && $producerMessage->setRoutingKey($annotation->routingKey);
$annotation->exchange && $producerMessage->setExchange($annotation->exchange);
$annotation->ttl && $producerMessage->setTtl($annotation->ttl);
}
}
}
}
新增app/Amqp/DelayProducerMessage.php
<?php
declare(strict_types=1);
namespace App\Amqp;
use Hyperf\Amqp\Message\ProducerMessage;
class DelayProducerMessage extends ProducerMessage
{
/**
* @var integer 延迟时间(秒)
*/
protected $ttl;
public function setTtl($ttl)
{
$this->ttl = $ttl;
return $this;
}
public function getTtl()
{
return $this->ttl;
}
}
生产者:app/Amqp/Producer/OrderTimeoutResponse.php
<?php
declare(strict_types=1);
namespace App\Amqp\Producer;
use App\Amqp\DelayProducerMessage;
use App\Annotations\DelayProducer;
/**
* @DelayProducer(exchange="order.timeout.response", routingKey="order.timeout.response", ttl=86400)
*/
class OrderTimeoutResponse extends DelayProducerMessage
{
public function __construct($data)
{
$this->payload = $data;
}
}
消费者:app/Amqp/Consumer/OrderTimeoutResponse.php
<?php
declare(strict_types=1);
namespace App\Amqp\Consumer;
use App\Exception\BusinessException;
use Hyperf\Amqp\Annotation\Consumer;
use Hyperf\Amqp\Message\ConsumerMessage;
use Hyperf\Amqp\Result;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Hyperf\Config\Annotation\Value;
use Psr\Log\LoggerInterface;
/**
* 确认超时自动处理 - 延时队列
* @Consumer(exchange="order.timeout.response", routingKey="order.timeout.response", queue="order_timeout_response", nums=1)
* Class OrderTimeoutResponse
* @package App\Amqp\Consumer
*/
class TransferOrderTimeoutResponse extends ConsumerMessage
{
/**
* @var ConfigInterface
*/
protected ConfigInterface $config;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->config = $container->get(ConfigInterface::class);
$this->logger = $this->container->get(LoggerFactory::class)->get('transferOrderTimeoutResponse');
}
public function consume($data): string
{
//业务处理
return Result::ACK;
}
}