• laravel5.5事件广播系统


    1. 定义广播事件

    要告知 Laravel 一个给定的事件是广播类型,只需在事件类中实现 IlluminateContractsBroadcastingShouldBroadcast 接口即可。

    ShouldBroadcast 接口要求你实现一个方法:broadcastOn. broadcastOn 方法返回一个频道或一个频道数组,事件会被广播到这些频道。

    频道必须是 Channel、PrivateChannel 或 PresenceChannel 的实例。Channel 实例表示任何用户都可以订阅的公开频道,而 PrivateChannels 和 PresenceChannels 则表示需要 频道授权 的私有频道:

    <?php
    
    namespace AppEvents;
    
    use IlluminateBroadcastingChannel;
    use IlluminateQueueSerializesModels;
    use IlluminateBroadcastingPrivateChannel;
    use IlluminateBroadcastingPresenceChannel;
    use IlluminateBroadcastingInteractsWithSockets;
    use IlluminateContractsBroadcastingShouldBroadcast;
    
    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              |                       | IlluminateBroadcastingBroadcastController@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 AppOrder;
    
    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);
    

    上面的路由信息知道 验证方法在 IlluminateBroadcastingBroadcastController@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 会自动认为事件在 AppEvents 命名空间下。你可以在实例化 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);
        });
    
  • 相关阅读:
    循环队列
    UVa10000_Longest Paths(最短路SPFA)
    最新jhost免费jsp云空间会员邀请码
    Vertica: 基于DBMS架构的列存储数据仓库
    java中接口的定义与实现
    【C++知识汇总】运营商 &amp; 运算符重载
    SVN与eclipse整合和利用、SVN与Apache综合
    Java单链逆转
    hdu1115(重力算法的多边形中心)
    高效C++规划
  • 原文地址:https://www.cnblogs.com/redirect/p/8658789.html
Copyright © 2020-2023  润新知