Laravel邮件、事件、队列浅谈

Sunday, March 26, 2017

Laravel 提供了很多的功能,今天通过用户注册后邮件通知用户的小功能来浅谈 邮件发送事件 以及 队列

使用 Laravel 自带的认证功能。

Laravel 自带的认证功能命令应该在新安装的应用下使用,它会生成 layout 布局视图,注册和登录视图,以及所有的认证路由,同时生成 HomeController ,用来处理登录成功后会跳转到该控制器下的请求,这里不细说了,详细的文档请看这里

php artisan make:auth

注册后发送通知邮件

Laravel 自带的注册功能完成后不会为用户发送邮件,我们要做的功能就是在用户注册完成之后为用户发送一封通知邮件。

完成邮件相关配置

Laravel 默认的 env 文件中列出了几项常规的邮件配置,包括驱动、主机、端口、账号、密码、加密方式等,默认使用的是 mailtrap 的服务,但国内访问的时候比较慢,所以我改成了网易邮箱。

注意:使用网易邮箱时,配置的账号必须和发送人邮箱一致,所以建议在 env 环境中增加 MAIL_FROM_ADDRESS MAIL_FROM_NAME 两个配置项,且 MAIL_FROM_ADDRESS MAIL_USERNAME 值要一致。

MAIL_PASSWORD 字段是网易邮箱邮箱的客户端授权码,不是网易账号密码,设置好客户端授权码后需要自行保存好,网易邮箱只会在设置授权码时展示一次,设置好后网易会发送短信提示。

网易邮箱示例配置如下:

MAIL_DRIVER=smtp
MAIL_HOST=smtp.163.com
MAIL_PORT=25
MAIL_FROM_ADDRESS=[网易邮箱账号]
MAIL_FROM_NAME=[发件人名称,可以自定义]
MAIL_USERNAME=[网易邮箱账号]
MAIL_PASSWORD=[网易邮箱客户端授权密码]
MAIL_ENCRYPTION=null

网易SMTP客户端授权码配置方式如下:

  1. 进入客户端授权密码设置页面

    进入客户端授权密码设置页面

  2. 开启客户端授权码,并点击设置授权码,设置前需要进行手机短信验证

    开启客户端授权码,并点击设置授权码,设置前需要进行手机短信验证

  3. 填写两次要设置的授权码

    填写两次要设置的授权码

  4. 设置成功,授权码需要自行保存,遗忘后只能重新设置

    设置成功,授权码需要自行保存,遗忘后只能重新设置

至此,邮件相关的配置就完成了,接下来需要做的就是在注册完成后增加发送邮件相关的代码。

增加发送邮件代码

Laravel 自带的注册功能代码在 app\Http\Controllers\Auth\RegisterController.php 文件中,使用的是Illuminate\Foundation\Auth\RegistersUsers 这个 Trait 来完成,我们可以在 RegisterController.php 文件的Create 函数中增加发送邮件相关的代码,完整的代码如下:

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return User
 */
protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => bcrypt($data['password']),
    ]);

    // 发送欢迎邮件
	Mail::send('emails.welcome',['user'=>$user],function($message) use($user){
        $message->to($user->email);
        $message->subject('Welcome to your Phplib');
    });

    return $user;
}

Mail 类的 send 函数接受三个参数,第一个参数是邮件要使用的视图名称,第二个参数为传入视图的数据,第三个参数是一个闭包,该闭包中定义了收件人、抄送人(如果有的话)、邮件主题、附件等信息,邮件的主体内容位于视图中。

接下来创建视图文件,在 resources/views 目录下创建 emails 目录,新建视图文件 welcome.blade.php,内容自定义。

至此,发送邮件相关的代码就完成了。

使用 Laravel 事件机制来发送邮件

前面我们通过修改 Laravel 自带的注册函数来完成邮件发送,这种方式的缺点是发送邮件的代码出现在了控制器中,接下来我们使用 Laravel 的事件机制来完成同样的功能。

laravel的事件机制包含两部分,一部分是事件类,保存在 app/Events 目录,另一部分是事件的监听类,保存在 app/Listeners 目录,项目中默认没有这两个目录,使用 artisan 创建事件时会自动生成。

手动创建事件和监听器是很麻烦的,简单的方式是,在 app/Providers/EventServiceProvider.phplisten 属性写上事件和监听器,代码如下:

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    'App\Events\WelcomeEmail' => [
        'App\Listeners\WelcomeEmailEventListener',
    ],
];

然后使用 event:generate 命令。这个命令会自动生成在 EventServiceProvider 中列出的所有事件和监听器,当然已经存在的事件和监听器将保持不变:

php artisan event:generate

接下来需要编写生成的事件类和监听类:

首先是事件类,由于我们需要在模版使用用户信息,所以我们需要在事件类中定义一个 publicuser 属性,供监听类使用,代码如下:

public $user;

/**
 * Create a new event instance.
 *
 * @return void
 */
public function __construct(User $user)
{
    $this->user = $user;
}

接下来是监听类,生成的监听类的 handle 函数默认已经添加了 WelcomeEmail $event 事件参数,所以我们只需要在函数中增加邮件发送相关代码就可以了,代码如下:

