「实用」微信扫码 - 关注公众号后网站自动登录

「实用」微信扫码关注公众号号后自动登录

序言

常见方式

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

「实用」微信扫码关注公众号号后自动登录

利于推广方式

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

前期准备

梳理

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

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

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

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

  • 生成二维码的时候你自定义一个参数到二维码中,顺便把这个参数传到前端页面中。
  • 前端页面根据这个参数轮询用户登录状态(也可使用 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,很实用的一个功能吧,赶快加到你项目中吧!

PS

感谢关注「GitHub 热门」公众号,带你了解技术圈内热门新鲜事!

file

本作品采用《CC 协议》,转载必须注明作者和本文链接
感谢关注「GitHub 热门」公众号
本帖由系统于 5年前 自动加精
Destiny
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 38

配图满分

5年前 评论
Destiny

@overtrue :smirk: :smirk: :blush: :blush: :blush: :blush: :blush: 哈哈

5年前 评论

学习了 , 想法新颖

5年前 评论
╰ゝSakura

最近刚刚好在做这个,开放平台那边太麻烦了,之前想的思路跟你这篇差不多 :+1:

5年前 评论
Destiny

@Marshall 可以试试。挺不错的一个功能

5年前 评论
jcc123

之前实现了一个微信扫码登陆,原理和楼主的差不多,只不过不用关注公众号(服务号直接可以测试,测试号还是需要关注的)体验地址

5年前 评论
Destiny

@jcc123 这篇文章 关注、扫描都可以登录的。:smile:

5年前 评论
jcc123

@Destiny 是我没有理解吗,第一步还是需要关注的吧

5年前 评论
newbing

微信还没有open 平台的时候,就用微信服务号这么做过微信登陆。技术上完全可信。

5年前 评论
Destiny

@newbing 嗯是的.

5年前 评论
Golx

WxUser 模型可以看下吗

5年前 评论
Destiny

@铁血情长 其实就是 Redis 存储的 Key

5年前 评论
Golx

@Destiny 好谢谢

5年前 评论
itdream

home.login.check 对应方法的代码应该怎么写?

5年前 评论
Destiny

@itdream 已更新,看文章下半篇

5年前 评论
Destiny

@铁血情长 什么回调。

5年前 评论
Golx

@Destiny 谢谢 已经好了 感谢

5年前 评论
Destiny
5年前 评论
Golx

@Destiny 但是在接收微信消息的时候报 Undefined index: MsgType"

5年前 评论
Destiny

@铁血情长 用 Log 去调试微信返回的消息吧。

5年前 评论
Golx

@Destiny post接口才可以接收 = =

5年前 评论
Golx

@Destiny 如果要拿 unionid 的话话 在包中该怎么拿

5年前 评论

摩拜大佬 :smile:

4年前 评论
Destiny

@铁血情长 你公众号绑定了开放平台,微信返回给你的事件中就有 uid

4年前 评论
Golx

@Destiny 解决了亲 谢谢

4年前 评论
萧晔离

以前倒是用laravel+easywechat写过,但是不是关注公众号,网页端生成自定义二维码+手机端微信授权,也是用redis方案

4年前 评论

有完整的源码分享出来吗?

4年前 评论
Destiny

@alexxd 文章上面已经是所有源码了。

4年前 评论

请问可以学习一下您ajaxSuccessl里如何写的嘛

4年前 评论
piupiu

未认证的公众号也可以吗?

4年前 评论
Destiny

@jhui 可以试试

4年前 评论
Destiny

@DongXin 里面就是一个 response()->json() 返回数据

4年前 评论

@Destiny 大神,请问普通的订阅号可以吗,认证过的。谢谢

4年前 评论

我一直没明白,是需要微信开放平台还是微信公众平台。 但是楼主好像两个都需要,就弄的我一脸懵逼。

3年前 评论
Destiny (楼主) 3年前
Dream_Liu (作者) 3年前

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

    return;
}

}

你好,请问 $wxUser->uid 这个 uid 在模型中如何定义?

3年前 评论

没理解前端是怎么区分哪个用户登录的?

3年前 评论

能把完整的源码分享出来吗?

3年前 评论

@Destiny 用户关注公众号动作 使用 $wxUser = $this->app->user->get($openId); 打印日志信息是空的,为什么这个方法获取不到用户的信息呢? openId是正常能获取到的

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!