Laravel 的事件广播系统
简介
在现代的 web 应用程序中,WebSockets 被用来实现需要实时、即时更新的接口。当服务器上的数据被更新后,更新信息将通过 WebSocket 连接发送到客户端等待处理。相比于不停地轮询应用程序,WebSocket 是一种更加可靠和高效的选择。
为了帮助你建立这类应用, Laravel 将通过 WebSocket 连接来使「广播」事件 变得更加轻松。广播事件允许你在服务端代码和客户端 JavaScript 应用之间共享相同的事件名。
在深入了解事件广播之前,请确认你已阅读所有关于 Laravel 事件和侦听器 的文档。
配置
所有关于事件广播的配置都被保存在 config/broadcasting.php
文件中。 Laravel 自带了几个广播驱动器:Pusher, Redis, 和一个用于本地开发与调试的 log
驱动器。另外,还有一个 null
驱动器可以让你完全关闭广播功能。每一个驱动的示例配置都可以在 config/broadcasting.php
文件中被找到。
广播服务提供者
在对事件进行广播之前,你必须先注册 App\Providers\BroadcastServiceProvider
。对于一个全新安装的 Laravel 应用程序,你只需在 config/app.php
配置文件的 providers
数组中取消对该提供者的注释即可。该提供者将允许你注册广播授权路由和回调。
CSRF 令牌
Laravel Echo 会需要访问当前会话的 CSRF 令牌。如果可用,Echo 会从 Laravel.csrfToken
JavaScript 对象中获取该令牌。如果你运行了 make:auth
Artisan 命令,该对象会在 resources/views/layouts/app.blade.php
布局文件中被定义。如果你未使用该布局文件,可以在应用程序的 head
HTML 元素中定义一个 meta
标签:
<meta name="csrf-token" content="{{ csrf_token() }}">
对驱动器的要求
Pusher
如果你使用 Pusher 对事件进行广播,请用 Composer 包管理器来安装 Pusher PHP SDK:
composer require pusher/pusher-php-server "~3.0"
然后,你需要在 config/broadcasting.php
配置文件中填写你的 Pusher 证书。该文件中已 经包含了一个 Pusher 示例配置,你只需指定 Pusher key、secret 和 application ID 即可。config/broadcasting.php
中的 pusher
配置项同时也允许你指定 Pusher 支持的 options
,例如 cluster:
'options' => [
'cluster' => 'eu',
'encrypted' => true
],
当把 Pusher 和 Laravel Echo 一起使用时,你应该在 resources/assets/js/bootstrap.js
文件中实例化 Echo 对象时指定 pusher
作为所需要的 broadcaster :
import Echo from "laravel-echo"
window.Pusher = require('pusher-js');
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key'
});
Redis
如果你使用 Redis 广播器,请安装 Predis 库:
composer require predis/predis
Redis 广播器会使用 Redis 的「生产者/消费者」特性来广播消息;尽管如此,你仍需将它与 WebSocket 服务器一起使用。WebSocket 服务器会从 Redis 接收消息,然后再将消息广播到你的 WebSocket 频道上去。
当 Redis 广播器发布一个事件时,该事件会被发布到它指定的频道上去,传输的数据是一个采用 JSON 编码的字符串。该字符串包含了事件名、 data
数据和生成该事件套接字 ID 的用户(如果可用的话)。
Socket.IO
如果你想把 Redis 广播器和 Socket.IO 服务器一起使用,你需要将 Socket.IO JavaScript 客户端库文件包含到应用程序的 head
HTML 元素中。当 Socket.IO 服务启动的时候,它会自动把 Socket.IO JavaScript 客户端库暴露在一个标准的 URL中。例如,如果你的应用和 Socket.IO 服务器运行在同域名下,你可以像下面这样来访问你的 Socket.IO JavaScript 客户端库:
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
接着,你需要在实例化 Echo 时指定 socket.io
连接器和 host
。
import Echo from "laravel-echo"
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ':6001'
});
最后,你需要运行一个与 Laravel 兼容的 Socket.IO 服务器。Laravel 官方并没有实现 Socket.IO 服务器;不过,可以选择一个由社区驱动维护的项目 tlaverdure/laravel-echo-server ,目前托管在 GitHub。
对队列的要求
在开始广播事件之前,你还需要配置和运行 队列侦听器 。所有的事件广播都是通过队列任务来完成的,因此应用程序的响应时间不会受到明显影响。
概念综述
Laravel 的事件广播允许你使 用基于驱动的 WebSockets 将服务端的 Larevel 事件广播到客户端的 JavaScript 应用程序。当前的 Laravel 自带了 Pusher 和 Redis 驱动。通过使用 Laravel Echo 的 Javascript 包,我们可以很方便地在客户端消费事件。
事件通过「频道」来广播,这些频道可以被指定为公开的或私有的。任何访客都可以订阅一个不需要认证和授权的公开频道;然而,如果想订阅一个私有频道,那么该用户必须通过认证,并获得该频道的授权。
使用示例程序
让我们先用一个电子商务网站作为例子来概览一下事件广播。我们不会讨论如何配置 Pusher 或者 Laravel Echo 的细节,因为这些会在本文档的其他章节被详细介绍。
在我们的应用程序中,让我们假设有一个允许用户查看订单配送状态的页面。有一个 ShippingStatusUpdated
事件会在配送状态更新时被触发:
event(new ShippingStatusUpdated($update));
ShouldBroadcast
接口
当用户在查看自己的订单时,我们不希望他们必须通过刷新页面才能看到状态更新。我们希望一旦有更新时就主动将更新信息广播到客户端。所以,我们必须让 ShippingStatusUpdated
事件实现 ShouldBroadcast
接口。这会让 Laravel 在事件被触发时广播该事件:
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class ShippingStatusUpdated implements ShouldBroadcast
{
/**
* Information about the shipping status update.
*
* @var string
*/
public $update;
}
ShouldBroadcast
接口要求事件实现 broadcastOn
方法。该方法负责指定事件被广播到哪些频道。在通过 Artisan 命令生成的事件类中,一个空的 broadcastOn
方法已经被预定义好了,所以我们要做的仅仅是指定频道。我们希望只有订单的创建者能够看到状态更新,所以我们要把该事件广播到与这个订单绑定的私有频道上去:
/**
* Get the channels the event should broadcast on.
*
* @return array
*/
public function broadcastOn()
{
return new PrivateChannel('order.'.$this->update->order_id);
}
频道授权
记住,用户只有在被授权后才能监听私有频道。我们可以在 routes/channels.php
文件中定义频道的授权规则。在本例中,我们需要对试图监听私有 order.1
频道的所有用户进行验证,确保只有订单的创建者才能进行监听:
Broadcast::channel('order.{orderId}', function ($user, $orderId) {
return $user->id === Order::findOrNew($orderId)->user_id;
});
channel
方法接收两个参数:频道名称和一个回调函数,该回调通过返回 true
或者 false
来表示用户是否被授权监听该频道。
所有的授权回调接收当前被认证的用户作为第一个参数,任何额外的通配符参数作为后续参数。在本例中,我们使用 {orderId}
占位符来表示频道名称的「ID」部分是通配符。
对事件广播进行监听
接下来,就只剩下在 JavaScript 应用程序中监听事件了。我们可以使用 Laravel Echo 来实现。首先,使用 private
方法来订阅私有频道。然后,使用 listen
方法来监听 ShippingStatusUpdated
事件。默认情况下,事件的所有公有属性会被包括在广播事件中:
Echo.private(`order.${orderId}`)
.listen('ShippingStatusUpdated', (e) => {
console.log(e.update);
});