1. 程式人生 > >laravel5.5事件廣播系統

laravel5.5事件廣播系統

並且 ref cookie 回調 method provider trac chan ron

[toc]

1. 定義廣播事件

要告知 Laravel 一個給定的事件是廣播類型,只需在事件類中實現 Illuminate\Contracts\Broadcasting\ShouldBroadcast 接口即可。

ShouldBroadcast 接口要求你實現一個方法:broadcastOn. 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);
    }
}

1.1 廣播名稱

Laravel 默認會使用事件的類名作為廣播名稱來廣播事件。不過,你也可以在事件類中通過定義一個 broadcastAs 方法來自定義廣播名稱:

public function broadcastAs()
{
    return 'server.created';
}

如果您使用 broadcastAs 方法自定義廣播名稱,你需要在你使用訂閱事件的時候為事件類加上 . 前綴。這將指示 Echo 不要將應用程序的命名空間添加到事件中:

.listen('.server.created', function (e) {
    ....
});

1.2 廣播數據

當一個事件被廣播時,它所有的 public 屬性會自動被序列化為廣播數據,舉個例子,如果你的事件有一個公有的 $user 屬性,它包含了一個 Elouqent 模型,那麽事件的廣播數據會是:

{
    "user": {
        "id": 1,
        "name": "Patrick Stewart"
        ...
    }
}

然而,如果你想更細粒度地控制你的廣播數據,你可以添加一個 broadcastWith 方法到你的事件中。這個方法應該返回一個數組,該數組中的數據會被添加到廣播數據中,如果使用此方法,那麽public屬性不會被自動序列化為廣播數據。


public function broadcastWith()
{
    return ['id' => $this->user->id];
}

1.3 廣播隊列

默認情況下,每一個廣播事件都被添加到默認的隊列上,默認的隊列連接在 queue.php 配置文件中指定。你可以通過在事件類中定義一個 broadcastQueue 屬性來自定義廣播器使用的隊列。該屬性用於指定廣播使用的隊列名稱:

public $broadcastQueue = 'your-queue-name';

1.4 廣播條件

有時,你想在給定條件為 true ,才廣播你的事件。你可以通過在事件類中添加一個 broadcastWhen 方法來定義這些條件:

public function broadcastWhen()
{
    return $this->value > 100;
}

2. 頻道授權

打開服務提供器 app/Providers/BroadcastServiceProvider.php

public function boot()
{   
    Broadcast::routes();

    require base_path('routes/channels.php');
}

發現boot()方法做了兩件事,定義授權路由 和 定義授權回調

2.1 定義授權路由

對於私有頻道,用戶只有被授權後才能監聽,這如何進行判定呢?

在使用 Laravel Echo 時,laravel-echo會自動向你的 Laravel 應用程序發起一個攜帶頻道名稱的 HTTP 請求,你的應用程序判斷該用戶是否能夠監聽該頻道,所以要定義一個檢測授權是否合法的路由。

這個路由就是通過boot()方法中的 Broadcast::routes 註冊的,Broadcast::routes 方法會自動把它的路由放進 web 中間件組中

php artisan route:list
+--------+----------------------------------------+--------------------------------+-----------------------+-----------------------------------------------------------+-----------------+
| Domain | Method                                 | URI                            | Name                  | Action                                                    | Middleware      |
+--------+----------------------------------------+--------------------------------+-----------------------+-----------------------------------------------------------+-----------------+

|        | POST                                   | broadcasting/auth              |                       | \Illuminate\Broadcasting\BroadcastController@authenticate | web             |

2.2 定義授權回調

routes/channels.php

Broadcast::channel('order.{orderId}', function ($user, $orderId) {
    return $user->id === Order::findOrNew($orderId)->user_id;
});

在這裏我們需要定義真正用於處理頻道授權的邏輯,channel 方法接收兩個參數:頻道名稱和一個回調函數,該回調通過返回 true 或 false 來表示用戶是否被授權監聽該頻道。

所有的授權回調接收當前被認證的用戶作為第一個參數,任何額外的通配符參數作為後續參數。

  • 可以利用顯示或者隱式 路由模型 綁定
use App\Order;

Broadcast::channel('order.{order}', function ($user, Order $order) {
    return $user->id === $order->user_id;
});

3. 對事件進行廣播

3.1 可以使用event()方法觸發

event(new ShippingStatusUpdated($update));

3.2 也可以使用broadcast()輔助函數

broadcast(new ShippingStatusUpdated($update));

//不同的是 broadcast 函數有一個 toOthers 方法允許你將當前用戶從廣播接收者中排除:
broadcast(new ShippingStatusUpdated($update))->toOthers();

3.3 只廣播給他人

當你實例化 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的源碼之後,發現修改Echo.connector.options的頭信息應該可以完成功能,但是遇到一個坑,就是Echo.socketId()的獲取會有延遲,一開始就直接拿會undefined,以下是我測試的解決方案

setTimeout(function() { 
    Echo.connector.options.auth.headers['X-Socket-ID'] = Echo.socketId();
}, 2000);

setTimeout(function() { 
    var orderId = 1
    Echo.private('order.' + orderId)
        .listen('TestPrivateEvent', (e) => {
            console.log(e);
        });
}, 3000);

上面的路由信息知道 驗證方法在 \Illuminate\Broadcasting\BroadcastController@authenticate,我們找到文件打下日誌信息,查看頭部信息是否有socketId

public function authenticate(Request $request)
{   
    info(json_encode($request->header()));
    return Broadcast::auth($request);
}

