Laravel 5.1事件系统

事件系统有啥用?

  • 解耦,抽出与核心业务关联不大的代码逻辑
  • 异步执行耗时长任务,加快页面响应速度

事件使用样例

现在需要完成这样一件事,当一个图片被用户收藏到一个专辑内时,专辑的封面会自动根据最新的4张图片来生成一个新的专辑封面。因为所有的图片均存储在第三方云存储上,每次都需要下载回来4张图片,在服务器上处理后再回传到云存储上,所以采用Laravel的事件系统来做。

创建事件触发器

php artisan make:event imageAddedToAlbum

这个时候会在app/Events里面生成对应的imageAddedToAlbum.php文件,当然手工创建也可以

代码如下:

namespace App\Events;
use App\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class imageAddedToAlbum extends Event
{
use SerializesModels;
public $albumId;
/**
* @param $albumId
*/
public function __construct($albumId) //为了重新生成专辑封面,需要传递一个专辑编号进来
{
$this->albumId = $albumId;
}
/**
* Get the channels the event should be broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return [];
}
}

创建事件监听器

php artisan make:listener reGenerateAlbumCover

这个时候会在app/Listeners里面生成对应的文件
上面的事件和监听主要作用就是,当图片被加入一个专辑的时候,这个专辑的专辑封面就自动生成一次。

namespace App\Listeners;
use App\Events\imageAddedToAlbum;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Album;
use Upyun;
use File;
use Intervention\Image\Facades\Image;
class reGenerateAlbumCover implements ShouldQueue //ShouldQueue主要是为了异步执行,要把任务推到beanstalkd里面去
{
public function __construct()
{
}
public function handle(imageAddedToAlbum $event) //在handle方法里面把imageAddedToAlbum事件传递进来
{
$album = Album::find($event->albumId); //获取专辑编辑,下面开始生成专辑封面
//重新生成和显示专辑封面
if ($album->thumbnail) {
$type = substr($album->thumbnail, 14, 1);
} else {
$type = 0;
}
$albumPicturesTotal = $album->pics()->count();
if ($albumPicturesTotal <= 3 && $albumPicturesTotal >= 1) {
//专辑图片小于3张的时候,专辑封面由一张图片构成
if ($type == 0) {
$albumThumbnail = uniqid() . '_1' . '.jpg';
//由专辑内的第一张图片生成专辑缩略图
$albumFirstImage = $album->pics()->getResults()->first();
if (! is_null($albumFirstImage)) {
$imagePath = config('asset.image_domain') . '/uploads/image/' . $albumFirstImage->image;
$albumartFolder = public_path('uploads/albumart/');
if (! File::exists($albumartFolder)) {
File::makeDirectory($albumartFolder, '0755', true);
}
$srcPath = $albumartFolder . $albumThumbnail;
Image::canvas(200, 200)
->fill(Image::make($imagePath)->resize(200, 200))
->save($srcPath);
$upyunOpt = [
'x-gmkerl-quality' => 95,
'x-gmkerl-unsharp' => true,
'x-gmkerl-type' => 'fix_width',
'x-gmkerl-value' => '1600',
];
$fileHandle = fopen($srcPath, 'r');
$upyunFileName = '/uploads/albumart/' . $albumThumbnail;
Upyun::writeFile($upyunFileName, $fileHandle, true, $upyunOpt);
}
$album->thumbnail = $albumThumbnail;
//保存专辑缩略图至专辑数据中
$album->save();
}
} else if ($albumPicturesTotal > 3) {
//专辑图片大于3张的时候,专辑封面由四张图片构成
$albumThumbnail = uniqid() . '_4' . '.jpg';
$albumImage1 = $album->pics()->orderBy('pivot_id', 'desc')->getResults()->get(0);
$albumImage1Path = config('asset.image_domain') . '/uploads/image/' . $albumImage1->image;
$albumImage2 = $album->pics()->orderBy('pivot_id', 'desc')->getResults()->get(1);
$albumImage2Path = config('asset.image_domain') . '/uploads/image/' . $albumImage2->image;
$albumImage3 = $album->pics()->orderBy('pivot_id', 'desc')->getResults()->get(2);
$albumImage3Path = config('asset.image_domain') . '/uploads/image/' . $albumImage3->image;
$albumImage4 = $album->pics()->orderBy('pivot_id', 'desc')->getResults()->get(3);
$albumImage4Path = config('asset.image_domain') . '/uploads/image/' . $albumImage4->image;
$albumartFolder = public_path('uploads/albumart/');
if (! File::exists($albumartFolder)) {
File::makeDirectory($albumartFolder, '0755', true);
}
$srcPath = $albumartFolder . $albumThumbnail;
Image::canvas(200, 200)
->insert(Image::make($albumImage1Path)->fit(100, 100), 'top-left')
->insert(Image::make($albumImage2Path)->fit(100, 100), 'top-right')
->insert(Image::make($albumImage3Path)->fit(100, 100), 'bottom-left')
->insert(Image::make($albumImage4Path)->fit(100, 100), 'bottom-right')
->save($srcPath);
$upyunOpt = [
'x-gmkerl-quality' => 95,
'x-gmkerl-unsharp' => true,
'x-gmkerl-type' => 'fix_width',
'x-gmkerl-value' => '1600',
];
$fileHandle = fopen($srcPath, 'r');
$upyunFileName = '/uploads/albumart/' . $albumThumbnail;
Upyun::writeFile($upyunFileName, $fileHandle, true, $upyunOpt);
Upyun::delete('/uploads/albumart/' . $album->thumbnail);
$album->thumbnail = $albumThumbnail;
//保存专辑缩略图至专辑数据中
$album->save();
}
}
}

触发事件

Event::fire(new imageAddedToAlbum($albumId));

触发Listener

php artisan queue:listen --sleep=10 --timeout=60

执行结果如下所示:

Processed: Illuminate\Events\CallQueuedHandler@call

异步执行事件任务

Laravel的默认的队列配置

将事件的任务推到队列里面去,默认Laravel的队列配置是同步执行的可以查看config/queue.php配置如下(部分)

'default' => env('QUEUE_DRIVER', 'sync'),

为了达到异步效果就要使用队列了,官方推荐为beanstalkd。

安装及监控beanstalkd队列

参考下面两篇文章
How to install Beanstalkd on CentOS 6
How to enable epel repo on centos 6 and 5
beanstalkd web 监控工具:https://github.com/ptrofimov/beanstalk_console
beanstalkd的web监控效果如下图所示,效果还是非常好的
beanstalk web console

supervisor的使用

保持进程的长久运行

为了让上面提到的

php artisan queue:listen --sleep=10 --timeout=60

在异常退出的情况下重新开起来,这里我们要使用supervisor这个进程监控工具来处理

supervisor安装

安装命令

sudo yum install supervisor

添加对应的脚本配置

supervisor的配置文件默认在:/etc/supervisor.conf

[program:reGenerateAlbumCover]
command=php /home/path/to/artisan queue:listen --sleep=10 --timeout=60
priority=999
autostart=true
autorestart=true
startsecs=10
startretries=3
exitcodes=0,2
stopsignal=QUIT
stopwaitsecs=10
user=chrism
log_stdout=true
log_stderr=true
logfile=/var/log/reGenerateAlbumCover.log
logfile_maxbytes=1MB
logfile_backups=10