序言
常見方式
平常大家見到過最多的掃碼登入應該是 開放平臺網頁登入 大概形式就是:點選微信登入後會出現一個黑頁面,頁面中有一個二維碼,掃碼後可以自動獲取使用者資訊然後登入,但是這種方式需要申請開放平臺比較麻煩。如圖
利於推廣方式
另外一種掃碼登入方式只需要一個微信服務號就行,大概流程是:點選微信登入,網站自己彈出一個二維碼、掃描二維碼後彈出公眾號的關注介面、只要一關注公眾號網站自動登入、第二次掃描登入的時候網站直接登入,大家可以體驗一下 「隨便找的一個網站」,這種掃碼登入的方式個人覺得非常利於推廣公眾號
前期準備
- 服務號(或者微信測試賬號)
- EasyWeChat 擴充套件包
梳理
其實第二種掃碼登入的原理很簡單,核心就是依靠 微信帶參二維碼、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,很實用的一個功能吧,趕快加到你專案中吧!