微信小程序靜默登錄和維護(hù)自定義登錄態(tài)詳解
1.背景
在小程序中,openid是一個(gè)用戶(hù)對(duì)于一個(gè)小程序/公眾號(hào)的標(biāo)識(shí),開(kāi)發(fā)者可以通過(guò)這個(gè)標(biāo)識(shí)識(shí)別出用戶(hù),就如同你的身份證一樣。
2.什么是靜默登錄?
在普通的應(yīng)用中,用戶(hù)通過(guò)表單驗(yàn)證登錄建立用戶(hù)體系,這種常見(jiàn)的登錄方式一般是通過(guò)登錄頁(yè)面表單進(jìn)行登錄,對(duì)用戶(hù)來(lái)說(shuō)是有感的。
在小程序中,由于是基于微信,可以通過(guò)微信官方提供的API能力,使我們能夠無(wú)感知得獲取用戶(hù)身份標(biāo)識(shí)(openid),快速建立小程序內(nèi)的用戶(hù)體系,對(duì)用戶(hù)來(lái)說(shuō)是無(wú)感知的,因此是由程序來(lái)完成這個(gè)自動(dòng)的登陸過(guò)程。
2.1登錄流程時(shí)序
下圖取自微信官方

小程序端調(diào)用wx.login(),獲取code并且上傳到服務(wù)器
export async function doLogin() {
if (isLogin) return false
isLogin = true
removeCache('token')
const { code } = await wxp.login()
const data = await login({ code })
setCache('token', data.data.token)
isLogin = false
return true
}
服務(wù)端拿到code,調(diào)用auth.code2Session接口換取openid
const getOpenid = async function (appid, secret, code) {
const resData = await axios.get('https://api.weixin.qq.com/sns/jscode2session?appid=' + appid + '&secret=' + secret + '&js_code=' + code + '&grant_type=authorization_code');
return resData.data;
}
總結(jié)流程:
- 小程序端調(diào)用wx.login(),獲取code并且上傳到服務(wù)器
- 服務(wù)器根據(jù)code,并且調(diào)用微信auth.code2Session接口換取openid
- 后臺(tái)服務(wù)器根據(jù)openid生成自定義token返回前端并且存儲(chǔ)起來(lái),后續(xù)業(yè)務(wù)邏輯用token來(lái)識(shí)別用戶(hù)身份
3.如何維護(hù)自定義登錄態(tài)
讓我們來(lái)看下官方的處理方式:

wx.checkSession({
success () {
//session_key 未過(guò)期,并且在本生命周期一直有效
},
fail () {
// session_key 已經(jīng)失效,需要重新執(zhí)行登錄流程
wx.login() //重新登錄
}
})
由圖中我們可以知道,真正決定登錄態(tài)的是微信的checkSession接口。因此每次檢查用戶(hù)登錄態(tài)是否有效就先調(diào)用一個(gè)checkSession接口,如果session_key失效,再發(fā)起登錄流程。
4.靜默登錄整體流程
4.1app.onLaunch中發(fā)起登錄
由于大部分的接口調(diào)用都需要token驗(yàn)證,因此在小程序啟動(dòng)的周期函數(shù)app.onLaunch中發(fā)起靜默登錄最為合適不過(guò)了。