日誌信息如下

{
    "x-requested-with": [
        "XMLHttpRequest"
    ], 
    "cookie": [
        ......
    ], 
    "x-socket-id": [
        "2lZruXuFAFDeK6tKAABB"
    ], 
    "x-csrf-token": [
        "eci68phBwGXOjHJVNXslx6l39S9WXshO2KGdMN2a"
    ]
    
    ...
}

後端可以拿到x-socket-id信息,但是感覺不是太好,有更好的方法大家可以交流。

4. 接受廣播

4.1 安裝 Laravel Echo

Laravel Echo 是一個 JavaScript 庫,它使得訂閱頻道和監聽由 Laravel 廣播的事件變得非常容易。你可以通過 NPM 包管理器來安裝 Echo。僅討論使用redis的情況

npm install laravel-echo --save  # 安裝laravel-echo 並記錄package.json

4.2 創建一個全新的 Echo 實例

官方說法是在resources/assets/js/bootstrap.js文件底部引入是個好地方

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

但是如果使用傳統的blade模板,沒有使用vue等前端,打包後發現#app未定義,並且會打包進去vue等我們不需要的內容,文件也會變大,

所以我修改resource/assets/js/app.js,直接打包我們需要的內容

import Echo from "laravel-echo"

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

4.3 使用laravel-mix打包

修改 webpack.mix.js

let mix = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js');
//   .sass('resources/assets/sass/app.scss', 'public/css');

生成

npm run dev

這樣我們就得到了一個壓縮的public/app.js文件

4.4 使用Echo實例監聽

4.4.1 基本用法

Laravel Echo 會需要訪問當前會話的 CSRF 令牌,可以在應用程序的 head HTML 元素中定義一個 meta 標簽:

<meta name="csrf-token" content="{{ csrf_token() }}">

引入js文件

// 引入Socket.IO JavaScript 客戶端庫
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>

//實例化Echo
<script src="/js/app.js"></script>
<script>
    // 上面app.js已經進行了Echo的實例化,然後應該使用實例化的Echo進行廣播事件的監聽
    Echo.channel('orders')
    .listen('OrderShipped', (e) => {
        console.log(e.order.name);
    });
</script>

4.4.2 監聽一個私有頻道

方法

Echo.private(`order.${orderId}`)
    .listen('ShippingStatusUpdated', (e) => {
        console.log(e.update);
    });

官方文檔可能說的不是太清楚,實際${orderId}是個占位符,你在實際使用的時候可能需要根據實際情況獲取到這個值,比如

var order_id = { $order_id }

Echo.private('order.' + order_id)
    .listen('ShippingStatusUpdated', (e) => {
        console.log(e.update);
    });

4.4.3 鏈式調用監聽一個頻道上多個事件

Echo.private('orders')
    .listen(...)
    .listen(...)
    .listen(...);

4.5 退出頻道

Echo.leave('orders');

4.6 命名空間

Echo 會自動認為事件在 App\Events 命名空間下。你可以在實例化 Echo 的時候傳遞一個 namespace 配置選項來指定根命名空間:

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'your-pusher-key',
    namespace: 'App.Other.Namespace'
});

另外,你也可以在使用 Echo 訂閱事件的時候為事件類加上 . 前綴。這時需要填寫完全限定名稱的類名:

Echo.channel('orders')
    .listen('.Namespace.Event.Class', (e) => {
        //
    });

5 Presence 頻道

Presence 頻道是在私有頻道的安全性基礎上,額外暴露出有哪些人訂閱了該頻道。這使得它可以很容易地建立強大的、協同的應用,如當有一個用戶在瀏覽頁面時,通知其他正在瀏覽相同頁面的用戶。

5.1 授權 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];
    }
});

5.2 加入 Presence 頻道

你可以用 Echo 的 join 方法來加入 presence 頻道。join 方法會返回一個實現了 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 會在其他用戶退出頻道時被執行。

5.3 廣播到 Presence 頻道

Presence 頻道可以像公開和私有頻道一樣接收事件。使用一個聊天室的例子,我們要把 NewMessage 事件廣播到聊天室的 presence 頻道。要實現它,我們將從事件的 broadcastOn 方法中返回一個 PresenceChannel 實例:

public function broadcastOn()
{
    return new PresenceChannel('room.'.$this->message->room_id);
}

和公開或私有事件一樣,presence 頻道事件也能使用 broadcast 函數來廣播。同樣的,你還能用 toOthers 方法將當前用戶從廣播接收者中排除:

broadcast(new NewMessage($message));

broadcast(new NewMessage($message))->toOthers();

你可以通過 Echo 的 listen 方法來監聽 join 事件:

Echo.join(`chat.${roomId}`)
    .here(...)
    .joining(...)
    .leaving(...)
    .listen('NewMessage', (e) => {
        //
    });

6. 客戶端事件

有時候你可能希望廣播一個事件給其他已經連接的客戶端,但並不需要通知你的 Laravel 程序。試想一下當你想提醒用戶,另外一個用戶正在輸入中的時候,顯而易見客服端事件對於「輸入中」之類的通知就顯得非常有用了。你可以使用 Echo 的 whisper 方法來廣播客戶端事件:

Echo.channel('chat')
    .whisper('typing', {
        name: this.user.name
    });

你可以使用 listenForWhisper 方法來監聽客戶端事件:

Echo.channel('chat')
    .listenForWhisper('typing', (e) => {
        console.log(e.name);
    });

laravel5.5事件廣播系統