> composer create-project --prefer-dist laravel/laravel joker
> cd joker
> php artisan key:generate
Application key set successfully.
> php artisan --version
Laravel Framework 8.34.0
laravel-websockets 是一款封裝好了 websocket 服務的軟體包。其特點是透過攔截 pusher 的請求來擺脫對 pusher 服務商(國外)的依賴。還具有除錯皮膚,實時統計資訊,甚至允許您建立自定義 WebSocket 控制器等能力。以下執行了兩步操作,首先引入軟體包,然後釋出軟體包的配置檔案和資料庫遷移檔案。你可以在config
資料夾中找到websockets.php
配置檔案。資料庫遷移檔案可以先不管。
> composer require beyondcode/laravel-websockets
> php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
我們需要在.env
檔案中配置幾個引數,PUSHER_APP_ID
、PUSHER_APP_KEY
、PUSHER_APP_SECRET
可以隨便寫,與文件中提到的 pusher 無關。因為有 Laravel-websockets
在程式裡攔截了pusher
的轉發動作,會將事件轉發到本地。BROADCAST_DRIVER
指定廣播驅動為pusher
固定值。LARAVEL_WEBSOCKETS_PORT
是指 socket 監聽的埠號,一定要讓這個埠可以被外網訪問:
// .env
BROADCAST_DRIVER=pusher
PUSHER_APP_ID=joker
PUSHER_APP_KEY=joker
PUSHER_APP_SECRET=joker
LARAVEL_WEBSOCKETS_PORT=2020
配置好以後,我們就可以啟動 socket 監聽程式了。雖然上面的.env
檔案中,我們已經配置好2020
埠了,但這裡的啟動命令中,我們還是需要--port=2020
手動指定埠號,否則預設是6001
埠:
> php artisan websockets:serve --port=2020
Starting the WebSocket server on port 2020...
看,啟動一個 socket 監聽程式就是這麼簡單。為了驗證是否能進行通訊,我們在位址列中輸入http://<your.host>/laravel-websockets
,然後會顯示一個在Laravel-websockets
中已經註冊好的頁面:
我們點選那個connect
按鈕,如果如下圖所示,則說明可以正常通訊了:
在routes/web.php
中新增web
路由,並建立相應的view
檢視
Route::get('/hello', function () {
// 首頁顯示二維碼
return view('hello');
});
Route::get('login', function () {
// 掃碼後,手機顯示的登陸頁
return view('login');
});
Route::post('/login', function () {
// 這裡先空著,後面會補充
return 'Logined';
});
我們需要在resources/views/hellow.blade.php
中先引入一些JS
工具,然後編寫一些JS
程式碼。
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
<!-- 引入jQuery工具 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<!-- 引入二維碼工具 -->
<script src="https://cdn.jsdelivr.net/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js"></script>
<!-- 引入laravel-echo工具,其實使用Larave自帶的也可以。但是,使用自帶的還需要用到node前端構建工具,我這裡只簡單的演示後端實現過程,就不用node了 -->
<script src="https://cdn.jsdelivr.net/npm/laravel-echo@1.10.0/dist/echo.iife.js"></script>
<!-- 引入pusher工具,pusher是Laravel-echo底層,Laravel-echo是pusher的一層封裝 -->
<script src="https://cdn.jsdelivr.net/npm/pusher-js@7.0.3/dist/web/pusher.min.js"></script>
</head>
<body>
<h1>二維碼</h1>
</body>
<script type="text/javascript">
// 簡單模擬一個 uuid 唯一身份碼,為了後端廣播時,不會廣播給錯人
var uuid = Math.random().toString(36);
// 初始化 laravel-echo 外掛
window.Echo = new Echo({
// 這裡是固定值 pusher
broadcaster: 'pusher',
// 這裡要和你在 .env 中配置的 PUSHER_APP_KEY 保持一致
key: 'joker',
wsHost: location.hostname,
// 這裡是我們在上一步啟動 socket 監聽的埠
wsPort: 2020,
// 這個也要加上
forceTLS: false,
});
// 我們隨便監聽一個頻道,這個頻道在專案還不存在,但不影響建立 socket 連線
Echo.channel('abcdefg.'+uuid)
// 隨便監聽一個事件,這個事件在專案中還不存在,但不影響建立 socket 連線
.listen('LoginedEvent', (e) => {
console.log(e);
});
// 顯示一個二維碼,內容是一個登陸頁地址,後面拼接 uuid。這個 uuid 會在後面廣播中用到,用來給監聽此 uuid 頻道的 socket 傳送資料
$("body").qrcode(location.origin+"/login?uuid="+uuid);
</script>
</html>
程式碼中已經註釋的比較清晰了,我們儲存檔案,然後在瀏覽器中輸入http://<your.host>/hello
點選回車。我們開啟瀏覽器的開發者工具,找到WS
選項,我們會看到 socket 已經與服務端進行了通訊。再看下服務端,會顯示連線者的一些資訊:
這一步我們只需要做兩件事,寫一個簡單的登陸頁面,然後實現登陸驗證:
簡單的登陸頁
我們開啟resources/views/login.blade.php
檢視檔案,寫入以下內容:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<!-- 引入jQuery工具 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
</head>
<body>
<input type="text" name="username">
<input type="text" name="password">
<button>登陸</button>
</body>
<script type="text/javascript">
// 請求登陸介面
$('button').click(function(event) {
// 還記得前面掃碼的連結裡,帶有 uuid 引數嗎。簡單的獲取 url 中 uuid 引數
var uuid = location.search.substring(1).split('=')[1];
// 請求登陸介面
$.ajax({
// 這個登陸介面就是上面 routes/web.app 中定義好的路由。因為是 POST 請求,所以會進入 Route::post() 定義的路由中
url: location.origin+'/login',
type: 'POST',
dataType: 'json',
data: {
username: $('input[name="username"]').val(),
password: $('input[name="password"]').val(),
uuid: uuid,
// Laravel 預設帶有 csrf_token 驗證,所以這裡要加 _token 變數
_token: '{{ csrf_token() }}',
},
success: function(data){
console.log(data);
}
});
});
</script>
</html>
很簡單的一個頁面,我們用微信掃一掃看下頁面效果:
哈哈,雖然很醜,但很簡潔不是嗎?接下來,我們要進行後臺程式碼的完善工作了。
登陸驗證
進入routes/web.php
中,修改Route::post()
路由。為了簡化程式碼流程,我就直接在路由中寫登陸邏輯了, 但平時一定不要這麼幹。
Route::post('/login', function (\Illuminate\Http\Request $request) {
// 為了儘可能簡化流程,我們就不執行資料庫查詢的邏輯了,直接在 session 中寫入資訊吧
session([
'login_info' => $request->all(),
]);
// 返回響應
return response([
'code' => 0,
'message' => '登陸成功',
'data' => session('login_info'),
]);
});
這應該是最簡化的登陸邏輯了吧,我們來小測一下,在http://<your.host>/login
頁面中,在兩個輸入框中寫入內容,然後點選登陸,看下返回結果:
確實返回了我們輸入的資訊哈。一步一個腳印,一步一回頭,每完成一個小功能呢,就小測一下,個人習慣哈。
這一環節,做的事情也不是很多,我們一步一步來。首先要把config/app.php
中的App\Providers\BroadcastServiceProvider::class
註釋開啟:
在routes/channels.php
中加入我們最開始,在前端JS
中寫的監聽頻道:
Broadcast::channel('abcdefg.{uuid}', function () {
return true;
});
在config/broadcasting.php
中的connections.pusher
下加入兩個配置資訊:
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
// 攔截 pusher 的廣播後,轉發到目標 ip
'host' => '127.0.0.1',
// 轉發的埠,就是我們之前在 .env 檔案中配置的 2020 埠
'port' => env('LARAVEL_WEBSOCKETS_PORT', 6001),
],
];
];
建立登陸事件
> php artisan make:event LoginedEvent
Event created successfully.
執行以上程式碼後,我們可以在app/Events
資料夾中找到LoginedEvent.php
檔案。
在事件中傳送廣播
只需在上面新建的app/Events/LoginedEvent.php
中,進行很小的改動就可以了。
<?php
namespace App\Events;
// 首先要讓這個事件類實現 ShouldBroadcast 介面,也就是在類名後加上 implements ShouldBroadcast 即可。
class LoginedEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
// 只有 public 屬性的變數會被廣播到指定的頻道中,所以這裡要使用 public 關鍵字修飾你需要廣播的變數
public $logined_info;
public function __construct($logined_info)
{
$this->logined_info = $logined_info;
}
// 實現 ShouldBroadcast 介面後,就必須實現這個方法,系統會自動將廣播傳送到這個方法中定義的頻道中
public function broadcastOn()
{
// 這裡我們改為 Channel。原 PrivateChannel 是私有頻道,未登入時前端無法監聽
// 這裡的頻道要與 routes/channels.php 中定義的頻道格式保持一致
return new Channel('abcdefg.'.$this->logined_info['uuid']);
}
}
觸發
我們再回到登陸驗證的路由中,在儲存 session 資料後,加入觸發登陸事件的程式碼:
Route::post('/login', function (\Illuminate\Http\Request $request) {
session([
'login_info' => $request->all(),
]);
// 觸發登陸事件
event(new \App\Events\LoginedEvent(session('login_info')));
return response([
'code' => 0,
'message' => '登陸成功',
'data' => session('login_info'),
]);
});
好了,我們再來驗證下,登陸後在 socket 中是否會收到廣播內容。這裡我們可以同時開啟http://<your.host>/login
和http://<your.host>/laravel-websockets
頁面,然後用手機微信的掃一掃功能掃描頁面上的二維碼。在手機上任意輸入內容,點選登陸
按鈕,看一下是否有新的廣播資料:
主要流程已經通了,現在離完成我們的目標已經近在咫尺了,加油!
現在我們來調整下app/Events/LoginedEvent.php
事件和routes/web.php
路由檔案中的/hello
部分。主要目的是:
在手機端觸發的登陸事件中,透過廣播將登陸後的
session_id
返回給 web 網頁前端。前端拿到後再將其拼接到路由中,執行頁面跳轉。後端會將 url 中的session_id
引數獲取到,然後將此 session id 儲存到此次 web 端請求的 session id 中。也就是使 web 端網頁與手機端使用同一個 session id 。
注意:在操作 session 資料時,如果你在框架 請求結束前 使用類似
dd()
、die()
等函式,則在此之前操作的 session 資料不會進行持久化儲存。詳細原因可參考原始碼:
這裡僅作為演示無需重複登入即可同步登陸資訊的實現,其中會有很多安全問題,這裡就先不考慮了,大佬們勿噴。先調整下app/Events/LoginedEvent.php
檔案:
class LoginedEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
// 只有 public 屬性的變數會被廣播到指定的頻道中,所以這裡要使用 public 關鍵字修飾你需要廣播的變數
public $logined_info;
// 加入新的變數 $session_id
public $session_id;
public function __construct($logined_info)
{
$this->logined_info = $logined_info;
// 獲取當前的 session id
$this->session_id = session()->getId();
}
public function broadcastOn()
{
return new Channel('abcdefg.'.$this->logined_info['uuid']);
}
}
修改resources/views/hellow.blade.php
檢視檔案,我們只需要修改監聽到事件後,執行回撥的部分。把上面事件中新增的session_id
欄位拼接到 url 中,然後跳轉到指定的路由:
Echo.channel('abcdefg.'+uuid)
.listen('LoginedEvent', (e) => {
console.log(e);
// 從登陸事件廣播出來的資料中,取出 session_id 欄位
var session_id = e.session_id;
// 拼接好引數後,跳轉到指定頁面,也就是當前頁面
location.href = location.origin+'/hello?session_id='+session_id;
});
在routes/web.php
檔案中的/hello
路由裡面加入替換 session id 的程式碼,這一步很關鍵:
Route::get('/hello', function (\Illuminate\Http\Request $request) {
if($request->get('session_id')){
// 將手機端登陸的 session_id 設定到當前頁面的 session 中
session()->setId($request->get('session_id'));
// 重新讀取 session 資料。其實就是將手機端登陸後的 session 內容讀取到當前頁面的 session 中
session()->start();
// 這裡只是做個提示
echo session('login_info.username')." 已透過手機掃碼登入";
}
return view('hello');
});
最後看下結果吧!手機端測試直接掃碼即可,如果想像我一樣在網頁上登陸,則需要在登陸頁的地址後面,手動拼接上?uuid=
引數。
此時已經實現手機端頁面與 web 端頁面使用的是同一個 session 資料了。記得在改後端程式碼時,儘量重啟一下laravel-wesocket
的 http 服務,以免出現改程式碼後,沒有生效的問題。
《透過laravel-echo主動向服務端傳送訊息,實現線上狀態管理》:主要講述了laravel-echo
如何主動向伺服器傳送訊息,並在後端編寫自己的控制器邏輯。
本作品採用《CC 協議》,轉載必須註明作者和本文連結