php之基于amqp封装rabbitmq消息发送代码示例一对多发送任务消息

文章详细介绍了如何使用PHP原生封装的rabbitmq框架,实现消息的发送和监听,支持一对多任务处理,并演示了如何配置交换机、绑定关系和使用延迟队列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


原创的amqp原生封装rabbitmq框架代码示例,一对多发送任务消息,开箱即用、也可以在php框架里面使用
可以兼容thinkphp、laravel、ci等任何框架

代码使用与学习跳转github地址、打开链接有使用说明!!
https://github.com/linsonggao/php-rabbitmq-bus

1.核心代码、监听消息、发送消息

<?php


namespace PhpRabbitMq\Lib\MessageBus\base;

use PhpRabbitMq\Lib\Instance;
use PhpRabbitMq\Lib\MessageBus\BusPassenger;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Connection\AMQPStreamConnection;


class AmqpBus extends AbstractBus implements BusInterface
{
    use Instance;
    /**
     * 启动
     * @return \think\Response
     */
    public function start($bus_name)
    {
        //dump("ensureBus");
        $param = config('rabbitmq.AMQP');
        //$amqpDetail = config('rabbitmq.direct_queue');
        $connection = new AMQPStreamConnection(
            $param['host'],
            $param['port'],
            $param['login'],
            $param['password'],
            $param['vhost'],
        );
        //dump("ensureBus");
        /*
         * 创建通道
         */
        $channel = $connection->channel();
        /*
         * 设置消费者(Consumer)客户端同时只处理一条队列
         * 这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个消费者(Consumer),
         * 直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的消费者(Consumer)。
         */
        //dump("ensureBus");
        $channel->basic_qos(0, 1, false);
        /*
         * 同样是创建路由和队列,以及绑定路由队列,注意要跟publisher的一致
         * 这里其实可以不用,但是为了防止队列没有被创建所以做的容错处理
         */
        var_dump("ensureBus");
        $this->ensureBus($channel, $bus_name);
        //$channel->queue_bind($amqpDetail['queue_name'], $amqpDetail['exchange_name'],$amqpDetail['route_key']);
        /*
            queue: 从哪里获取消息的队列
            consumer_tag: 消费者标识符,用于区分多个客户端
            no_local: 不接收此使用者发布的消息
            no_ack: 设置为true,则使用者将使用自动确认模式。详情请参见.
                        自动ACK:消息一旦被接收,消费者自动发送ACK
                        手动ACK:消息接收后,不会发送ACK,需要手动调用
            exclusive:是否排他,即这个队列只能由一个消费者消费。适用于任务不允许进行并发处理的情况下
            nowait: 不返回执行结果,但是如果排他开启的话,则必须需要等待结果的,如果两个一起开就会报错
            callback: :回调逻辑处理函数,PHP回调 array($this, 'process_message') 调用本对象的process_message方法
        */
        $noAck = $options['no_ack'] ?? true;

        //消费队列~
        $busInfo = BusPassenger::instance()->getBusInfo($bus_name);

        //var_dump($busInfo);
        foreach ($busInfo['passengers'] as $queue_name) {
            //$this->queue_name = $queue_name;
            //var_dump($queue_name);
            $channel->basic_consume($queue_name, '', false, $noAck, false, false, function (AMQPMessage $msg) use ($queue_name) {
                $messageData = json_decode($msg->body, true);
                //执行对应的class即可
                //todo.
                //var_dump($queue_name);
                $handler = BusPassenger::Instance()->getWork()[$queue_name]['class'];

                try {
                    /**@var AbstractWork $handler**/
                    $handler::Instance($handler)->handler($messageData);
                } catch (\Throwable $e) {
                    var_dump("消息消费错误信息:".$e->getMessage());
                };
            });
        }

        register_shutdown_function(array($this, 'shutdown'), $channel, $connection);
        // AMQP 队列的轮询时长
        $waitSeconds = $options['wait_seconds'] ?? 15;
        try {
            $channel->consume($waitSeconds);
        } catch (AMQPConnectionClosedException $e) {
        }
    }

