微信扫码:关注公众号后网站自动登录的实现原理

2年前 (2022) 程序员胖胖胖虎阿
205 0 0

点击上方 Java后端选择 设为星标

优质文章,及时送达


作者:destiny
链接:segmentfault.com/a/1190000022188562

序言

常见方式

平常大家见到过最多的扫码登录应该是 开放平台网页登录 大概形式就是:点击微信登录后会出现一个黑页面,页面中有一个二维码,扫码后可以自动获取用户信息然后登录,但是这种方式需要申请开放平台比较麻烦。如图

微信扫码:关注公众号后网站自动登录的实现原理
「实用」微信扫码关注公众号号后自动登录

利于推广方式

另外一种扫码登录方式只需要一个微信服务号就行,大概流程是:点击微信登录,网站自己弹出一个二维码、扫描二维码后弹出公众号的关注界面、只要一关注公众号网站自动登录、第二次扫描登录的时候网站直接登录,大家可以体验一下 「随便找的一个网站」,这种扫码登录的方式个人觉得非常利于推广公众号。

此外公众号 Java后端,还发布过很多扫描登陆、扫描支付等实战交教程,关注公众号 Java后端 ,回复 666 即可下载。

前期准备

  • 服务号(或者微信测试账号)
  • EasyWeChat 扩展包

梳理

其实第二种扫码登录的原理很简单,核心就是依靠 微信带参二维码、EasyWeChat 二维码文档

https://mp.weixin.qq.com/wiki?action=doc&id=mp1443433542&t=0.376179226179156

https://www.easywechat.com/docs/4.0/basic-services/qrcode

简单的解释一下扫描这个带参二维码有什么不同:

  • 扫描二维码,如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值(自定义值)关注事件推送给开发者。
  • 扫描二维码,如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值(自定义值)扫码事件推送给开发者。

看到这里相信你已经明白了,梳理一下:

  • 生成二维码的时候你自定义一个参数到二维码中,顺便把这个参数传到前端页面中。
  • 前端页面根据这个参数轮询用户登录状态(也可使用 socket)。
  • 用户扫码关注后会推送一个关注事件到服务端,也会把自定义参数带入到事件中。
  • 根据 openid 创建用户后,然后在 Redis 中存储 Key 为场景值(自定义参数) Value 为用户创建后的 id。
  • 前端轮询方法中如果在 Redis 中获取到 Id 后,Auth 登陆,页面再重载一下,流程完毕。

实战

请求登录二维码

前端通过一个点击事件请求微信登录二维码

// 方便清除轮询
let timer = null

$(document).on('click', '.wechat-login', function () {
// 请求登录二维码
axios.get('{{ route('wx.pic') }}').then(response => {
let result = response.data
if (result.status_code !== 200)
{
return
}
// 显示二维码图片
$('.wechat-url').attr('src', result.data.url)
// 轮询登录状态
timer = setInterval(() => {
// 请求参数是二维码中的场景值
axios.get('{{ route('home.login.check') }}', {params: {wechat_flag: result.data.weChatFlag}}).then(response => {
let result = response.data
if (result.data)
{
window.location.href = '/'
}
})
}, 2000)
})
})

// 返回时清除轮询
$('.wechat-back').click(function () {
clearInterval(timer)
})

后端生成带参二维码逻辑,EasyWeChat 配置请自行查阅 文档

protected $app;

/**
* Construct
*
* WeChatController constructor.
*/

public function __construct() {
$this->app = app('wechat.official_account');
}

/**
* 获取二维码图片
*
* @param Request $request
*
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/

public function getWxPic(Request $request) {
// 查询 cookie,如果没有就重新生成一次
if (!$weChatFlag = $request->cookie(WxUser::WECHAT_FLAG)) {
$weChatFlag = Uuid::uuid4()->getHex();
}

// 缓存微信带参二维码
if (!$url = Cache::get(WxUser::QR_URL . $weChatFlag)) {
// 有效期 1 天的二维码
$qrCode = $this->app->qrcode;
$result = $qrCode->temporary($weChatFlag, 3600 * 24);
$url = $qrCode->url($result["ticket"]);

Cache::put(WxUser::QR_URL . $weChatFlag, $url, now()->addDay());
}

// 自定义参数返回给前端,前端轮询
return $this->ajaxSuccess(compact('url', 'weChatFlag'))
->cookie(WxUser::WECHAT_FLAG, $weChatFlag, 24 * 60);
}

用户扫描二维码后处理

/**
* 微信消息接入(这里拆分函数处理)
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \ReflectionException
*/

public function serve() {
$app = $this->app;

$app->server->push(function ($message) {
if ($message) {
$method = camel_case('handle_' . $message['MsgType']);

if (method_exists($this, $method)) {
$this->openid = $message['FromUserName'];

return call_user_func_array([$this, $method], [$message]);
}

Log::info('无此处理方法:' . $method);
}
});

return $app->server->serve();
}