4.2處理小程序不支持異步阻塞
由于小程序的啟動(dòng)流程中,頁(yè)面級(jí)和組件級(jí)的生命周期函數(shù)都不支持異步阻塞;因此會(huì)造成一個(gè)情況,app.onLaunch中發(fā)起的wx.login還沒(méi)有成功的時(shí)候,頁(yè)面級(jí)的生命周期函數(shù)已經(jīng)向服務(wù)器發(fā)起請(qǐng)求。由于我們的接口設(shè)計(jì)大部分都是需要驗(yàn)證的,此時(shí)登錄還未成功,token也還沒(méi)有正確返回,因此頁(yè)面級(jí)的生命周期發(fā)起的數(shù)據(jù)獲取接口肯定是會(huì)報(bào)錯(cuò)的(例如返回了401)
4.2.1粗糙的方案
采用回調(diào)函數(shù)的方式
//app.js
this.globalData.wxp.showLoading({
title: '登錄中...'
});
await login();
this.globalData.hasLogin = true;
if (this.checkLoginReadyCallback) {
this.checkLoginReadyCallback();
}
this.globalData.wxp.hideLoading();
頁(yè)面的生命周期中
async onLoad() {
if (app.globalData.hasLogin) {
//如果已經(jīng)登錄了直接獲取數(shù)據(jù)
this.getUserInfo();
this.getEvent();
} else {
//未登錄定義下回調(diào)函數(shù),等app.js登錄成功之后進(jìn)行調(diào)用
app.checkLoginReadyCallback = async () => {
this.getUserInfo();
this.getEvent();
};
}
},
優(yōu)點(diǎn):簡(jiǎn)單粗暴
缺點(diǎn):代碼結(jié)構(gòu)差;如果是多個(gè)頁(yè)面為啟動(dòng)頁(yè),則需要多個(gè)頁(yè)面都定義回調(diào)函數(shù)(假設(shè)使用了小程序onShare模式)
4.2.2優(yōu)雅的方式
借助fly.js庫(kù),實(shí)現(xiàn)對(duì)請(qǐng)求進(jìn)行上鎖機(jī)制。流程:app.js中發(fā)起登錄,同時(shí)頁(yè)面中也會(huì)發(fā)起請(qǐng)求。在請(qǐng)求攔截器中判斷請(qǐng)求的接口是否為白名單(不需要token驗(yàn)證的接口)接口和token是否存在;如果都為false,鎖住當(dāng)前請(qǐng)求進(jìn)入請(qǐng)求隊(duì)列,執(zhí)行登錄流程。等待登錄流程成功之后解鎖請(qǐng)求隊(duì)列,繼續(xù)發(fā)起頁(yè)面級(jí)的請(qǐng)求任務(wù)。如下為請(qǐng)求攔截器中的代碼:
//攔截處理
fly.interceptors.request.use(async (request) => {
//沒(méi)有token且請(qǐng)求不是白名單的都鎖住
if (
!getCache('token') &&
!whiteList.some((item) => request.url.startsWith(item))
) {
fly.lock()
//去登陸 成功之后再u(mài)nlock
await doLogin()
fly.unlock() //解鎖后,會(huì)繼續(xù)發(fā)起請(qǐng)求隊(duì)列中的任務(wù)
}
if (getCache('token') && !fly.config.headers['Authorization']) {
request.headers['Authorization'] = getCache('token')
}
request.headers['Content-Type'] = 'application/x-www-form-urlencoded'
return request
})
當(dāng)然,自定義登錄態(tài)也會(huì)存在過(guò)期的情況,我們可以在響應(yīng)攔截器中捕獲出錯(cuò)進(jìn)行處理:當(dāng)檢測(cè)到401token過(guò)期代碼時(shí),需要把請(qǐng)求隊(duì)列后面的請(qǐng)求都鎖死,防止多次出現(xiàn)401自定義登錄態(tài)過(guò)期的情況,然后發(fā)起登錄,登陸成功之后再進(jìn)行解鎖行為,觸發(fā)后續(xù)的請(qǐng)求隊(duì)列執(zhí)行,并且重新執(zhí)行本次由于token過(guò)期被服務(wù)器拒絕的接口,否則會(huì)造成請(qǐng)求失敗的情況(由于靜默登錄是用戶(hù)無(wú)感知的,突然出現(xiàn)身份驗(yàn)證信息過(guò)期會(huì)使用戶(hù)感覺(jué)到特別地奇怪,因此需要重新執(zhí)行本次請(qǐng)求操作而不是由用戶(hù)再次點(diǎn)擊或者其他的行為再發(fā)起):
// 響應(yīng)攔截
fly.interceptors.response.use(
(response) => {
//只將請(qǐng)求結(jié)果的data字段返回
return response.data
},
async (err) => {
if (err.status === 401) {
//401之后,把后面的請(qǐng)求都鎖死 防止再次401
fly.lock()
removeCache('token')
//去登陸 成功之后再u(mài)nlock
const isLoginSuccess = await doLogin()
if (isLoginSuccess) {
fly.unlock()
}
//新執(zhí)行本次由于token過(guò)期被服務(wù)器拒絕的接口
return fly.request(err.request)
}
}
)
由于請(qǐng)求有可能是并發(fā)的,為了防止登錄被多次執(zhí)行,因此對(duì)doLogin函數(shù)進(jìn)行了小小的改造(盡管寫(xiě)得很不優(yōu)雅,但是能力有限,大佬們賜教了):
export async function doLogin() {
//如果正在登錄中則不執(zhí)行
if (isLogin) return false
isLogin = true
//修改狀態(tài)為登錄中,反正重復(fù)多次登錄
removeCache('token')
const { code } = await wxp.login()
const data = await login({ code })
setCache('token', data.data.token)
isLogin = false
return true
}
4.3 整體流程圖

