Laravel 使用 RabbitMQ 消息队列消费邮件

Laravel 使用 RabbitMQ 消息队列消费邮件
准备工作 (这里默认你的 RabbitMQ 和 Laravel 已经安装完毕)
  • 安装 php-amqplib 在项目目录下运行composer require php-amqplib/php-amqplib
  • 封装 RabbitMQ 的基本操作



namespace App\Components;

use App\Components\BaseComponent;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

class RmqClientComponent extends BaseComponent
{
    /**
     * @var object $instance 单例对象
     */
    private static $instance = null;

    /**
     * @var object $connection 队列连接对象
     */
    private $connection = null;

    /**
     * @var object $channel 队列通道对象
     */
    private $channel = null;

    /**
     * @var object $message 队列消息对象
     */
    private $message = null;

    /**
     * 构造函数
     *
     */
    private function __construct()
    {
        $this->connection = new AMQPStreamConnection(
            config('database.rabbitmq.host'),
            config('database.rabbitmq.port'),
            config('database.rabbitmq.user'),
            config('database.rabbitmq.pass'),
            config('database.rabbitmq.vhost')
        );
        $this->channel = $this->connection->channel();
        $this->message = new AMQPMessage('', ['content_type' => 'json', 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT]);
    }

    /**
     * 克隆函数
     *
     * 私有防克隆
     */
    private function __clone()
    {
    }

    /**
     * 析构函数
     */
    public function __destruct()
    {
        $this->channel->close();
        $this->connection->close();
        self::$instance = null;
    }

    /**
     * 单例实例化入口
     */
    public static function getInstance()
    {
        if (!self::$instance instanceof self) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * 消息入队列
     *
     * @param string $strExchange 交换机名称
     * @param string $strQueue 队列名称
     * @param array $aBody 消息内容
     * @param string $strType 路由类型
     *
     * @return array $aRes 标准输出
     */
    public function add($strExchange, $strQueue, $aBody, $strType = 'direct')
    {
        // 参数校验
        $objValidator = \Validator::make(
            [
                'exchange' => $strExchange,
                'queue' => $strQueue,
                'body' => $aBody,
                'type' => $strType,
            ],
            [
                'exchange' => 'required|string',
                'queue' => 'required|string',
                'body' => 'required|array',
                'type' => 'required|string',
            ]
        );
        if ($objValidator->fails()) {
            return self::error($objValidator->errors()->first());
        }
        try {
            // 声明交换机
            // 默认精准推送,不检测同名交换机,持久化,不自动删除交换机
            $this->channel->exchange_declare($strExchange, $strType, false, true, false);
            // 声明队列
            // 不检测同名队列,持久化,不允许其他队列访问,不自动删除队列
            $this->channel->queue_declare($strQueue, false, true, false, false);
            // 绑定队列和交换机,用队列名作routingKey
            $this->channel->queue_bind($strQueue, $strExchange, $strQueue);
            // 设备消息体
            $this->message->setBody(json_encode($aBody));
            // 推送信息
            $this->channel->basic_publish($this->message, $strExchange, $strQueue);

        } catch (Exception $e) {
            return self::error($e->getMessage());
        }
        return self::success();
    }

    /**
     * 从队列读取消息
     *
     * @param string $strQueue 队列名称
     * @param boolean $bForceDelete 是否取后即删
     *
     * @return array $aRes 标准输出
     * $aRes['data'] => message object
     */
    public function get($strQueue, $bForceDelete = false)
    {
        // 参数校验
        $objValidator = \Validator::make(
            [
                'queue' => $strQueue,
            ],
            [
                'queue' => 'required|string',
            ]
        );
        if ($objValidator->fails()) {
            return self::error($objValidator->errors()->first());
        }
        try {
            // 取数据
            $aValue = [];
            // 声明队列
            // 不检测同名队列,持久化,不允许其他队列访问,不自动删除队列
            $this->channel->queue_declare($strQueue, false, true, false, false);
            $objMessage = $this->channel->basic_get($strQueue);
            if ($objMessage && $bForceDelete) {
                // 回复确认信息
                $this->channel->basic_ack($objMessage->delivery_info['delivery_tag']);
            }
        } catch (Exception $e) {
            return self::error($e->getMessage());


        }
        return self::success($objMessage);
    }

    /**
     * 回复响应消息
     *
     * @param int $nTag 消息传递标签
     *
     * @return array $aRes  标准输出
     */
    public function ack($nTag)
    {
        try {
            $this->channel->basic_ack($nTag);
        } catch (Exception $e) {
            return self::error($e->getMessage());
        }
        return self::success();
    }
}

  • 创建消费的 artisan 命令


namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Components\RmqClientComponent;
use App\Components\EmailComponent;


class AsynSendEmail extends Command
{
    protected $signature = 'send:AsynSendEmail';
    protected $description = '异步发送邮件';

    public function handle()
    {
        while (true) {
        
            $objRabbitMQ = RmqClientComponent::getInstance();
            $strQueue = 'test_send_mail';
            $aMqMessage = $objRabbitMQ->get($strQueue, true);
            $objMqData = $aMqMessage['data'];
            if ($objMqData) {

                $strBody = $objMqData->body;
                $aMail = json_decode($strBody, true);

                $aResult = EmailComponent::sendEmail($aEmail);

                if ($aResult['status_code'] != 200) {
                    $strExchange = 'test_exchange';
                    $objRabbitMQ->add($strExchange, $strQueue, $aMail, $strType = 'direct');
                }
            }

        }

    }
}

  • 创建生产邮件的方法
  public static function sendEmail($aEmail) 
  {
       
        // 参数校验                                                                                                                                                                                                       
        $objValidator = \Validator::make(
            [
                'email' => $aEmail,
            ],
            [
                'email' => 'required|array',
                'email.mail_to' => 'required|string',
                'email.mail_title' => 'required|string',
                'email.mail_cc' => 'string',
                'email.mail_files' => 'string',
                'email.mail_id' => 'integer',
            ]
        );
        if ($objValidator->fails()) {
            return self::error($objValidator->errors()->first());
        }       
        $objRabbitMQ = RmqClientComponent::getInstance();
        $strExchange = 'test_exchange';
        $strQueue = 'test_send_mail';
        $aBody=$aEmail;
        $aMqResult= $objRabbitMQ->add($strExchange, $strQueue, $aBody, $strType = 'direct');
        if($aMqResult['status']=='true'){
            return self::success();
        }else{
            return self::error($aMqResult['msg']);
        }   
 }

  • 防止 artisan 命令的进程挂掉

    • 使用命令 ps axu|grep "php artisan send:AsynSendEmail"|grep -v "grep"|wc -l;
    • 若结果大于0则说明进程在运行
    • 若结果等于0则说明进程挂了,需要重启进程
    • 根据上面的命令,我们可以轻松的设计出一个监控进程的 artisan 命令
    • 使用定时任务一分钟执行一次检查消费队列的进程是否需要重启

你可能感兴趣的:(php,rabbitmq)