基于 Redis 消息队列实现 Laravel 邮件通知的异步发送


由于发送邮件、短信之类的操作通常涉及到第三方服务的调用,所以也是个响应时间不确定的耗时操作,如果放到处理用户请求进程中同步处理,需要等待很长时间才能获取响应结果,为了提升用户体验,可以让这些操作通过消息队列异步处理。

为了简化演示流程,我们使用邮件作为通知通道,一并介绍邮件和通知的异步发送。

配置邮件驱动

为了方便本地开发调试,使用 Maillog 作为邮件驱动,它可以在本地拦截应用发送的所有邮件并提供一个 Web 界面在浏览器中预览这些邮件信息,Laravel Sail 开发环境默认提供了这个容器服务,会随着 sail up -d 命令一起启动:

-w1264

.env 中配置邮件驱动信息,这里配置下系统发件人和用户名即可,其他可保持默认配置:

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=username
MAIL_PASSWORD=password
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=admin@redis.test
MAIL_FROM_NAME="${APP_NAME}"

注:这里随意配置发件人、用户名和密码即可,因为是本地测试,线上生产环境不能使用 Maillog,必须严格按照邮箱服务的主机、端口、用户账户进行配置

创建邮件通知类

接下来,我们创建一个通知类

sail artisan make:notification UserRegistered

该通知类用于在用户注册成功后发送邮件通知。编写这个通知类实现代码如下:

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;

class UserRegistered extends Notification implements ShouldQueue
{
    use Queueable;

    /**
     * Create a new notification instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->queue = 'notifications';
    }

    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

    /**
     * Get the mail representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return MailMessage
     */
    public function toMail($notifiable)
    {
        return (new MailMessage)
                    ->subject('新用户注册')
                    ->line('恭喜你成功注册' . config('app.name'))
                    ->action('立即进入应用', url('/'))
                    ->line('感谢你使用我们的应用!');
    }
}

我们在 via 方法中定义了通知通道为 mail,表示会通过上述邮件驱动发送这个通知,通邮件通知的具体实现定义在 toMail 方法中。此外,和队列任务类和事件监听器类一样,我们为通知类实现了 ShouldQueue 接口,表示会将邮件通知发送操作推送到消息队列异步处理,并且通过 queue 属性设置了队列名称为 notifications

接下来,我们就可以在用户注册成功后通过如下方式发送邮件通知了:

// 方式1:通过 User 实例提供的 notify 方法
$user->notify(new UserRegistered());

// 方式2:通过 Notification 门面
Notification::send($user, new UserRegistered());

不过,如果你使用 Laravel 官方提供的认证扩展包,用户注册路由和控制器动作扩展包底层都已经提供了,无需重新编写,底层代码又不好直接修改,这个时候,我们还可以通过监听用户注册事件来处理邮件通知异步发送。

定义用户注册事件监听器

以学院君现在使用的 Laravel Breeze 认证扩展包为例,该扩展包在用户注册成功后会触发 Laravel 底层提供的 Illuminate\Auth\Events\Registered 事件:

-w738

要在用户注册成功后发送邮件通知,可以监听这个事件并进行处理,为此,我们需要在 App\Providers\EventServiceProvider 注册监听这个事件的监听器类,Laravel 已经自带了一个针对该事件的监听器类:

-w685

SendEmailVerificationNotification 是由 Laravel 底层提供的,用于发送邮箱验证通知,该通知只有在启用邮箱验证功能的时候才会发送,目前我们并没有做此配置,所以这个通知不会发送。

这里,我们需要为 Registered 新增一个监听器类用于在注册成功后发送邮件通知:

Registered::class => [
    SendEmailVerificationNotification::class,
    'App\Listeners\SendRegistrationNotification',
],

运行 sail artisan event:generate 命令生成对应的监听器类,然后编写其实现代码如下:

<?php

namespace App\Listeners;

use App\Notifications\UserRegistered;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Notification;

class SendRegistrationNotification
{
    /**
     * Create the event listener.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Handle the event.
     *
     * @param  Registered  $event
     * @return void
     */
    public function handle(Registered $event)
    {
        Notification::send($event->user, new UserRegistered());
    }
}

由于通知类已经实现了 ShouldQueue 接口,所以事件监听同步处理就好了,当然,你也可以可以在这里实现 ShouldQueue 接口,这样的话,通知类 UserRegistered 就没有不要实现这个接口了,如果两个都实现 ShouldQueue 接口,都通过消息队列处理,反而是对系统资源的浪费,因为真正需要异步处理的只有邮件通知发送而已,我们不需要把简单的、能够快速处理的操作放到消息队列,因为这涉及到与 Redis 的交互、网络传输、序列化操作,这些都是需要消耗系统资源和网络传输时间的,如果比同步操作本身性能还要差,就没必要使用消息队列了。

演示用户注册邮件通知

到这里,我们就已经为用户注册成功后发送邮件通知功能做好了所有准备工作,在终端启动队列处理器进程监听并处理 notifications 队列中的任务:

sail artisan queue:work --queue=notifications --tries=3

在浏览器通过 http://redis.test/register 访问用户注册页面,填写用户信息后点击「REGISTER」按钮:

-w687

注册成功,会跳转到后台页面:

-w937

此时,你可以通过 http://redis.test:8025 查看 Maillog 拦截到的邮件信息:

-w1071

-w1065

至此,我们就完成了通过消息队列异步处理邮件通知的功能演示,当然了,你还以发送短信通知数据库通知(站内通知)、广播通知等更多通信类型,详情请参考 Laravel 通知文档。

关于 Laravel 底层是如何将通知发送推送到消息队列的,可以参考之前事件监听和广播的底层源码分析思路去查看,这里就不再赘述了。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 基于 Redis 消息队列实现 Laravel 文件存储的异步处理

>> 下一篇: 基于 Redis 实现 Laravel 分布式 Session 存取及底层源码探究