廣播系統
廣播系統
簡介
在現代的web 應用程式中, WebSockets 被用來實現即時、即時更新的使用者介面。當伺服器上的資料更新後,更新資訊會透過 WebSocket 連線傳送到客戶端等待處理。相較於不停地輪詢應用程序,這是一種更可靠和高效的選擇。 為了幫助你建立這類應用, Laravel 將透過 WebSocket 連線來讓「廣播」 事件 變得更加輕鬆。廣播 Laravel 事件允許你在服務端和客戶端 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 令牌。你應該驗證你的應用程式的head
HTML 元素是否定義了包含CSRF 令牌的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/predisRedis 廣播器會使用Redis 的發布/ 訂閱功能來廣播訊息;儘管如此,你仍需將它與能夠從Redis 接收訊息的WebSocket 伺服器配對使用以便將訊息廣播到你的WebSocket 頻道上去。 當 Redis 廣播器發布一個事件的時候,該事件會被發佈到它指定的頻道上去,傳輸的資料是一個採用 JSON 編碼的字串。該字串包含了事件名稱、
data 資料和產生該事件 socket ID 的使用者(如果可用的話)。
Socket.IO
如果你想將 Redis 廣播器 和 Socket.IO 伺服器進行配對,你需要在你的應用程式中引入 Socket.IO JavaScript 用戶端程式庫。你可以透過 NPM 套件管理器進行安裝:
npm install --save socket.io-client
然後,你需要在實例化 Echo 時指定 socket.io
連接器和 host
。
import Echo from "laravel-echo"window.io = require('socket.io-client'); 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 將服務端的Laravel 事件廣播到用戶端的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{ /** * 有关配送状态更新的信息。 * * @var string */ public $update; }
ShouldBroadcast
介面要求事件定義一個 broadcastOn
方法。此方法負責指定事件被廣播到哪些頻道。在(透過 Artisan 指令)產生的事件類別中,一個空的 broadcastOn
方法已經被預先定義好了,所以我們只需要完成其細節。我們希望只有訂單的建立者能夠看到狀態的更新,所以我們要把該事件廣播到與這個訂單綁定的私有頻道上去:
/** * 获取事件应该广播的频道。 * * @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” 部分是萬用字元。
private 方法來訂閱私有頻道。然後,使用
listen 方法來監聽
ShippingStatusUpdated 事件。預設情況下,事件的所有公有屬性會被包含在廣播事件中:
Echo.private(`order.${orderId}`) .listen('ShippingStatusUpdated', (e) => { console.log(e.update); });
定義廣播事件
要告知 Laravel 一個給定的事件需要廣播,只需要在事件類別中實作 Illuminate\Contracts\Broadcasting\ShouldBroadcast
介面即可。這個介面已被匯入到所有由框架產生的事件類別中,所以你可以很方便地將它新增到你自己的事件中。
ShouldBroadcast
介面要求你實作一個方法: broadcastOn
。此方法傳回一個頻道或一個頻道數組,事件會被廣播到這些頻道。這些頻道必須是 Channel
、PrivateChannel
或 PresenceChannel
的實例。 Channel
代表任何使用者都可以訂閱的公開頻道, 而PrivateChannels
和PresenceChannels
則代表需要頻道授權 的私人頻道:
<?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 ServerCreated implements ShouldBroadcast{ use SerializesModels; public $user; /** * 创建一个新的事件实例。 * * @return void */ public function __construct(User $user) { $this->user = $user; } /** *获得事件广播的频道。 * * @return Channel|array */ public function broadcastOn() { return new PrivateChannel('user.'.$this->user->id); } }
然後,你只需要像你平常那樣觸發事件。一旦事件被觸發,一個 佇列任務 會自動廣播事件到你指定的廣播驅動程式。
廣播名稱
Laravel 預設會使用事件的類別名稱作為廣播名稱來廣播事件。不過,你也可以在事件類別中定義一個broadcastAs
方法來自訂廣播名稱:
/** * 事件的广播名称。 * * @return string */ public function broadcastAs(){ return 'server.created'; }
如果你使用了broadcastAs
方法來自訂廣播名稱,你應當確保在你註冊監聽器時加上一個.
的前綴。這將指示Echo 不要在事件之前新增應用程式的命名空間:
.listen('.server.created', function (e) { .... });
廣播資料
當一個當事件被廣播時,其所有的public
屬性都會自動序列化並作為事件有效載荷進行廣播,這允許你在JavaScript 應用程式中存取到事件所有的公有資料。舉個例子,如果你的事件有一個單獨的包含了一個Eloquent 模型的公有$user
屬性,那麼事件的廣播有效載荷將會是:
{ "user": { "id": 1, "name": "Patrick Stewart" ... } }
不過,如果你想更細緻地控制你的廣播有效載荷,你可以在你的事件中添加一個broadcastWith
方法。這個方法會回傳一個你想要作為事件有效載荷進行廣播的資料數組:
/** * 指定广播数据。 * * @return array */ public function broadcastWith(){ return ['id' => $this->user->id]; }#
廣播佇列
預設情況下,每個廣播事件都會被推送到在 queue.php
設定檔中指定的預設佇列連接對應的預設佇列中。你可以在事件類別中定義一個 broadcastQueue
屬性來自訂廣播器所使用的佇列。這個屬性需要你指定廣播時你想要用的佇列名稱:
/** * 事件被推送到的队列名称。 * * @var string */ public $broadcastQueue = 'your-queue-name';
如果你想使用sync
佇列而不是預設佇列驅動程式來廣播事件,你可以實作ShouldBroadcastNow
介面而不是ShouldBroadcast
:
<?php use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow; class ShippingStatusUpdated implements ShouldBroadcastNow{ // }
廣播條件
有時,你想在給定條件為true 的情況下才廣播你的事件。你可以透過在事件類別中新增一個broadcastWhen
方法來定義這些條件:
/** * 判定事件是否可以广播。 * * @return bool */ public function broadcastWhen(){ return $this->value > 100; }##授權頻道對於私有頻道,使用者只有被授權之後才能監聽。實現過程是使用者向你的 Laravel 應用程式發起一個攜帶頻道名稱的 HTTP 請求,由你的應用程式判斷該使用者是否能夠監聽該頻道。在使用
Laravel Echo 時,授權訂閱私有頻道的 HTTP 請求會自動傳送;儘管如此,你仍需定義對應的路由來回應這些要求。
定義授權路由#幸運的是,在Laravel 中我們可以輕鬆定義路由來回應頻道授權請求。在 Laravel 自帶的BroadcastServiceProvider 中,你可以看到對
Broadcast::routes 方法的呼叫。此方法會註冊
/broadcasting/auth 路由來處理授權請求:
Broadcast::routes();
Broadcast::routes 方法會自動將它的路由置入
web 中間件組中;不過,如果你想自訂指定的屬性,你可以向該方法傳遞一個路由屬性數組:
Broadcast::routes($attributes);自訂授權端點
預設情況下,Echo 將使用
/broadcasting/auth 端點來授權頻道存取。但是,您可以透過將
authEndpoint 設定選項傳遞給 Echo 實例來指定自己的授權端點:
window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key', authEndpoint: '/custom/endpoint/auth' });##
定義授權回呼
接下來,我們需要定義真正用來處理頻道授權的邏輯。邏輯在應用程式自帶的 routes/channels.php
檔案中完成。在這個檔案中,你可以使用Broadcast::channel
方法來註冊頻道授權回呼:
Broadcast::channel('order.{orderId}', function ($user, $orderId) { return $user->id === Order::findOrNew($orderId)->user_id; });
channel
方法接收兩個參數:頻道名稱和一個回調函數,該回呼透過傳回true
或false
來表示使用者是否被授權監聽該頻道。
所有的授權回呼接收目前認證使用者作為第一個參數,任何額外的通配符參數作為後續參數。在本例中,我們使用 {orderId}
佔位符來表示頻道名稱的 “ID” 部分是萬用字元。
授權回呼模型綁定
就像 HTTP 路由一樣,頻道路由也可以利用明確或隱含 路由模型綁定 。例如,你可以請求接收一個真正的Order
模型實例,而不是字串或數字類型的order ID:
use App\Order; Broadcast::channel('order.{order}', function ($user, Order $order) { return $user->id === $order->user_id; });##授權回呼驗證私人頻道和線上廣播頻道透過應用程式的預設授權驗證對目前使用者身份進行驗證。如果使用者未經過授權驗證,則會自動拒絕通道授權,並且永遠不會執行授權回呼。但是,您可以指派多個自訂防護,以便在必要時對傳入請求進行驗證:
Broadcast::channel('channel', function() { // ... }, ['guards' => ['web', 'admin']])定義頻道類別如果你的應用程式用到了許多不同的頻道,你的
routes/channels.php 檔案可能會變得很龐大。所以,你可以使用頻道類別來取代使用閉包授權頻道。若要產生頻道類,請使用
make:channel Artisan 指令。指令會在
App/Broadcasting 目錄中放置一個新的頻道類別。
php artisan make:channel OrderChannel接下來,在你的
routes/channels.php 檔案中註冊你的頻道:
use App\Broadcasting\OrderChannel; Broadcast::channel('order.{order}', OrderChannel::class);最後,你可以將頻道的授權邏輯放入頻道類別的
join 方法中。該
join 方法將保存你通常放置在頻道授權閉包中的相同邏輯。當然,你也可以利用頻道模型綁定:
<?php namespace App\Broadcasting; use App\User;use App\Order; class OrderChannel{ /** * 创建一个新的频道实例。 * * @return void */ public function __construct() { // } /** * 认证用户的频道访问权限。 * * @param \App\User $user * @param \App\Order $order * @return array|bool */ public function join(User $user, Order $order) { return $user->id === $order->user_id; } }
{注} 就像 Laravel 中的許多其它類,頻道類會透過 服務容器 自動解析。因此,你可以在頻道類別的建構函式中對其進行所需依賴項的型別提示。
廣播事件
定義好一個事件且將其標記實作 ShouldBroadcast
介面之後,你所要做的只是透過 event
函數來觸發該事件。事件分發器會辨識出標記了實作ShouldBroadcast
介面的事件,並將其推送到佇列中進行廣播:
event(new ShippingStatusUpdated($update));
只廣播給他人
當建立一個會用到事件廣播的應用程式時,你可以使用 broadcast
函數來取代 event
。和event
函數一樣, broadcast
函數將事件分發到服務端監聽器:
broadcast(new ShippingStatusUpdated($update));
不過, broadcast
函數還有一個允許你將目前使用者排除在廣播接收者之外的toOthers
方法:
broadcast(new ShippingStatusUpdated($update))->toOthers();
為了更好地理解什麼時候使用toOthers
方法,讓我們假設有一個任務清單的應用程序,使用者可以透過輸入任務名稱來新建任務。要新建任務,你的應用程式需要發起一個請求到一個 /task
路由,該路由會廣播任務的創建,並傳回新任務的 JSON 回應。當你的 JavaScript 應用程式從路由收到回應後,它會直接將新任務插入任務清單中,就像這樣:
axios.post('/task', task) .then((response) => { this.tasks.push(respo });
然而,別忘了,我們也廣播了任務的創建。如果你的 JavaScript 應用程式正在監聽該事件以便添加任務至任務列表,任務列表中將出現重複的任務:一個來自路由回應,另一個來自廣播。你可以透過使用 toOthers
方法告知廣播器不要將事件廣播到目前使用者來解決這個問題。
{註} 為了能呼叫
toOthers
方法,你的事件必須使用Illuminate\Broadcasting\InteractsWithSockets
trait 。設定
當你初始化 Laravel Echo 實例的時候,一個套接字 ID 會被指派到該連線。如果你使用了Vue 和Axios ,該套接字ID 會自動地以X-Socket-ID
頭的方式新增到每一個傳出請求中。那麼,當你呼叫 toOthers
方法時,Laravel 會從請求頭中取出套接字 ID ,並告知廣播器不要廣播任何訊息到帶有這個套接字 ID 的連接上。
你如果你沒有使用 Vue 和 Axios ,則需要手動設定 JavaScript 應用程式來傳送 X-Socket-ID
請求頭。你可以用Echo.socketId
方法來取得套接字ID :
var socketId = Echo.socketId();##接收廣播
安裝 Laravel Echo
Laravel Echo 是一個 JavaScript 函式庫,有了這個函式庫之後,訂閱頻道監聽 Laravel 廣播的事件變得非常容易。你可以透過 NPM 套件管理器來安裝 Echo 。在這個例子中,因為我們會使用Pusher 廣播器,所以我們也會安裝pusher-js
套件:
npm install --save laravel-echo pusher-js
安裝好Echo 之後,你就可以在應用程式的JavaScript 中建立一個全新的Echo 實例。做這件事的一個理想的地方是在Laravel 框架自帶的resources/js/bootstrap.js
檔案的底部:
import Echo from "laravel-echo" window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key' });
當你使用pusher
連接器來建立一個Echo 實例的時候,也可以指定cluster
以及連線是否需要加密:
window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key', cluster: 'eu', encrypted: true });##使用現有客戶端實例如果您已經有一個希望Echo 使用的Pusher 或Socket.io 用戶端實例,您可以透過
client 設定選項將其傳遞給Echo:
const client = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'pusher', key: 'your-pusher-key', client: client });#對事件進行監聽安裝並實例化Echo 之後, 你就可以開始監聽事件廣播了。首先,使用
channel 方法取得頻道實例,然後呼叫
listen 方法來監聽指定的事件:
Echo.channel('orders') .listen('OrderShipped', (e) => { console.log(e.order.name); });如果你想監聽私有頻道上的事件,請使用
private 方法。你可以透過鍊式呼叫
listen 方法來監聽單一頻道上的多個事件:
Echo.private('orders') .listen(...) .listen(...) .listen(...);
要離開頻道,您可以在Echo 實例上呼叫
leaveChannel 方法:
如果您想離開私人頻道和線上頻道,您可以呼叫Echo.leaveChannel('orders');
方法:Echo.leave('orders');
你可能已經注意到在上面的例子中,我們並沒有為事件類別指定完整的命名空間。這是因為 Echo 會預設事件都在
App\Events 命名空間下。不過,你可以在實例化Echo 時傳遞一個namespace
配置項目來指定根命名空間:
另外,你可以在使用Echo 訂閱事件的時候為事件類別加上window.Echo = new Echo({
broadcaster: 'pusher',
key: 'your-pusher-key',
namespace: 'App.Other.Namespace'
});
前綴。這樣就可以指定完全限定名稱的類別名稱了:Echo.channel('orders')
.listen('.Namespace.Event.Class', (e) => {
//
});
Presence 頻道建構在私有在頻道的安全性基礎之上,並提供了額外的特性:獲知誰訂閱了該頻道。這一點使建立強大的,協同的應用程式變得非常容易,例如一個使用者在瀏覽頁面時,通知其他正在瀏覽相同頁面的使用者。
授權 Presence 頻道
所有的 presence 頻道也是私有頻道;因此,使用者必須被 授權之後才能存取 。不過,在為 presence 頻道定義授權回呼函數時,如果一個使用者已經加入了該頻道,那麼不應該傳回 true
,而應該傳回一個關於該使用者資訊的陣列。
由授權回呼函數傳回的資料能夠在你的 JavaScript 應用程式中被 presence 頻道事件監聽器所使用。如果使用者沒有被授權加入該presence 頻道,那麼你應該回傳false
或null
:
Broadcast::channel('chat.{roomId}', function ($user, $roomId) { if ($user->canJoinRoom($roomId)) { return ['id' => $user->id, 'name' => $user->name]; } });
加入Presence 頻道
你可以使用Echo 的
join
方法來加入presence 頻道。
方法會回傳一個實作了PresenceChannel
的對象,透過揭露listen
方法,讓你訂閱here
、
joining 和leaving
Echo.join(`chat.${roomId}`) .here((users) => { // }) .joining((user) => { console.log(user.name); }) .leaving((user) => { console.log(user.name); });
here
回呼函數會在你成功加入頻道後被立即執行,並接收一個包含其他所有目前訂閱該頻道的使用者的使用者資訊陣列 。joining 方法會在新使用者加入頻道時被執行,而
leaving 方法會在使用者登出頻道時執行。
廣播到 Presence 頻道
Presence 頻道可以像公開和私人頻道一樣接收事件。使用一個聊天室的例子,我們可能會想把
事件廣播到聊天室的 presence 頻道。要實現它,我們將從事件的broadcastOn
方法中返回一個
實例:/**
* 获得事件广播的频道。
*
* @return Channel|array
*/
public function broadcastOn(){
return new PresenceChannel('room.'.$this->message->room_id);
}
就像公開或私有事件, presence 頻道事件也可以使用
broadcast(new NewMessage($message)); broadcast(new NewMessage($message))->toOthers();###你可以透過Echo 的###listen### 方法來監聽join 事件:###
Echo.join(`chat.${roomId}`) .here(...) .joining(...) .leaving(...) .listen('NewMessage', (e) => { // });###################
客戶端事件
有時,你可能會想要廣播一個事件給其它已經連接的客戶端,但不通知你的 Laravel 應用程式。這在處理「輸入中」這類事情的通知時尤其有用,例如提醒你已應用的用戶,另一個用戶正在給定螢幕上輸入資訊。
你可以使用Echo 的whisper
方法來廣播客戶端事件:
Echo.private('chat') .whisper('typing', { name: this.user.name });
你可以使用listenForWhisper
方法來監聽客戶端事件:
Echo.private('chat') .listenForWhisper('typing', (e) => { console.log(e.name); });
訊息通知
#透過與將事件廣播與訊息通知配對,你的JavaScript 應用程式可以在不刷新頁面的情況下接收新的訊息通知。在此之前,請確保你已經讀過如何使用 廣播通知頻道 的文檔。
設定好使用廣播頻道的訊息通知後,你可以使用 Echo 的 notification
方法來監聽廣播事件。謹記,頻道名稱應該和接收訊息通知的實體類別名稱相符:
Echo.private(`App.User.${userId}`) .notification((notification) => { console.log(notification.type); });
在本例中,所有透過broadcast
頻道傳送到App\User
#實例的訊息通知都會被回調接收。一個針對 App.User.{id}
頻道的授權回呼函數已經包含在 Laravel 框架內建的 BroadcastServiceProvider
中了。