新手必看
准備工作
主要流程
初始化項目
> 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軟件包
laravel-websockets 是一款封裝好了 websocket 服務的軟件包。其特點是通過攔截 pusher 的請求來擺脫對 pusher 服務商(國外)的依賴。還具有調試面板,實時統計信息,甚至允許您創建自定義 WebSocket 控制器等能力。以下執行了兩步操作,首先引入軟件包,然后發布軟件包的配置文件和數據庫遷移文件。你可以在config
文件夾中找到websockets.php
配置文件。數據庫遷移文件可以先不管。
> composer require beyondcode/laravel-websockets
> php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
啟動websocket監聽
我們需要在.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';
});
建立socket連接
我們需要在app/resources/views/hellow.blade.php
中先引入一些JS
工具,然后編寫一些JS
代碼。
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
<!-- 引入jQuery工具 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- 引入二維碼工具 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<!-- 引入laravel-echo工具,其實使用Larave自帶的也可以。但是,使用自帶的還需要用到node前端構建工具,我這里只簡單的演示后端實現過程,就不用node了 -->
<script src="https://cdn.bootcdn.net/ajax/libs/laravel-echo/1.10.0/echo.iife.js"></script>
<!-- 引入pusher工具,pusher是Laravel-echo底層,Laravel-echo是pusher的一層封裝 -->
<script src="https://cdn.bootcdn.net/ajax/libs/pusher/7.0.3/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 已經與服務端進行了通信。再看下服務端,會顯示連接者的一些信息:
手機端掃碼登陸
這一步我們只需要做兩件事,寫一個簡單的登陸頁面,然后實現登陸驗證:
簡單的登陸頁
我們打開app/resources/views/login.blade.php
視圖文件,寫入以下內容:
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<!-- 引入jQuery工具 -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/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;
// 只有 punlic 屬性的變量會被廣播到指定的頻道中,所以這里要使用 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;
// 只有 punlic 屬性的變量會被廣播到指定的頻道中,所以這里要使用 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']);
}
}
在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');
});
最后是app/resources/views/hellow.blade.php
視圖文件,我們只需要修改監聽到事件后,執行回調的部分:
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;
});
最后看下結果吧!
此時已經實現手機端頁面與 web 端頁面使用的是同一個 session 數據了。記得在改后端代碼時,盡量重啟一下laravel-wesocket
的 http 服務,以免出現改代碼后,沒有生效的問題。
相關文章
《利用laravel-echo主動向服務端發送消息,實現在線狀態管理》:主要講述了laravel-echo
如何主動向服務器發送消息,並在后端編寫自己的控制器邏輯。