/**
* 事件引导处理方法(事件有许多,拆分处理)
*
* @param $event
*
* @return mixed
*/

protected function handleEvent($event) {
Log::info('事件参数:', [$event]);

$method = camel_case('event_' . $event['Event']);
Log::info('处理方法:' . $method);

if (method_exists($this, $method)) {
return call_user_func_array([$this, $method], [$event]);
}

Log::info('无此事件处理方法:' . $method);
}

/**
* 取消订阅
*
* @param $event
*/

protected function eventUnsubscribe($event) {
$wxUser = WxUser::whereOpenid($this->openid)->first();
$wxUser->subscribe = 0;
$wxUser->subscribe_time = null;
$wxUser->save();
}

/**
* 扫描带参二维码事件
*
* @param $event
*/

public function eventSCAN($event) {
if ($wxUser = WxUser::whereOpenid($this->openid)->first()) {
// 标记前端可登陆
$this->markTheLogin($event, $wxUser->uid);

return;
}
}

/**
* 订阅
*
* @param $event
*
* @throws \Throwable
*/

protected function eventSubscribe($event) {
$openId = $this->openid;

if ($wxUser = WxUser::whereOpenid($openId)->first()) {
// 标记前端可登陆
$this->markTheLogin($event, $wxUser->uid);

return;
}

// 微信用户信息
$wxUser = $this->app->user->get($openId);
// 注册
$nickname = $this->filterEmoji($wxUser['nickname']);

$result = DB::transaction(function () use ($openId, $event, $nickname, $wxUser) {
$uid = Uuid::uuid4()->getHex();
$time = time();

// 用户
$user = User::create([
'uid' => $uid,
'created_at' => $time,
]);
// 用户信息
$user->user_info()->create([
'email' => $user->email,
'nickname' => $nickname,
'sex' => $wxUser['sex'],
'address' => $wxUser['country'] . ' ' . $wxUser['province'] . ' ' . $wxUser['city'],
'avatar' => $wxUser['headimgurl'],
'code' => app(UserRegisterController::class)->inviteCode(10),
'created_at' => $time,
]);
// 用户账户
$user->user_account()->create([
'gold' => 200,
'created_at' => $time,
]);

$wxUserModel = $user->wx_user()->create([
'subscribe' => $wxUser['subscribe'],
'subscribe_time' => $wxUser['subscribe_time'],
'openid' => $wxUser['openid'],
'created_at' => $time,
]);

Log::info('用户注册成功 openid:' . $openId);

$this->markTheLogin($event, $wxUserModel->uid);
});

Log::debug('SQL 错误: ', [$result]);
}

/**
* 标记可登录
*
* @param $event
* @param $uid
*/

public function markTheLogin($event, $uid) {
if (empty($event['EventKey'])) {
return;
}

$eventKey = $event['EventKey'];

// 关注事件的场景值会带一个前缀需要去掉
if ($event['Event'] == 'subscribe') {
$eventKey = str_after($event['EventKey'], 'qrscene_');
}

Log::info('EventKey:' . $eventKey, [$event['EventKey']]);

// 标记前端可登陆
Cache::put(WxUser::LOGIN_WECHAT . $eventKey, $uid, now()->addMinute(30));
}

前端登录检查

/**
* 微信用户登录检查
*
* @param Request $request
*
* @return bool|\Illuminate\Http\JsonResponse
*/

public function loginCheck(Request $request) {
// 判断请求是否有微信登录标识
if (!$flag = $request->wechat_flag) {
return $this->ajaxSuccess(false);
}

// 根据微信标识在缓存中获取需要登录用户的 UID
$uid = Cache::get(WxUser::LOGIN_WECHAT . $flag);
$user = User::whereUid($uid)->first();

if (empty($user)) {
return $this->ajaxSuccess(false);
}

// 登录用户、并清空缓存
auth('web')->login($user);
Cache::forget(WxUser::LOGIN_WECHAT . $flag);
Cache::forget(WxUser::QR_URL . $flag);

return $this->ajaxSuccess(true);
}

OK,很实用的一个功能吧,赶快加到你项目中吧!


-END-

如果看到这里,说明你喜欢这篇文章,请 转发、点赞。同时 标星(置顶)本公众号可以第一时间接受到博文推送。

1. 2020 年 5 月全国程序员工资出炉!

2. 使用 Docker 部署 Spring Cloud 项目详细步骤

3. 一行命令下载全网视频

4. 彻底理解 SpringIOC、DI

微信扫码:关注公众号后网站自动登录的实现原理


最近整理一份资料
《Java技术栈学习手册》
,覆盖了Java技术、面试题精选、Spring全家桶、Nginx、SSM、微服务、数据库、数据结构、架构等等。

获取方式:点“ 在看,关注公众号 Java后端 并回复 777 领取,更多内容陆续奉上。

喜欢文章,点个在看 微信扫码:关注公众号后网站自动登录的实现原理

本文分享自微信公众号 - Java后端(web_resource)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

相关文章

暂无评论

暂无评论...