Laravel PC 网页微信扫码登陆

Laravel PC 网页微信扫码登陆

流程

  1. 访问 PC 网页,通过微信接口生成一个临时二维码,二维码夹带一个 token 参数进行识别。
  2. PC 网页向服务端进行轮询
  3. 手机微信扫描二维码进入(关注)公众号
  4. 微信推送信息到服务端
  5. 服务端接收到微信推送后进行处理
  6. PC 网页收到服务端成功信息后停止轮询

依赖

1
$ composer require overtrue/wechat:~4.0

数据表

需要创建两个表,一个保存用户信息,一个保存 token 信息。如果应用到项目上需要自行调整。

database\migrations\2020_01_01_000000_create_users_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
protected $table = 'users';

/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if(Schema::hasTable($this->table)) {
return true;
}

Schema::create($this->table, function (Blueprint $table) {
$table->id();
$table->string('openid', 30)->unique()->comment('openid');
$table->string('nickname', 100)->comment('昵称');
$table->timestamps();
});

\DB::statement("ALTER TABLE `$this->table` comment '用户表'");
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists($this->table);
}
}

database\migrations\2020_01_01_000000_create_token_table.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateTokenTable extends Migration
{
protected $table = 'token';

/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if(Schema::hasTable($this->table)) {
return true;
}

Schema::create($this->table, function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('uid')->nullable()->comment('UID');
$table->string('value', 40)->unique()->comment('令牌值');
$table->unsignedTinyInteger('status')->default(0)->comment('状态 0 未使用 1 已使用');
$table->timestamps();
});

\DB::statement("ALTER TABLE `$this->table` comment '令牌表'");
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists($this->table);
}
}

运行迁移。

1
$ php artisan migrate

模型

app\Models\Users.php

1
2
3
4
5
6
7
8
9
10
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Users extends Model
{
protected $table = 'users';
}

app\Models\Token.php

1
2
3
4
5
6
7
8
9
10
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Token extends Model
{
protected $table = 'token';
}

控制器

IndexController 控制器用于视图显示和轮询处理。

WechatController 控制器用于接收微信推送。

app\Http\Controllers\IndexController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?php

namespace App\Http\Controllers;

use App\Models\Users;
use App\Models\Token;
use EasyWeChat\Factory;
use Illuminate\Support\Str;
use Illuminate\Http\Request;

class IndexController extends Controller
{
public function index(Request $request)
{
$config = [
'app_id' => 'wx120dfab834000d0c',
'secret' => '2126be89ea53de4976c4cebdac79a74f',
];

$app = Factory::officialAccount($config);

// GET 请求返回视图
if($request->isMethod('GET')) {
// 创建 token
$token = new Token;
$token->value = Str::random(40);

if(!$token->save()) {
return '创建 token 失败';
}

// 获取二维码 ticket
$qrcode = $app->qrcode->temporary($token->value, 5 * 60);

// 换取二维码 URL
$image_url = $app->qrcode->url($qrcode['ticket']);

return view('welcome', [
'token' => $token->value,
'image_url' => $image_url,
]);
}

// 获取前端传过来的 token
$token_str = $request->query('token');

if(is_null($token_str)) {
return '参数错误';
}


// 查询 token 信息
$token = Token::where('value', $token_str)
->first();

if(is_null($token)) {
return '参数错误';
}

// 判断是否为已使用状态
if($token->status !== 1) {
return '';
}

// 查询用户信息
$user = Users::where('id', $token->uid)
->first();

return '登陆成功,用户名:' . $user->nickname;
}
}

app\Http\Controllers\WechatController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
<?php

namespace App\Http\Controllers;

use App\Models\Users;
use App\Models\Token;
use EasyWeChat\Factory;
use Illuminate\Http\Request;

class WechatController extends Controller
{
public function index(Request $request)
{
$config = [
'app_id' => 'wx120dfab834000d0c',
'secret' => '2126be89ea53de4976c4cebdac79a74f',
'token' => 'hongfs',
];

$app = Factory::officialAccount($config);

$app->server->push(function ($message) use($app) {
// 接收到事件信息
if(isset($message['MsgType']) && $message['MsgType'] === 'event') {
// 事件名称
$event_name = $message['Event'];

// 二维码相关事件
if(in_array($event_name, ['SCAN', 'subscribe']) && $message['EventKey'] !== null) {
// 获取二维码里面的 token 参数
$token_str = $message['EventKey'];

// 未关注通过扫码进来,它的 EventKey 值是有个 `qrscene_` 前缀的
// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html#扫描带参数二维码事件
if($event_name === 'subscribe') {
$token_str = substr($token_str, 8);
}

// 查询 token 信息
$token = Token::where('value', $token_str)
->first();

if(is_null($token)) {
return 'token 不存在';
}

// 判断 token 是否未使用或已过期
if($token->status !== 0 || strtotime($token->created_at) + 600 < time()) {
return 'token 已失效';
}

// 用户 openid
$openid = $message['FromUserName'];

// 调用微信接口获取用户信息
$user_data = $app->user->get($openid);

// 查询用户信息
$user = Users::where('openid', $openid)
->first();

// 用户不存在于数据库,则利用微信接口拿到的数据创建一个新用户
if(is_null($user)) {
$user = new Users;
$user->openid = $openid;
$user->nickname = $user_data['nickname'];

if(!$user->save()) {
return '用户创建失败';
}
}

// 将 token 绑定为当前用户
$token->uid = $user->getKey();
// 将 token 状态设置为已使用
$token->status = 1;

if(!$token->save()) {
return 'token 更改失败';
}

return '登陆成功';
}
}

return 'Hi';
});

$response = $app->server->serve();

return $response;
}
}

视图

resources\views\welcome.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>微信扫码登录</title>
<link rel="stylesheet" href="https://lib.baomitu.com/bulma/0.8.0/css/bulma.min.css" />
</head>
<body>
<section class="hero is-info is-large">
<div class="hero-body">
<div class="container" style="display: flex; flex-direction: column; align-items: center; width: 200px;">
<h1 class="title">
<img src="{{ $image_url }}" />
</h1>
<h2 class="subtitle">
二维码只有五分钟有效期,超时刷新。
</h2>
</div>
</div>
</section>
<script src="https://lib.baomitu.com/jquery/3.5.0/jquery.min.js"></script>
<script>
var t;
t = setInterval(_ => {
$.post(`?token={{ $token }}`, (response) => {
if(response.length) {
alert(response);
clearInterval(t);
}
});
}, 1000);
</script>
</body>
</html>

路由

routes\web.php

1
2
3
4
5
6
<?php

use Illuminate\Support\Facades\Route;

Route::any('/', 'IndexController@index');
Route::any('/wechat', 'WechatController@index');

测试

其他

服务端接收推送前要进行服务器配置。微信验证 Token 是 GET 请求,推送是 POST 请求。

另外微信还提供测试号:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

往上