Laravel框架整合眾多開發包的而功能, 雖然它的東西很多,但是它確是一個很好的框架。接下來的這篇文章將基於 Laravel 5.6 版本為大家講述如何使用內建的 Broadcast(廣播)功能實現與客戶端即時通訊。
1、準備
廣播系統
使用者認證
事件系統
佇列系統
前端指南
tlaverdure/laravel-echo-server
沒錯,這是你需要的知識儲備。
因為 PHP 本身不支援 WebSocket,所以我們需要一個能夠將「伺服器」資料發給「客戶端」的間接層。也就是說,實作即時通訊可以大致分為兩個步驟:
「Laravel」-> 「間接層」
「間接層」->(via WebSocket)-> ;「客戶端」
至於間接層我們採用什麼實現,後面再說。
2、配置
根據如上廣播系統的文檔,我們首先需要做如下的設定工作。
(1)首先,修改 config/broadcasting.php 或 .env 檔案。確保 Broadcast Default Driver 是 log,以開啟此功能,且方便我們除錯。
(2)使用 Broadcast 廣播,必須了解 Laravel 的事件系統,它們是相互依賴的。接下來我們創建一個可以「被廣播」的事件。
修改app/Providers/EventServiceProvider.php,在成員變數$listen內追加:
'App\Events\OrderShipped' => [ 'App\Listeners\SendShipmentNotification', ],
這裡我們將此事件命名為OrderShipped(訂單已結算);執行php artisan event:generate產生事件類別及其監聽器。
若是需要將事件執行(即廣播到客戶端)修改為非同步,請參考如上佇列系統文件。
(3)為了能讓事件「被廣播」,我們需要讓事件類別繼承 ShouldBroadcast 介面。
開啟app/Events/OrderShipped.php,修改類別定義為:
class OrderShipped implements ShouldBroadcast
(4)ShouldBroadcast 介面要求實作broadcastOn 方法,用於告知框架:此事件應該被傳送到哪個“頻道」。
Laravel 的廣播系統允許有多個頻道存在,你可以使用用戶名區別不同頻道,這樣不同的用戶手痛不同頻道,即可獲得不同訊息,也就能實現和不同客戶端進行單獨通信。
當然,你也可以任意命名頻道,但最好具有有一定規則。
因為我們剛剛使用 Artisan 指令產生的事件程式碼,所以在檔案最下方,你已經能夠看到 broadcastOn 方法的定義了。我們稍作修改:
public function broadcastOn() { return new Channel('orderStatus'); }
這裡我們將頻道命名為:orderStatus,並回傳。也就是說此事件被廣播時,它將會被廣播到名字為 orderStatus 的頻道。
這是一個「公有頻道」,任何人可以監聽此頻道,並收到廣播的訊息。 Laravel 同時提供了「私有頻道」,經過權限驗證後才能成功監聽。我們後面再說。
(5)預設情況下,Laravel 會將「事件名稱」作為廣播的「訊息名稱」,且不帶任何資料。我們可以在事件類別內新增任意成員變量,並修改建構函數,以實現將資料傳送給客戶端。
//可添加任意成员变量 public $id; //事件构造函数 public function __construct($id) { $this->id = $id; } //自定义广播的消息名 public function broadcastAs() { return 'anyName'; }
(6)如上,我們基本上已經建立起廣播的基本機制。接下來我們需要一個能「觸發事件」(即發送廣播)的介面。
在 routes/api.php 添加如下程式碼:
Route::get('/ship', function (Request $request) { $id = $request->input('id'); event(new OrderShipped($id)); // 触发事件 return Response::make('Order Shipped!'); });
(7)好了!開啟 Postman,輸入:http://***/api/ship?id=1000,發送。
開啟 storage/logs/laravel.log,你會發現多了幾行:
[2018-03-02 01:41:19] local.INFO: Broadcasting [App\Events\OrderShipped] on channels [orderStatus] with payload: { "id": "1000", "socket": null }
恭喜,你已經成功地設定好 Broadcast 的 log Driver。
3、廣播
在上一節,我們採用log 作為Broadcast Driver,也就是說廣播的訊息會被記錄到日誌內,那麼如何真正地與客戶端進行通訊呢?這就需要用到最開始提到的「間接層」了。
(1)首先,將 Driver 由 log 修改為 pusher。
為了節省安裝 Redis 的步驟,我們將採用 HTTP 協定直推至相容的「本機 Pusher 伺服器」(即間接層)。
(2)由於 Laravel 內建並沒有攜帶 Broadcast Pusher Driver,因此需要使用 Composer 安裝 Pusher PHP SDK:
composer require pusher/pusher-php-server
(3)註冊 App\Providers\BroadcastServiceProvider。
對於 Laravel 5.6,只需要取消 config/app.php 內 providers 陣列中的對應註解即可。
(4)接下來,需要安裝並設定伺服器與客戶端通訊的「間接層」。
我們採用官方文件推薦的:tlaverdure/laravel-echo-server。這是一個使用 Node.js Socket.IO 實作的 WebSocket 服務端。
它相容於 Pusher HTTP API,所以如上能夠直接將 Driver 修改為 Pusher,而不是 Redis。
npm install -g laravel-echo-server
初始化設定文件,按照提示填寫即可。
laravel-echo-server init
開啟 laravel-echo-server.json,檢查部分關鍵配置項目是否正確:
"authHost": "http://xxx" // 确保能够访问到你的 Laravel 项目 "port": "6001" // 建议不作修改,这是与客户端通信的端口 "protocol": "http" // 与客户端通信的协议,支持 HTTPS
复制两个值:clients.appId 以及 clients.key,并修改 config/broadcasting.php。
'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => null, 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'host' => 'localhost', 'port' => 6001, ], ],
顾名思义,将 appId 和 key 分别填入相应配置项或修改 .env 文件即可。
接下来,我们便可以使用命令 laravel-echo-server start 来启动服务器,监听来自 Laravel 的「广播」请求、以及来自客户端的「收听」请求,并转发相应广播消息。
(5)从这里开始我们将会配置前端部分。
首先,需要配置 CSRF Token。
如果你使用 Blade 模板引擎,则执行 php artisan make:auth 命令即可将 Token 加入 resources/views/layouts/app.blade.php。
若没有使用此模板文件,则可以直接在 Blade 模板文件首行直接写入 Meta 值。
为了便于测试,我们在 resources/views/welcome.blade.php 内添加:
<meta name="csrf-token" content="{{ csrf_token() }}">
对于前后端分离的项目,可关闭 CSRF Token 验证。
(6)其次,你需要引用 Socket.IO 的客户端 JS 文件,位置同上。
<script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
(7)这里我们采用官方的 Laravel Echo 扩展包收听服务端广播。打开 resources/assets/js/bootstrap.js,修改最后几行即可。
import Echo from 'laravel-echo'
window.Echo = new Echo({ broadcaster: 'socket.io', host: window.location.hostname + ':6001' });
接着编写「收听频道」代码,非常简单:
Echo.channel(`orderStatus`) // 广播频道名称 .listen('OrderShipped', (e) => { // 消息名称 console.log(e); // 收到消息进行的操作,参数 e 为所携带的数据 });
(8)安装依赖,编译前端文件。执行如下命令:
npm install npm run dev
最后,在 Blade 模板内引用我们刚刚写好的 JS 脚本。由于 app.js 默认已经引用 bootstrap.js,所以我们只需要引用 app.js 即可。
我们在第(5)步的文件内添加:
<script src="{{ asset('/js/app.js') }}"></script>
(9)好了!在查看效果前,不要忘记执行第(4)步的最后一条命令,启动 Laravel Echo Server。
Laravel 默认已经定义首页路由渲染 welcome.blade.php 模板,现在只要使用浏览器访问应用 URL 即可。
如果查看 Chrome 控制台,没有任何错误产生;查看命令行窗口,没有错误输出;则说明客户端与服务器似乎已经正常建立 WebSocket 连接。
这时,你可以重新打开Postman,发送上一节中的请求。
再次查看如上两个窗口,会有惊喜哟。
4、私有频道
上一节我们成功实现可以被任何人收听的「共有频道」广播,那么如何与部分客户端进行通讯呢?仅仅依靠前端的验证是不够的。我们需要创建带有「认证」功能的「私有频道」。
(1)首先,打开 app/Providers/BroadcastServiceProvider.php,在上一节中我们已经注册此服务提供者,现在我们需要取消注释一部分代码。
public function boot() { Broadcast::routes(); // 还记得 laravel-echo-server.json 的 authEndpoint 配置项吗? require base_path('routes/channels.php'); }
Broadcast::routes() 用于注册验证路由(即 /broadcasting/auth),当客户端收听频道时,Laravel Echo Server 会访问此路由,以验证客户端是否符合「认证」条件。
(2)Broadcast 认证分为两个部分:
使用 Laravel 内置的 Auth 系统进行认证。
根据自定义规则进行部分频道的认证。
(3)首先,需要配置 Auth 认证系统。
根据情况修改 .env 文件的数据库配置项后,只需要执行 php artisan make:auth 创建所需文件,再执行 php artisan migrate 创建所需数据库结构即可。
深入了解,请参考用户认证文档。
接下来我们在浏览器中打开你的应用,会发现右上角多了登录和注册,我们随意注册一个测试用户以备使用。
(4)接下来,配置频道认证。
还记得第(1)步的 routes/channels.php 吗,我们打开此文件。并新增一条频道认证规则。
注意:虽然此文件位于 routes 目录下,但并不是路由文件!在此定义后并不能访问,且无法使用分组、中间件。所有的验证路由都已经在 Broadcast::routes() 中定义。
Broadcast::channel('orderStatus', function ($user, $value) { return true; // or false });
由于 Broadcast 已经使用 Auth 进行用户登录认证,所以我们只需无条件返回 true 即可实现:任何已登录用户都可以收听此频道。
(5)认证部分我们已经配置完成,接下来将共有频道的定义改为私有。
修改广播消息基于的事件类 app/Events/OrderShipped.php:
public function broadcastOn() { return new PrivateChannel('orderStatus'); // 私有频道 }
修改客户端收听代码 resources/assets/js/bootstrap.js。
Echo.private(`orderStatus`) // 私有频道 .listen('OrderShipped', (e) => { console.log(e); });
(6)接下来,再次运行 Laravel Echo Server。使用浏览器打开你的应用首页,在未登录状态,可以看到 Echo Server 输出一个来自 Laravel 的 AccessDeniedHttpException 异常,提示用户认证失败,无法收听频道。
登录后,即可获得与上一节相同的预期结果。
(7)如上,我们成功实现所有登录用户均可收听私有频道。那么如何实现某部分用户可以收听,某部分用户不可以收听频道?例如:某个用户均有属于自己的频道,他只能收听自己的频道。请继续往下看。
首先,修改广播消息基于的事件类 app/Events/OrderShipped.php。你需要将频道命名修改为动态的值。
public $userId; // 新增成员变量 userId,不要忘记在构造函数内对其进行赋值
public function broadcastOn() { return new PrivateChannel('orderStatus-' . $this->userId); // 动态命名私有频道 }
其次,修改第(4)步中的 routes/channels.php 文件。Broadcast 支持使用通配符匹配「某一类」频道进行验证。
Broadcast::channel('orderStatus-{userId}', function ($user, $value) { // $user Auth认证通过的用户模型实例 // $value 频道规则匹配到的 userId 值 return $user->id == $value; // 可使用任意条件验证此用户是否可监听此频道 });
最后别忘记修改前端收听的频道名称。
再次打开浏览器测试即可发现:在本例中,若客户端收听的频道不匹配当前用户 ID,则会报错。
(8)如上,我们成功实现对私有频道进行自定义规则的认证;但如果我们没有使用 Auth 认证系统,或采用了自己编写的用户认证中间件,该如何兼容呢?
经过一番源码调试,我发现 Broadcast 在 vendor/laravel/framework/src/Illuminate/Broadcasting/Broadcasters/ 文件夹下定义的 Broadcaster 内调用 $request->user() 进行了用户验证。
例如我们采用的 PusherBroadcaster.php:
/** * Authenticate the incoming request for a given channel. * * @param \Illuminate\Http\Request $request * @return mixed * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ public function auth($request) { if (Str::startsWith($request->channel_name, ['private-', 'presence-']) && ! $request->user()) { throw new AccessDeniedHttpException; } $channelName = Str::startsWith($request->channel_name, 'private-') ? Str::replaceFirst('private-', '', $request->channel_name) : Str::replaceFirst('presence-', '', $request->channel_name); return parent::verifyUserCanAccessChannel( $request, $channelName ); }
由此可得,我们有两种方式实现。
第一种
直接注释 throw new AccessDeniedHttpException,并修改 app/Providers/BroadcastServiceProvider.php。
Broadcast::routes() 可接收一个参数。在 vendor/laravel/framework/src/Illuminate/Broadcasting/BroadcastManager.php 可查看其定义:
/** * Register the routes for handling broadcast authentication and sockets. * * @param array|null $attributes * @return void */ public function routes(array $attributes = null) { if ($this->app->routesAreCached()) { return; } $attributes = $attributes ?: ['middleware' => ['web']]; $this->app['router']->group($attributes, function ($router) { $router->post('/broadcasting/auth', '\\'.BroadcastController::class.'@authenticate'); }); }
通过源码可知:此参数等效于 Route::group() 的第一个参数。所以我们只要将其修改为如下形式:
Broadcast::routes(['middleware' => ['yourMiddleware'])
并在中间件内进行用户认证;如未登录,照常抛出 AccessDeniedHttpException 异常即可。
第二种
在 vendor/laravel/framework/src/Illuminate/Http/Request.php 可以查看到 $request->user() 的定义。
/** * Get the user making the request. * * @param string|null $guard * @return mixed */ public function user($guard = null) { return call_user_func($this->getUserResolver(), $guard); }
如上可知,它使用 $this->userResolver 内的匿名函数获取用户模型。所以我们只需要在AuthServiceProvider 注册后,Broadcast 认证前,替换掉其 userResolver 即可。
例如:继承 Illuminate\Auth\AuthServiceProvider(vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php),并重写 registerRequestRebindHandler 方法及构造函数,添加如下代码。
$request->setUserResolver(function ($guard = null) use ($app) { // 在此判断用户登录状态 // 若登录,请返回 App\User 或其它用户模型实例 // 未登录,请返回 null });
修改 config/app.php,使用改造过的 AuthServiceProvider 类替换原服务提供者即可。
5、总结
至此,你已经建立对 Laravel Boardcast 的基本认识,成功入门「广播系统」。
另外,Laravel 5.6 新增一条关于 Broadcast 的新特性,避免在 routes/channels.php 文件内编写众多闭包导致的难以维护。详情可查看:Laravel 5.6 新版特性。
其实,本文只不过抛砖引玉而已。对于部署到生产环境,仍然存在许多问题,例如:
与常见 PHP 应用不同,广播基于 WebSocket 长连接;那么如何确保通信稳定性,如何剔除死链?
为了确保应用访问速度,广播通常是异步执行的;那么如何配置事件队列?如何将队列配置为异步?
如何确保 Laravel Echo Server 进程稳定运行?如何处理异常?是否需要关闭 Debug 模式?
如何确保 Laravel Echo 与服务端连接稳定?是否需要定时检查?如何捕捉并完善地处理所有异常?
客户端收听私有频道,若被服务器拒绝,是否需要断开连接?如何给予用户友好地提示?
……
当然,这些问题本文没有提到,但网络上已经有无数成熟、便捷的解决方案;更多高级用法也可以参照本文最初列出的官方文档。
以上就是本篇文章的全部内容了,更多内容请关注laravel入门教程!
相关文章推荐:
Laravel框架实现发送短信验证功能代码,laravel发送短信
相关课程推荐:
#以上是Laravel框架內建的Broadcast功能如何實現與客戶端即時通訊的詳細內容。更多資訊請關注PHP中文網其他相關文章!