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事件广播系统的更多相关文章

  1. laravel5.5事件广播系统实例laravel-echo + redis + socket.io

    目录 1. 广播配置说明 1.1 广播驱动配置 1.2 注册服务提供器 2. 驱动器配置 2.1 安装predis 2.2. 配置服务端 2.2.1 安装方法 2.2.2 初始化服务端 2.2.3 运 ...

  2. 初级知识六——C#事件通知系统实现(观察者模式运用)

    观察者模式,绝对是游戏中十分重要的一种模式,运用这种模式,可以让游戏模块间的通信变得简单,耦合度也会大大降低,下面讲解如何利用C#实现事件通知系统. 补充,首先说下这个系统的实现原理,不然一头扎进去就 ...

  3. 大型 JavaScript 应用架构中的模式

    原文:Patterns For Large-Scale JavaScript Application Architecture by @Addy Osmani 今天我们要讨论大型 JavaScript ...

  4. [转]大型 JavaScript 应用架构中的模式

    目录 1.我是谁,以及我为什么写这个主题 2.可以用140个字概述这篇文章吗? 3.究竟什么是“大型”JavaScript应用程序? 4.让我们回顾一下当前的架构 5.想得长远一些 6.头脑风暴 7. ...

  5. JNI详解---从不懂到理解

    转载:https://blog.csdn.net/hui12581/article/details/44832651 Chap1:JNI完全手册... 3 Chap2:JNI-百度百科... 11 C ...

  6. laravel5通过auth.attempt事件加入登陆验证码

    <?php namespace WangDong\Http\Controllers\Auth; use Illuminate\Http\Exception\HttpResponseExcepti ...

  7. Atitit  数据库的事件机制--触发器与定时任务attilax总结

    Atitit  数据库的事件机制--触发器与定时任务attilax总结 1.1. 事件机制的图谱1 2. 触发器的类型2 3. 实现原理 After触发器 Vs Instead Of触发器2 3.1. ...

  8. Laravel5.0学习--01 入门

    本文以laravel5.0.22为例. 生产环境建议使用laravel5.1版本,因为该版本是长期支持版本.5.1文档更详细:http://laravel-china.org/docs/5.1. 环境 ...

  9. Twisted 介绍 及TCP广播系统实例

    twisted 提供更多传输层 udp,tcp,tls及应用层HTTP,FTP等协议的支持,在开发方法上更提供了丰富的特性来支持异步编程 安装twisted 建议使用anaconda 安装,conda ...

随机推荐

  1. java之Socket多线程传递对象

    服务器端利用线程池回复客户端: public class Server implements Runnable { private final ServerSocket server; private ...

  2. Python基础学习之标识符

    1.合法的Python标识符 Python标识符字符串规则和其他大部分用C编写的高级语言相似: 第一个字符必须是字母或下划线(_) 剩下的字符可以是字母和数字或下滑线 大小写敏感 标识符不能以数字开头 ...

  3. java ——String , StringBuffer, StringBuilder类

    一.String类概述 1.String对象一旦创建就不能改变. 2.字符串常量池. 字符串常量池的特点:池中有则直接使用,池中没有则创建新的字符串常量. 例1: “==”  比较两个对象是否引用同一 ...

  4. 【转载】#437 - Access Interface Members through an Interface Variable

    Onece a class implementation a particular interface, you can interact with the members of the interf ...

  5. Treap 实现名次树

    在主流STL版本中,set,map,都是BST实现的,具体来说是一种称为红黑树的动态平衡BST: 但是在竞赛中并不常用,因为红黑树过于复杂,他的插入 5 种,删除 6 中,代码量极大(如果你要改板子的 ...

  6. Linux 初学者:移动文件

    你学习了有关目录和访问目录的权限是如何工作的.你在这些文章中学习的大多数内容都可应用于文件 -- Paul Brown 在之前的该系列的部分中, 你学习了有关目录 和 访问目录 的权限 是如何工作的. ...

  7. python中的for循环如何控制步长

    for i in range(开始/左边界, 结束/右边界, 步长): print i 例如 for i in range(1, 10, 2): print i 等价于 for (i=1;i<= ...

  8. mysql常用命令添加外键主键约束存储过程索引

    数据库连接 mysql -u root -p123456 查看表 show databases 创建数据库设置编码 create table books character set utf8; 创建用 ...

  9. 共变导数(Covariant Derivative)

    原文链接 导数是指某一点的导数表示了某点上指定函数的变化率. 比如,要确定某物体的速度在某时刻的加速度,就取时间轴上下一时刻的一个微小增量,然后考察速度的增量和时间增量的比值.如果这个比值比较大,说明 ...

  10. javascript中的作用域与作用域链

    前几天,在写一段js代码时,出现了一些问题,调了很长时间也没有调通,其原因是,我在处理变量的作用域时错误地沿用了C++的作用域机制.因此我回炉了一次. 如果你使用过C++或java等一系列的面向对象的 ...