5.寫(xiě)在最后
細(xì)節(jié)的讀者即可發(fā)現(xiàn),api請(qǐng)求中并未設(shè)置最大請(qǐng)求數(shù)量的(微信小程序最大支持五個(gè)api同時(shí)發(fā)起),這點(diǎn)是需要補(bǔ)充進(jìn)來(lái)的。總體寫(xiě)下來(lái)作者覺(jué)得在實(shí)現(xiàn)方式上還有進(jìn)步的空間,作者能力有限,也是一邊學(xué)習(xí)一邊探討!
到此這篇關(guān)于微信小程序靜默登錄和維護(hù)自定義登錄態(tài)的文章就介紹到這了,更多相關(guān)微信小程序靜默登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
toString()一個(gè)會(huì)自動(dòng)調(diào)用的方法
toString方法的作用是不用多說(shuō)的了,這個(gè)JavaScript內(nèi)置方法還有一個(gè)特性是:在執(zhí)行一些特殊方法的時(shí)候,比如alert或innerHTML等方法,它將由腳本解析器自動(dòng)調(diào)用。2010-02-02
使用原生js編寫(xiě)一個(gè)簡(jiǎn)單的框選功能方法
這篇文章主要介紹了使用原生js編寫(xiě)一個(gè)簡(jiǎn)單的框選功能方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05
bootstrap flask登錄頁(yè)面編寫(xiě)實(shí)例
這篇文章主要為大家詳細(xì)介紹了bootstrap flask登錄頁(yè)面編寫(xiě)實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
php對(duì)mongodb的擴(kuò)展(初識(shí)如故)
mongodb的數(shù)據(jù)存儲(chǔ)格式是一種由于MongoDB的文檔結(jié)構(gòu)為BJSON格式(BJSON全稱(chēng):Binary JSON),而B(niǎo)JSON格式本身就支持保存二進(jìn)制格式的數(shù)據(jù),因此可以把文件的二進(jìn)制格式的數(shù)據(jù)直接保存到MongoDB的文檔結(jié)構(gòu)中2012-11-11
開(kāi)源的javascript項(xiàng)目Kissy介紹
本文向大家介紹了開(kāi)源的javascript項(xiàng)目Kissy,是taobao UED小組推的一款js框架,我們來(lái)簡(jiǎn)單研究下,為什么taobao這么推崇他呢。2014-11-11
Javascript編寫(xiě)俄羅斯方塊思路及實(shí)例
本文主要給大家介紹的是使用javascript編寫(xiě)俄羅斯方塊小游戲的思路和具體的實(shí)例代碼,有需要的小伙伴可以參考下2015-07-07
JavaScript滾動(dòng)條屬性scrollTop和scrollHeight
在開(kāi)發(fā)中我們常常會(huì)用到判斷滾動(dòng)條是否觸底的邏輯,我一般都會(huì)在網(wǎng)上搜一段代碼,這段代碼有用到scrollTop、clientHeight、 scrollHeight,這篇文章主要給大家介紹了關(guān)于JavaScript滾動(dòng)條屬性scrollTop和scrollHeight的相關(guān)資料,需要的朋友可以參考下2023-11-11