/**
 * Handle the event.
 *
 * @param  WelcomeEmail  $event
 * @return void
 */
public function handle(WelcomeEmail $event)
{
    $user = $event->user;
    //发送邮件
    Mail::send('emails.welcome',['user'=>$user],function($message) use($user){
        $message->to($user->email);
        $message->subject('Welcome to your Phplib');
    });
}

至此,事件和监听相关的代码就完成了,接下来我们需要修改之前 app\Http\Controllers\Auth\RegisterController.phpcreate 函数的代码,去掉发送邮件相关的代码,增加触发我们刚才编写的事件的代码,完整代码如下:

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return User
 */
protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => bcrypt($data['password']),
    ]);

    //触发发送邮件事件
	event(new WelcomeEmail($user));
	
   return $user;
}

event() 函数是 Laravel 内置的全局辅助函数,用于发派指定的到所属的监听器

除了 event() 函数外,也可以使用 Event::fire() 来触发事件。

至此,通过事件的方式来监听用户注册触发发送欢迎邮件的功能就完成了。

使用 Laravel 的队列功能完成邮件发送

前面我们通过使用 Laravel 的事件机制提取了发送欢迎邮件的功能,让控制器完成自己的功能,但事实上这里又有一个问题需要考虑,发送邮件是需要时间的,如果每个用户注册的时候都耗费一点时间去发送的话会造成页面响应时间变成,影响用户体验,接下来,我们使用 Laravel 的队列功能来解决这个问题。

Laravel的队列允许将一个耗时的任务进行延迟处理,来加快应用程序对页面请求的响应,它的配置文件为 config/queue.php,Laravel内置了几种驱动,包括数据库、Beanstalkd、IronMQ、Amazon SQS、Redis 以及 synchronous 驱动( 本地使用 )。队列驱动也可以配置为 null,这样就表示丢弃队列任务。本次我们使用 Beanstalkd 驱动来完成对应功能。

首先我们需要生成任务类,Laravel 的任务类默认都在 app/Jobs 这个目录中

php artisan make:job SendWelcomeemail

生成的了类默认都实现了 Illuminate\Contracts\Queue\ShouldQueue 接口,意味着这个任务将会被推送到队列中,而不是同步执行。

前面的事件监听器中,我们也可以实现 ShouldQueue 接口将发送邮件的任务中放到队列中。

队列类和监听类的结构相似,都是包含一个 __construct() 函数和一个 handle() 函数,不同的是,队列类的属性在本类中将会被调用,所以不需要设置为 public 属性, protected 属性即可,接下来我们将前面事件中的代码放到生成的队列类中,完整代码如下:

class SendWelcomeemail implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    protected $user;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $user = $this->user;
        //发送邮件
        Mail::send('emails.welcome',['user'=>$user],function($message) use($user){
            $message->to($user->email);
            $message->subject('Welcome to your Phplib');
        });
    }
}

至此,队列类就编写完成了,接下来我们需要做的就是在用户注册完成后来分发这个任务,使用的是 dispatch() 函数,这个函数接受一个任务类对象参数,比如我们要触发当前这个任务,可以在控制器文件中添加如下代码:

...
dispatch(new SendWelcomeemail($user))	
...	

这段代码执行后会在当前连接的默认队列中增加一个任务,接下来我们可以执行 queue:workqueue:listen 命令来分发这个队列中的任务:

php artisan queue:work [或者queue:listen] [--queue=vip,default]

--queue 选项是执行的队列名称,根据优先级依次填写即可,中间用英文逗号分隔。

这段代码会将当前的任务添加到默认的队列中,当分发队列任务时会立即执行,有时候我们需要将任务添加到一个高优先级的队列并且在被分发的时候延迟执行,这个时候我们可以使用队列的 onQueue() 函数来选择要放置任务的队列,使用 delay() 函数来延迟任务执行的时间,比如我们可以将发送欢迎邮件的任务分发到 vip 队列中,并且在被分发时延迟 20秒 执行,完整代码如下:

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return User
 */
protected function create(array $data)
{
    $user = User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => bcrypt($data['password']),
    ]);

    //分发发送邮件任务
    $job = (new SendWelcomeemail($user))->onQueue('vip')->delay(20);
    dispatch($job);

    return $user;
}

至此,通过队列来发送欢迎邮件的功能就完成了,可以 点击这里 注册账号测试下。

问题

如果机器上没有安装或者没有启动 beanstalkd 服务,会报下述错误:

ConnectionException in NativeSocket.php line 45:
Socket error 111: Connection refused (connecting to localhost:11300)

解决方式很简单,安装并启用beanstalkd 服务即可:

$ # 安装
$ yum install beanstalkd
$ # 启动
$ /etc/init.d/beanstalkd start

小结

这片文章中,通过对用户注册功能增加发送欢迎邮件功能,简单使用了 Laravel 自带的 邮件发送事件 以及 队列 功能,详细的功能可以点击链接查看对应的文档。

Laravel Laravel Queue

Laravel的核心概念全新环境安装Homestead