新手必看

准备工作

主要流程

初始化项目

> 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中已经注册好的页面:

我们点击那个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>/loginhttp://<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如何主动向服务器发送消息,并在后端编写自己的控制器逻辑。

利用websocket实现手机扫码登陆后,同步登陆信息到web端页面的更多相关文章

  1. 【flask】登陆后返回之前重定向跳转的页面

    登陆后返回之前重定向跳转的页面 一.前言 实现强制跳转到登陆页面,登陆后返回之前的页面的功能.网上跳登陆页面的很多:返回之前页面功能没多少.这里我只是用了自己的方法,有缺点和其他方法也请指点!(´ε` ...

  2. 12.18 微信扫码获取openid与登陆

    官方文档: https://open.weixin.qq.com/ 1.先获取code 1-1 配置项目配置文件 wechat: mpAppId: wxd898fcb01713c658 mpAppSe ...

  3. 在做微信分享到朋友圈时,手机扫码报config:invalid signature,分享后后正常的问题,是url问题

    是按照以下步骤检查的 除了ACCESS_TOKEN没有缓存其他都可以 如果是invalid signature签名错误.建议按如下顺序检查: 1.确认签名算法正确,可用 http://mp.weixi ...

  4. python-Web-django-qq扫码登陆

    1.建路由 2.写qq登录的a链接 3 在控制器的loginQq的方法:拼接url,跳转到这个url: 去:https://graph.qq.com/oauth2.0/authorize?respon ...

  5. 微信扫码登陆(JAVA)

    在web端用到weChat扫码登录,在手机扫码登陆成功后,跳转到相应的界面. 1.第一步请求code 调用接口:https://open.weixin.qq.com/connect/qrconnect ...

  6. 分享一下,PHP实现第四方QQ微信扫码登陆,不接入qq互联以及微信开发者平台就可以实现用户对接鹅厂,phpQQ微信扫码登陆

    自己抓的QQ包以及整合了网上一些已经封装好了的代码具体如下:QQ: <?php class QQ extends Curl_Api { //获取登录验证码 public function QRc ...

  7. 微信扫码登陆js

    先贴一个微信开发文档教程 https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.ht ...

  8. Java 语言实现简易版扫码登录

    基本介绍 相信大家对二维码都不陌生,生活中到处充斥着扫码登录的场景,如登录网页版微信.支付宝等.最近学习了一下扫码登录的原理,感觉蛮有趣的,于是自己实现了一个简易版扫码登录的 Demo,以此记录一下学 ...

  9. 初涉扫码登录:edusoho实现客户端扫码登录(简版)

    一.项目简介及需求 edusoho是一套商业版的在线教育平台,项目本身基于symfony2框架开发,现在有一款自己的APP,要求在不多修改edusoho自身代码的基础上,实现客户端对PC端扫码登录.不 ...

随机推荐

  1. docker入门_image、container相关命令

    docker入门_image.container相关命令 镜像仓库服务.镜像仓库.镜像相关概念 镜像仓库服务:docker镜像仓库服务.阿里云镜像服务 镜像仓库:docker镜像仓库服务中会有很多仓库 ...

  2. 手把手教会将 Windows 窗体桌面应用从.NET Framework迁移到 .NET SDK/.NET 6 格式

    接上篇:手把手教会 VS2022 设计 Winform 高DPI兼容程序 (net461 net6.0 双出) https://www.cnblogs.com/densen2014/p/1614293 ...

  3. Revit二次开发之创建风管

      在Revit中,风管用于连接管件,风道末端和机械设备,今天简单尝试了下使用RevitAPI创建风管,现分享下我的方法.   风管从类型上可分为三类:一般风管,软风管和风管占位符:从形状上也分为三类 ...

  4. EF Core忽略某个属性保存

    1.事情起因 某天朋友突然问我他的EF不能保存,让我帮忙看看,观察发现主表中存在明细表的集合,导致保存失败. 2.解决方案 方案1:DTO模型与DO模型分开,保存时映射. 分层领域模型规约名词解释: ...

  5. 解读先电2.4版 iaas-install-mysql.sh 脚本

    #!/bin/bash #声明解释器路径 source /etc/xiandian/openrc.sh #生效环境变量 ping $HOST_IP -c 4 >> /dev/null 2& ...

  6. 使用requests爬取梨视频、bilibili视频、汽车之家,bs4遍历文档树、搜索文档树,css选择器

    今日内容概要 使用requests爬取梨视频 requests+bs4爬取汽车之家 bs4遍历文档树 bs4搜索文档树 css选择器 内容详细 1.使用requests爬取梨视频 # 模拟发送http ...

  7. HTML语言的简要学习

    什么是HTML? HTML 是用来描述网页的一种语言. l  HTML 指的是超文本标记语言 (Hyper Text Markup Language) l  HTML 不是一种编程语言,而是一种标记语 ...

  8. 并发编程之volatile与JMM多线程内存模型

    一.通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码.这段代码的逻辑很简单:主线程启动了两个子线程,一个线程1.一个线程2.线程1先执行,sleep睡眠2秒钟之后 ...

  9. 深入C++06:深入掌握OOP最强大的机制

    深入掌握OOP最强大的机制 1. 继承的基本意义 类与类之间的关系:①组合:a part of ... 一部分的关系:②继承: a kind of ... 属于同一种的关系: 继承的本质:a. 代码的 ...

  10. 398. Random Pick Index - LeetCode

    Question 398. Random Pick Index Solution 思路:重点是如果数据中有多个数target相等,要从这些数中随机取一个,根据例题 假设输入是: int[] nums ...