    public function publish($data)
    {
        $param = config('rabbitmq.AMQP');
        $connection = new AMQPStreamConnection(
            $param['host'],
            $param['port'],
            $param['login'],
            $param['password'],
            $param['vhost']
        );
        $channel = $connection->channel();
        /*
         * 创建交换机(Exchange)
         * name: vckai_exchange// 交换机名称
         * type: direct        // 交换机类型,分别为direct/fanout/topic,参考另外文章的Exchange Type说明。
         * passive: false      // 如果设置true存在则返回OK,否则就报错。设置false存在返回OK,不存在则自动创建
         * durable: false      // 是否持久化,设置false是存放到内存中的,RabbitMQ重启后会丢失
         * auto_delete: false  // 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除
         */
        $channel->exchange_declare($data['bus_name'], "fanout", false, true, false);

        /*
             $messageBody:消息体
             content_type:消息的类型 可以不指定
             delivery_mode:消息持久化最关键的参数
             AMQPMessage::DELIVERY_MODE_NON_PERSISTENT = 1; 不持久化
             AMQPMessage::DELIVERY_MODE_PERSISTENT = 2; 持久化
         */

        //将要发送数据变为json字符串
        $messageBody = json_encode($data);
        /*
         * 创建AMQP消息类型
         * $messageBody:消息体
         * delivery_mode 消息是否持久化
         *      AMQPMessage::DELIVERY_MODE_NON_PERSISTENT = 1; 不持久化
         *      AMQPMessage::DELIVERY_MODE_PERSISTENT = 2; 持久化
         */
        $message = new AMQPMessage($messageBody, array('content_type' => 'text/plain', 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT));

        /*
         * 发送消息
         * msg       // AMQP消息内容
         * exchange  // 交换机名称
         * routing key     // 路由键名称
         */
        $channel->basic_publish($message, $data['bus_name'], $data['bus_name']);
        $channel->close();
        $connection->close();
        //echo  "ok";
    }
}

2.交换机绑定关系相关代码

<?php


namespace PhpRabbitMq\Lib\MessageBus\base;


use PhpRabbitMq\Lib\MessageBus\BusPassenger;
use PhpAmqpLib\Channel\AMQPChannel;
use PhpAmqpLib\Wire\AMQPTable;

abstract class AbstractBus
{
    private const DEFAULT_EXCHANGE = 'default.direct';
    private const DEFAULT_DELAYED_MESSAGE_EXCHANGE = 'default.dmx.direct';
    private const DEAD_LETTER_EXCHANGE = 'dlx.direct';

    /**
     * 消费端 消费端需要保持运行状态实现方式
     **/
    function shutdown(AMQPChannel $channel, $connection)
    {
        $channel->close();
        $connection->close();
    }
    /**
     * @param $channel
     * @param $name bus_name,交换机
     * @return void
     */
    function bindBusPassengers(AMQPChannel $channel, $name, callable $bindDirectBusQueue, callable $bindDelayBusQueue)
    {
        $businfo = BusPassenger::instance()->getBusInfo($name);
        $channel->queue_declare($name, false, true, false, false);
        if (is_array($businfo['delay_seconds_arr']) && count($businfo['delay_seconds_arr']) > 0 ) {
            foreach ($businfo['passengers'] as $passenger) {
                if (isset($businfo['delay_arr'][$passenger]) && $businfo['delay_arr'][$passenger]) {
                    $bindDelayBusQueue($businfo,$channel, $passenger, $name);
                } else {
                    $bindDirectBusQueue($businfo,$channel, $name);
                }
            }
        } else {
            $bindDirectBusQueue($businfo,$channel, $name);
        }
    }

    protected static function array2Table(array $array): AMQPTable
    {
        $table = new AMQPTable();
        foreach ($array as $key => $value) {
            $table->set($key, $value);
        }

        return $table;
    }

