利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面


「手把手」利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

新手必看

准備工作

主要流程

初始化項目

> 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_IDPUSHER_APP_KEYPUSHER_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中已經注冊好的頁面:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

我們點擊那個connect按鈕,如果如下圖所示,則說明可以正常通信了:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

創建兩個頁面

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';
});

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

建立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 已經與服務端進行了通信。再看下服務端,會顯示連接者的一些信息:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

手機端掃碼登陸

這一步我們只需要做兩件事,寫一個簡單的登陸頁面,然后實現登陸驗證:

簡單的登陸頁

我們打開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>

很簡單的一個頁面,我們用微信掃一掃看下頁面效果:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

哈哈,雖然很丑,但很簡潔不是嗎?接下來,我們要進行后台代碼的完善工作了。

登陸驗證

進入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頁面中,在兩個輸入框中寫入內容,然后點擊登陸,看下返回結果:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

確實返回了我們輸入的信息哈。一步一個腳印,一步一回頭,每完成一個小功能呢,就小測一下,個人習慣哈。

服務端觸發登陸事件

這一環節,做的事情也不是很多,我們一步一步來。首先要把config/app.php中的App\Providers\BroadcastServiceProvider::class注釋打開:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

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>/loginhttp://<your.host>/laravel-websockets頁面,然后用手機微信的掃一掃功能掃描頁面上的二維碼。在手機上任意輸入內容,點擊登陸按鈕,看一下是否有新的廣播數據:

【手把手】利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

主要流程已經通了,現在離完成我們的目標已經近在咫尺了,加油!

頁面跳轉(重點)

現在我們來調整下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 數據不會進行持久化保存。詳細原因可參考源碼:
「手把手」利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

這里僅作為演示無需重復登錄即可同步登陸信息的實現,其中會有很多安全問題,這里就先不考慮了,大佬們勿噴。先調整下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;
        });

最后看下結果吧!

[手把手]利用websocket實現手機掃碼登陸后,同步登陸信息到web端頁面

此時已經實現手機端頁面與 web 端頁面使用的是同一個 session 數據了。記得在改后端代碼時,盡量重啟一下laravel-wesocket的 http 服務,以免出現改代碼后,沒有生效的問題。

相關文章

利用laravel-echo主動向服務端發送消息,實現在線狀態管理:主要講述了laravel-echo如何主動向服務器發送消息,並在后端編寫自己的控制器邏輯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM