「實用」微信掃碼 - 關注公眾號後網站自動登入

Destiny發表於2019-04-03

「實用」微信掃碼關注公眾號號後自動登入

序言

常見方式

平常大家見到過最多的掃碼登入應該是 開放平臺網頁登入 大概形式就是:點選微信登入後會出現一個黑頁面,頁面中有一個二維碼,掃碼後可以自動獲取使用者資訊然後登入,但是這種方式需要申請開放平臺比較麻煩。如圖

「實用」微信掃碼關注公眾號號後自動登入

利於推廣方式

另外一種掃碼登入方式只需要一個微信服務號就行,大概流程是:點選微信登入,網站自己彈出一個二維碼、掃描二維碼後彈出公眾號的關注介面、只要一關注公眾號網站自動登入、第二次掃描登入的時候網站直接登入,大家可以體驗一下 「隨便找的一個網站」,這種掃碼登入的方式個人覺得非常利於推廣公眾號

前期準備

梳理

其實第二種掃碼登入的原理很簡單,核心就是依靠 微信帶參二維碼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 欣賞一下

「實用」微信掃碼關注公眾號號後自動登入

將來的你一定會感謝現在努力的自己!

相關文章