    protected function ensureBus(AMQPChannel $channel, $bus_name)
    {
        // 默认交换机
        $channel->exchange_declare(self::DEFAULT_EXCHANGE, "direct", false, true, false);
        // 默认延迟消息交换机
        // 如果 send 指定了延迟时间,使用此交换机
        $channel->exchange_declare(self::DEFAULT_DELAYED_MESSAGE_EXCHANGE, "direct", false, true, false);
        // 死信交换机
        $channel->exchange_declare(self::DEAD_LETTER_EXCHANGE, "direct", false, true, false);
        // 事件交换机
        // 事件交换机都是 fanout 类型的,消息被分发复制到每个绑定的队列
        $channel->exchange_declare($bus_name, 'fanout', false, true, false);
        $bindDirectBusQueue = function ($businfo,AMQPChannel $channel, $bus_name) {
            // 不需要延迟(还包含仅动态延迟的队列)
            // 直接把队列绑定到默认交换机和事件交换机
            // routing key 等于原始队列名称
            $channel->queue_bind($bus_name, self::DEFAULT_EXCHANGE, $bus_name);
            //dump("  bound to " . self::DEFAULT_EXCHANGE);
            foreach ($businfo['passengers'] as $passenger) {
                $channel->queue_declare($passenger, false, true, false, false);
                $channel->queue_bind($passenger, $bus_name, $passenger);
            }
        };
        $bindDelayBusQueue = function ($businfo,AMQPChannel $channel, $passenger, $bus_name) {
            //Message->Exchange--> Delayed Queue ->默认交换机(dlx.direct)->Queue->consumer hander
            $delayedName = $passenger . '.delayed.' . $businfo['delay_seconds_arr'][$passenger];
            // 因为有动态绑定的问题,需要删除之前的延迟队列
            $channel->queue_declare($delayedName, false, true, false, false, false, self::array2Table([
                'x-message-ttl' => 1000 * $businfo['delay_seconds_arr'][$passenger],
                'x-dead-letter-exchange' => self::DEAD_LETTER_EXCHANGE,
                'x-dead-letter-routing-key' => $bus_name,
            ]));

            // 把延迟队列绑定到事件交换机
            // routing key 等于原始队列名称
            $channel->queue_bind($delayedName, $bus_name, $bus_name);

            // 实际消费队列绑定到死信交换机
            $channel->queue_declare($passenger, false, true, false, false);
            $channel->queue_bind($passenger, self::DEAD_LETTER_EXCHANGE, $bus_name);
        };
        $this->bindBusPassengers($channel, $bus_name,$bindDirectBusQueue,$bindDelayBusQueue);
    }
}

3.rabbit交换机规则class

<?php

namespace PhpRabbitMq\Lib\MessageBus;

use PhpRabbitMq\Lib\Instance;
use PhpRabbitMq\Lib\MessageBus\works\OrderCancel;

/**
 * rabbitMq的交换机队列规则class
 */
class BusPassenger
{
    use Instance;
    //交换机
    private array $bus;
    //交换机的配置信息以及绑定的队列
    private array $busInfo;
    //队列对应的class对用works目录下的class
    private array $works = [];

    public function __construct()
    {
        $this->registerBus();
        $this->registerWork();


        //不存在的handler会自动绑定
        //自动模式会遵循大驼峰命名
        //比如member-login-cpa-callback对应MemberLoginCpaCallback
        $this->autoRegisterHanders();
    }

    /**
     * 注册交换机跟队列
     * 1对多绑定,1个事件发送到多个队列
     * @return void
     */
    private function registerBus()
    {
        //用户注册
        $this->defineBus(BusEnum::MEMBER_LOGIN);
        $this->defineBusInfo(BusEnum::MEMBER_LOGIN,BusEnum::MEMBER_LOGIN_CPA_CALL_BACK);
        $this->defineBusInfo(BusEnum::MEMBER_LOGIN,BusEnum::MEMBER_LOGIN_CPA_CALL_BACK_DATA);
    }
    /**
     * 手动绑定事件绑定类
     * @return void
     */
    private function registerWork()
    {
        // Core
        $this->registeHandler(BusEnum::ORDER_CANCEL, OrderCancel::class);
    }

    /**
     * 自动注册器,默认处理规则
     * @return void
     */
    private function autoRegisterHanders()
    {
        foreach ($this->getBus() as $bus_name){
            $busInfo = $this->getBusInfo($bus_name);
            foreach ($busInfo['passengers'] as $passenger) {
                if(!isset($this->works[$passenger])) {
                    $psArr = explode("-",$passenger);
                    $class = '';
                    foreach ($psArr as $psStr)
                    {
                        $class .= ucfirst($psStr);
                    }
                    $this->registeHandler($passenger,'PhpRabbitMq\\Lib\\MessageBus\\works\\'.$class);
                }
            }
        }
    }

