Laravel8 实现消息通知

Laravel从5.3起,提供了广播通知两个模块,可以很方便的实现消息通知。这里以评论回复通知为例进行介绍。

简单介绍下概念

  • 广播的对象是Socket客户端,通知的对象是根据渠道而定。可选的通知渠道有广播、短信、邮件、数据库或自定义渠道。
  • 广播可以直接由事件触发或由通知触发,这里采用的是后者。

流程

image

Laravel部分

用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

namespace App\Models\Sys;

use App\Models\Model;
use Illuminate\Notifications\Notifiable;

class User extends Model
{
use Notifiable;

protected $table = 'user';

/**
* 用户接收广播通知的通道.
*
* @return mixed
*/
public function receivesBroadcastNotificationsOn()
{
return 'user.' . $this->id;
}

}

评论

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

namespace App\Models\Common;

use App\Models\Model;
use App\Models\Sys\User;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;

class Comment extends Model
{
protected $table = 'comment';

protected $fillable = [
'content', 'uid', 'commentable_id', 'commentable_type'
];

protected $with = [
'user'
];

public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'uid');
}

public function commentable(): MorphTo
{
return $this->morphTo();
}
}

观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

namespace App\Observers;

use App\Models\Common\Comment;
use App\Notifications\CommentCreated;

class CommentObserver extends Observer
{
/**
* Handle the User "created" event.
*
* @param Comment $comment
* @return void
*/
public function created(Comment $comment)
{
event(new CommentCreated($comment));
}
}

注册观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

namespace App\Providers;

use App\Models\Common\Comment;
use App\Observers\CommentObserver;

use Illuminate\Support\ServiceProvider;


class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}

/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Comment::observe(CommentObserver::class);
}
}

事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

namespace App\Events;

use App\Models\Common\Comment;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class CommentCreated
{
use Dispatchable, SerializesModels;

/**
* @var Comment
*/
public $comment;

/**
* Create a new event instance.
*
* @param $comment
*/
public function __construct($comment)
{
$this->comment = $comment;
}
}

监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

namespace App\Listeners;

use App\Events\CommentCreated as CommentCreatedEvent;
use App\Models\Gs\IssueLog;
use App\Models\Sys\User;
use App\Notifications\CommentCreated;

class NotifyComment extends Listener
{
/**
* Handle the notification.
*
* @param CommentCreatedEvent $event
*/
public function handle(CommentCreatedEvent $event)
{
// 相关用户
$users = [];
if ($event->comment->commentable instanceof IssueLog) {
// 工单跟进者
$user[$event->comment->commentable->user->id] = $event->comment->commentable->user;
// 工单创建者
$user[$event->comment->commentable->creator->id] = $event->comment->commentable->creator;
// 工单报表评论者
foreach ($event->comment->commentable->comments as $cmt) {
$users[$cmt->user->id] = $users[$cmt->user->id] ?? $cmt->user;
}
}

// 发送通知
foreach ($users as $user) {
/** @var User $user */
if ($user->id != $event->comment->uid) {
$user->notify(new CommentCreated($event->comment));
}
}
}
}

Laravel从5.8.9开始支持事件发现,无需手动注册事件及对应监听器。

通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Common\Comment;
use \Illuminate\Notifications\Notification

class CommentCreated extends BaseNotification implements ShouldQueue
{
use Queueable;

protected $comment;

/**
* Get the notification's delivery channels.
*
* @return array
*/
public function via()
{
return ['broadcast', 'database'];
}

/**
* Create a new notification instance.
*
* @param Comment $comment
*/
public function __construct(Comment $comment)
{
$this->comment = $comment;
}

/**
* Get the array representation of the notification.
*
* @return array
*/
public function toArray()
{
return [
'commentable' => $this->comment->commentable,
'commentable_type' => get_class($this->comment->commentable),
'comment' => $this->comment
];
}
}

NodeJS部分

安装依赖

1
npm i laravel-echo laravel-echo-server pm2 socket.io-client

启动Larvel Echo Server

启动脚本

queue-listen.sh

1
2
3
#!/usr/bin/env bash

php artisan queue:listen --tries=1

socket.sh

1
2
3
#!/usr/bin/env bash

node_modules/laravel-echo-server/bin/server.js start

使用PM2调度

1
2
3
4
node_modules/pm2/bin/pm2 start queue-listen.sh
node_modules/pm2/bin/pm2 start socket.sh
node_modules/pm2/bin/pm2 monit # 监控进程输出,便于开发调试
# 后续调度可使用stop/start/restart all

你可以使用Supervisor代替PM2。

PM2启动的进程与用户关联,使用同一个户进行操作,避免重复启动

Laravel Echo监听通知

全局变量声明

resources/js/bootstrap.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/

import Echo from 'laravel-echo';

window.io = require('socket.io-client');
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + (process.env.MIX_APP_ENV === 'local' ? ':6001' : ''),
auth: {
headers: {}
},
});

启动监听

主视图

1
2
3
4
5
6
<script>
Echo.private('user.' + uid)
.notification((e) => {
// do something
});
</script>