    /**
     * 注册处理器
     * @param $command
     * @param $handlerClass
     * @return void
     */
    public function registeHandler($command, $handlerClass)
    {
        $this->works[$command] = [
            'class' => $handlerClass,
        ];
    }

    /**
     * 获取所有works
     * @return array
     */
    public function getWork()
    {
        return $this->works;
    }

    /**
     * 获取单个bus详情
     * @param $bus_name
     * @return mixed
     */
    public function getBusInfo($bus_name)
    {
        if(!isset($this->busInfo[$bus_name])) {
            dd("不存在的bus:".$bus_name);
        }
        return $this->busInfo[$bus_name];
    }

    /**
     * 获取所有bus
     * @return \Generator
     */
    public function getBus(): \Generator
    {
        foreach ($this->bus as $bus => $entry) {
            yield $bus;
        }
    }
    /**
     * 注册一个命令-
     *
     * @param string $command
     *
     * @return void
     */
    public function defineBus(string $command)
    {
        $this->bus[$command] = [
            'delay' => false,
            'delay_dynamic' => false,
            'delay_seconds' => 0,
        ];
    }
    /**
     * 注册命令和事件绑定关系-
     *
     * @param string $command
     * @param string $queue
     *
     * @return void
     */
    public function defineBusInfo(string $command, string $queue)
    {
        $queues = $this->busInfo[$command]["passengers"] ??[];
        $queues[]= $queue;
        $this->busInfo[$command] = [
            'delay' => false,
            'delay_dynamic' => false,
            'delay_seconds' => 0,
            'delay_seconds_arr' => [],
            'passengers' => $queues
        ];
    }
    /**
     * 注册一个有延迟需求的命令,参数 $delay_second 表示默认延迟时间。
     *
     * @param string $command
     * @param int $delay_seconds
     *
     * @return void
     */
    public function defineDelayedBusInfo(string $command, string $queue,int $delay_seconds = 0)
    {
        $queues = $this->busInfo[$command]["passenger"] ??[];
        $queues[]= $queue;

        $delay_seconds_arr = $this->busInfo[$command]["delay_seconds_arr"] ??[];
        $delay_seconds_arr[$queue]= $delay_seconds;

        $delay_arr = $this->busInfo[$command]["delay_arr"] ??[];
        $delay_arr[$queue]= true;

        $this->busInfo[$command] = [
            'delay' => true,
            'delay_dynamic' => false,
            'delay_arr' => $delay_arr,
            'delay_seconds_arr' => $delay_seconds_arr,
            'passengers' => $queues
        ];
    }
}

4.交换机队列枚举类

<?php

namespace PhpRabbitMq\Lib\MessageBus;

final class BusEnum
{

    public  const MEMBER_LOGIN="member-login";                                      //用户登录bus
    public  const MEMBER_LOGIN_CPA_CALL_BACK="member-login-cpa-call-back";           //用户登录bus:用户登录
    public  const MEMBER_LOGIN_CPA_CALL_BACK_DATA="member-login-call-back-data";     //用户登录bus:用户登录记录

}

5.监听代码

<?php
require "./vendor/autoload.php";

use PhpRabbitMq\Lib\MessageBus\base\AmqpBus;
ini_set ("memory_limit","-1");
//* 1.用户登录事件监听   php think message_bus --bus member-login
//*
//* 2.订单支付事件监听  php think message_bus --bus order-paid
//*
//* 3.订单创建事件监听  php think message_bus --bus order-create
AmqpBus::instance()->start("member-login");

6.测试发送消息demo代码

<?php
require "./vendor/autoload.php";
use PhpRabbitMq\Lib\MessageBus\base\AmqpBus;

$data = [
    "bus_name"=>"member-login",
    "test"=>"测试消息发送成功!!!"
];
var_dump("生产消息");
AmqpBus::instance()->publish($data);

测试成功发送消息、监听消息
​​在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兰博lamb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值