小編給大家分享一下微信小程序登錄前端設(shè)計(jì)與實(shí)現(xiàn)的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

對(duì)于登錄/注冊(cè)的設(shè)計(jì)如此精雕細(xì)琢的目的,當(dāng)然是想讓這個(gè)作為應(yīng)用的基礎(chǔ)能力,有足夠的健壯性,避免出現(xiàn)全站性的阻塞。
同時(shí)要充分考慮如何解耦和封裝,在開(kāi)展新的小程序的時(shí)候,能更快的去復(fù)用能力,避免重復(fù)采坑。
登錄注冊(cè)這模塊,就像個(gè)冰山,我們以為它就是「輸入賬號(hào)密碼,就完成登錄了」,但實(shí)際下面還有各種需要考慮的問(wèn)題。

在此,跟在座的各位分享一下,最近做完一個(gè)小程序登錄/注冊(cè)模塊之后,沉淀下來(lái)的一些設(shè)計(jì)經(jīng)驗(yàn)和想法。
在用戶(hù)瀏覽小程序的過(guò)程中,由業(yè)務(wù)需要,往往需要獲取用戶(hù)的一些基本信息,常見(jiàn)的有:
微信昵稱(chēng)
微信手機(jī)號(hào)
而不同的產(chǎn)品,對(duì)于用戶(hù)的信息要求不盡相同,也會(huì)有不一樣的授權(quán)流程。
第一種,常見(jiàn)于電商系統(tǒng)中,用戶(hù)購(gòu)買(mǎi)商品的時(shí)候,為了識(shí)別用戶(hù)多平臺(tái)的賬號(hào),往往用手機(jī)號(hào)去做一個(gè)聯(lián)系,這時(shí)候需要用戶(hù)去授權(quán)手機(jī)號(hào)。

第二種,為了讓用戶(hù)信息得到基本的初始化,往往需要更進(jìn)一步獲取用戶(hù)信息:如微信昵稱(chēng),unionId 等,就需要詢(xún)問(wèn)用戶(hù)授權(quán)。

第三種,囊括第一種,第二種。

秉著沉淀一套通用的小程序登錄方案和服務(wù)為目標(biāo),我們?nèi)シ治鲆幌聵I(yè)務(wù),得出變量。
在做技術(shù)設(shè)計(jì)之前,講點(diǎn)必要的廢話(huà),對(duì)一些概念進(jìn)行基本調(diào)頻。
登錄在英文中是 「login」,對(duì)應(yīng)的還有 「logout」。而登錄之前,你需要擁有一個(gè)賬號(hào),就要 「register」(or sign up)。
話(huà)說(shuō)一開(kāi)始的產(chǎn)品是沒(méi)有登錄/注冊(cè)功能的,用的人多了就慢慢有了。出于產(chǎn)品本身的需求,需要對(duì)「用戶(hù)」進(jìn)行身份識(shí)別。
在現(xiàn)實(shí)社會(huì)中,我們每個(gè)人都有一個(gè)身份ID:身份證。當(dāng)我到了16歲的時(shí)候,第一次去公安局領(lǐng)身份證的時(shí)候,就完成了一次「注冊(cè)」行為。然后我去網(wǎng)吧上網(wǎng),身份證刷一下,完成了一次「登錄」行為。
那么對(duì)于虛擬世界的互聯(lián)網(wǎng)來(lái)說(shuō),這個(gè)身份證明就是「賬號(hào)+密碼」。
常見(jiàn)的登錄/注冊(cè)方式有:
賬號(hào)密碼注冊(cè)
在互聯(lián)網(wǎng)的早期,個(gè)人郵箱和手機(jī)覆蓋度小。所以,就需要用戶(hù)自己想一個(gè)賬號(hào)名,我們注冊(cè)個(gè)QQ號(hào),就是這種形式。

郵箱地址注冊(cè)
千禧年之后,PC互聯(lián)網(wǎng)時(shí)代快速普及,我們都創(chuàng)建了屬于自己的個(gè)人郵箱。加上QQ也自帶郵箱賬號(hào)。由于郵箱具有個(gè)人私密性,且能夠進(jìn)行信息的溝通,因此,大部分網(wǎng)站開(kāi)始采用郵箱賬號(hào)作為用戶(hù)名來(lái)進(jìn)行注冊(cè),并且會(huì)在注冊(cè)的過(guò)程中要求登錄到相應(yīng)郵箱內(nèi)查收激活郵件,驗(yàn)證我們對(duì)該注冊(cè)郵箱的所有權(quán)。

手機(jī)號(hào)碼注冊(cè)
在互聯(lián)網(wǎng)普及之后,智能手機(jī)與移動(dòng)互聯(lián)網(wǎng)發(fā)展迅猛。手機(jī)也成為每個(gè)人必不可少的移動(dòng)設(shè)備,同時(shí)移動(dòng)互聯(lián)網(wǎng)也已經(jīng)深深融入每個(gè)人的現(xiàn)代生活當(dāng)中。所以,相較于郵箱,目前手機(jī)號(hào)碼與個(gè)人的聯(lián)系更加緊密,而且越來(lái)越多的移動(dòng)應(yīng)用出現(xiàn),采用手機(jī)號(hào)碼作為用戶(hù)名的注冊(cè)方式也得到了廣泛的使用。

到了 2020 年,微信用戶(hù)規(guī)模達(dá) 12 億。那么,微信賬號(hào),起碼在中國(guó),已成為新一代互聯(lián)網(wǎng)世界的「身份標(biāo)識(shí)」。
而對(duì)微信小程序而言,天然就能知道當(dāng)前用戶(hù)的微信賬號(hào)ID。微信允許小程序應(yīng)用,能在用戶(hù)無(wú)感知的情況下,悄無(wú)聲息的「登錄」到我們的小程序應(yīng)用中去,這個(gè)就是我們經(jīng)常稱(chēng)之為的「靜默登錄」。
其實(shí)微信小程序的登錄,跟傳統(tǒng) Web 應(yīng)用的「單點(diǎn)登錄」本質(zhì)是一樣的概念。
單點(diǎn)登錄:在 A 站登錄了,C 站和 B 站能實(shí)現(xiàn)快速的「靜默登錄」。
微信小程序登錄:在微信中,登錄了微信賬號(hào),那么在整個(gè)小程序生態(tài)中,都可以實(shí)現(xiàn)「靜默登錄」。
由于 Http 本來(lái)是無(wú)狀態(tài)的,業(yè)界基本對(duì)于登錄態(tài)的一般做法:
cookie-session:常用于瀏覽器應(yīng)用中
access token:常用于移動(dòng)端等非瀏覽器應(yīng)用
在微信小程序來(lái)說(shuō),對(duì)于「JS邏輯層」并不是一個(gè)瀏覽器環(huán)境,自然沒(méi)有Cookie,那么通常會(huì)使用access token 的方式。
對(duì)于需要更進(jìn)一步獲取用的用戶(hù)昵稱(chēng)、用戶(hù)手機(jī)號(hào)等信息的產(chǎn)品來(lái)說(shuō)。微信出于用戶(hù)隱私的考慮,需要用戶(hù)主動(dòng)同意授權(quán)。小程序應(yīng)用才能獲取到這部分信息,這就有了目前流行的小程序「授權(quán)用戶(hù)信息」、「授權(quán)手機(jī)號(hào)」的交互了。
出于不同的用戶(hù)信息敏感度不同的考慮,微信小程序?qū)τ诓煌挠脩?hù)信息提供「授權(quán)」的方式不盡相同:
調(diào)用具體 API 方式,彈窗授權(quán)。
例如調(diào)用wx.getLocation() 的時(shí)候,如果用戶(hù)未授權(quán),則會(huì)彈出地址授權(quán)界面。
如果拒絕了,就不會(huì)再次彈窗,wx.getLocation()直接返回失敗。
<button open-type="xxx" /> 方式。
僅支持:用戶(hù)敏感信息,用戶(hù)手機(jī)號(hào),需要配合后端進(jìn)行對(duì)稱(chēng)加解密,方能拿到數(shù)據(jù)。
用戶(hù)已拒絕,再次點(diǎn)擊按鈕,仍然會(huì)彈窗。
通過(guò)wx.authorize(),提前詢(xún)問(wèn)授權(quán),之后需要獲取相關(guān)信息的時(shí)候不用再次彈出授權(quán)。
梳理清楚了概念之后,我們模塊的劃分上,可以拆分為兩大塊:
登錄:負(fù)責(zé)與服務(wù)端創(chuàng)建起一個(gè)會(huì)話(huà),這個(gè)會(huì)話(huà)實(shí)現(xiàn)靜默登錄以及相關(guān)的容錯(cuò)處理等,模塊命名為:Session
授權(quán):負(fù)責(zé)與用戶(hù)交互,獲取與更新信息,以及權(quán)限的控制處理等,模塊命名為:Auth

微信官方提供的登錄方案,總結(jié)為三步:
前端通過(guò)wx.login() 獲取一次性加密憑證 code,交給后端。
后端把這個(gè) code 傳輸給微信服務(wù)器端,換取用戶(hù)標(biāo)識(shí)openId 和授權(quán)憑證session_key。(用于后續(xù)服務(wù)器端和微信服務(wù)器的特殊 API 調(diào)用,具體看:微信官方文檔-服務(wù)端獲取開(kāi)放數(shù)據(jù))。
后端把從微信服務(wù)器獲取到的用戶(hù)憑證與自行生成的登錄態(tài)憑證(token),傳輸給前端。前端保存起來(lái),下次請(qǐng)求的時(shí)候帶給后端,就能識(shí)別哪個(gè)用戶(hù)。
如果只是實(shí)現(xiàn)這個(gè)流程的話(huà),挺簡(jiǎn)單的。
但要實(shí)現(xiàn)一個(gè)健壯的登錄過(guò)程,還需要注意更多的邊界情況:
收攏wx.login() 的調(diào)用:
由于wx.login() 會(huì)產(chǎn)生不可預(yù)測(cè)的副作用,例如會(huì)可能導(dǎo)致session_key失效,從而導(dǎo)致后續(xù)的授權(quán)解密場(chǎng)景中的失敗。我們這里可以提供一個(gè)像session.login() 的方法,掌握wx.login() 控制權(quán),對(duì)其做一系列的封裝和容錯(cuò)處理。
調(diào)用的時(shí)機(jī):
通常我們會(huì)在應(yīng)用啟動(dòng)的時(shí)候(app.onLaunch() ),去發(fā)起靜默登錄。但這里會(huì)由小程序生命周期設(shè)計(jì)問(wèn)題而導(dǎo)致的一個(gè)異步問(wèn)題:加載頁(yè)面的時(shí)候,去調(diào)用一個(gè)需要登錄態(tài)的后端 API 的時(shí)候,前面異步的靜態(tài)登錄過(guò)程有可能還沒(méi)有完成,從而導(dǎo)致請(qǐng)求失敗。
當(dāng)然也可以在第一個(gè)需要登錄態(tài)的接口調(diào)用的時(shí)候以異步阻塞的方式發(fā)起登錄調(diào)用,這個(gè)需要結(jié)合良好設(shè)計(jì)的接口層。
以上講到的兩種場(chǎng)景的詳細(xì)設(shè)計(jì)思路下文會(huì)講到。
并發(fā)調(diào)用的問(wèn)題:
在業(yè)務(wù)場(chǎng)景中,難免會(huì)出現(xiàn)多處代碼需要觸發(fā)登錄,如果遇到極端情況,這多處代碼同時(shí)間發(fā)起調(diào)用。那就會(huì)造成短時(shí)間多次發(fā)起登錄過(guò)程,盡管之前的請(qǐng)求還沒(méi)有完成。針對(duì)這種情況,我們可以以第一個(gè)調(diào)用為阻塞,后續(xù)調(diào)用等待結(jié)果,就像精子和卵子結(jié)合的過(guò)程。
未過(guò)期調(diào)用的問(wèn)題:
如果我們的登錄態(tài)未過(guò)期,完全可以正常使用的,默認(rèn)情況就不需再去發(fā)起登錄過(guò)程了。這時(shí)候我們可以默認(rèn)情況下先去檢查登錄態(tài)是否可用,不能用,我們?cè)侔l(fā)起請(qǐng)求。然后還可以提供一個(gè)類(lèi)似session.login({ force: true })的參數(shù)去強(qiáng)行發(fā)起登錄。
1. 應(yīng)用啟動(dòng)的時(shí)候調(diào)用
因?yàn)榇蟛糠智闆r都需要依賴(lài)登錄態(tài),我們會(huì)很自然而然的想到把這個(gè)調(diào)用的時(shí)機(jī)放到應(yīng)用啟動(dòng)的時(shí)候(app.onLaunch() )來(lái)調(diào)用。
但是由于原生的小程序啟動(dòng)流程中,App,Page,Component 的生命周期鉤子函數(shù),都不支持異步阻塞。
那么我們很容易會(huì)遇到app.onLaunch 發(fā)起的「登錄過(guò)程」在page.onLoad 的時(shí)候還沒(méi)有完成,我們就無(wú)法正確去做一些依賴(lài)登錄態(tài)的操作。
針對(duì)這種情況,我們?cè)O(shè)計(jì)了一個(gè)狀態(tài)機(jī)的工具:status

基于狀態(tài)機(jī),我們就可以編寫(xiě)這樣的代碼:
import { Status } from '@beautywe/plugin-status';// on app.jsApp({ status: { login: new Status('login');
}, onLaunch() {
session // 發(fā)起靜默登錄調(diào)用
.login() // 把狀態(tài)機(jī)設(shè)置為 success
.then(() => this.status.login.success())
// 把狀態(tài)機(jī)設(shè)置為 fail
.catch(() => this.status.login.fail());
},
});// on page.jsPage({ onLoad() { const loginStatus = getApp().status.login;
// must 里面會(huì)進(jìn)行狀態(tài)的判斷,例如登錄中就等待,登錄成功就直接返回,登錄失敗拋出等。
loginStatus().status.login.must(() => { // 進(jìn)行一些需要登錄態(tài)的操作...
});
},
});復(fù)制代碼2. 在「第一個(gè)需要登錄態(tài)接口」被調(diào)用的時(shí)候去發(fā)起登錄
更進(jìn)一步,我們會(huì)發(fā)現(xiàn),需要登錄態(tài)的更深層次的節(jié)點(diǎn)是在發(fā)起的「需要登錄態(tài)的后端 API 」的時(shí)候。
那么我們可以在調(diào)用「需要登錄態(tài)的后端 API」的時(shí)候再去發(fā)起「靜默登錄」,對(duì)于并發(fā)的場(chǎng)景,讓其他請(qǐng)求等待一下就好了。
以 fly.js 作為wx.request() 封裝的「網(wǎng)絡(luò)請(qǐng)求層」,做一個(gè)簡(jiǎn)單的例子:
// 發(fā)起請(qǐng)求,并表明該請(qǐng)求是需要登錄態(tài)的fly.post('https://...', params, { needLogin: true });// 在 fly 攔截器中處理邏輯fly.interceptors.request.use(async (req)=>{ // 在請(qǐng)求需要登錄態(tài)的時(shí)候
if (req.needLogin !== false) { // ensureLogin 核心邏輯是:判斷是否已登錄,如否發(fā)起登錄調(diào)用,如果正在登錄,則進(jìn)入隊(duì)列等待回調(diào)。
await session.ensureLogin();
// 登錄成功后,獲取 token,通過(guò) headers 傳遞給后端。
const token = await session.getToken(); Object.assign(req.headers, { [AUTH_KEY_NAME]: token });
}
return req;
});復(fù)制代碼當(dāng)自定義登錄態(tài)過(guò)期的時(shí)候,后端需要返回特定的狀態(tài)碼,例如:AUTH_EXPIRED 、AUTH_INVALID 等。
前端可以在「網(wǎng)絡(luò)請(qǐng)求層」去監(jiān)聽(tīng)所有請(qǐng)求的這個(gè)狀態(tài)碼,然后發(fā)起刷新登錄態(tài),再去重放失敗的請(qǐng)求:
// 添加響應(yīng)攔截器fly.interceptors.response.use( (response) => { const code = res.data;
// 登錄態(tài)過(guò)期或失效
if ( ['AUTH_EXPIRED', 'AUTH_INVALID'].includes(code) ) {
// 刷新登錄態(tài)
await session.refreshLogin();
// 然后重新發(fā)起請(qǐng)求
return fly.request(request);
}
}
)復(fù)制代碼那么如果并發(fā)的發(fā)起多個(gè)請(qǐng)求,都返回了登錄態(tài)失效的狀態(tài)碼,上述代碼就會(huì)被執(zhí)行多次。
我們需要對(duì)session.refreshLogin() 做一些特殊的容錯(cuò)處理:
請(qǐng)求鎖:同一時(shí)間,只允許一個(gè)正在過(guò)程中的網(wǎng)絡(luò)請(qǐng)求。
等待隊(duì)列:請(qǐng)求被鎖定之后,調(diào)用該方法的所有調(diào)用,都推入一個(gè)隊(duì)列中,等待網(wǎng)絡(luò)請(qǐng)求完成之后共用返回結(jié)果。
熔斷機(jī)制:如果短時(shí)間內(nèi)多次調(diào)用,則停止響應(yīng)一段時(shí)間,類(lèi)似于 TCP 慢啟動(dòng)。
示例代碼:
class Session { // ....
// 刷新登錄保險(xiǎn)絲,最多重復(fù) 3 次,然后熔斷,5s 后恢復(fù)
refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT;
refreshLoginFuseLocked = false;
refreshLoginFuseRestoreTime = 5000; // 熔斷控制
refreshLoginFuse(): Promise<void> { if (this.refreshLoginFuseLocked) { return Promise.reject('刷新登錄-保險(xiǎn)絲已熔斷,請(qǐng)稍后');
} if (this.refreshLoginFuseLine > 0) { this.refreshLoginFuseLine = this.refreshLoginFuseLine - 1; return Promise.resolve();
} else { this.refreshLoginFuseLocked = true; setTimeout(() => { this.refreshLoginFuseLocked = false; this.refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT;
logger.info('刷新登錄-保險(xiǎn)絲熔斷解除');
}, this.refreshLoginFuseRestoreTime); return Promise.reject('刷新登錄-保險(xiǎn)絲熔斷!!');
}
} // 并發(fā)回調(diào)隊(duì)列
refreshLoginQueueMaxLength = 100;
refreshLoginQueue: any[] = [];
refreshLoginLocked = false; // 刷新登錄態(tài)
refreshLogin(): Promise<void> { return Promise.resolve()
// 回調(diào)隊(duì)列 + 熔斷 控制
.then(() => this.refreshLoginFuse())
.then(() => { if (this.refreshLoginLocked) { const maxLength = this.refreshLoginQueueMaxLength; if (this.refreshLoginQueue.length >= maxLength) { return Promise.reject(`refreshLoginQueue 超出容量:${maxLength}`);
} return new Promise((resolve, reject) => { this.refreshLoginQueue.push([resolve, reject]);
});
} this.refreshLoginLocked = true;
}) // 通過(guò)前置控制之后,發(fā)起登錄過(guò)程
.then(() => { this.clearSession();
wx.showLoading({ title: '刷新登錄態(tài)中', mask: true }); return this.login()
.then(() => {
wx.hideLoading();
wx.showToast({ icon: 'none', title: '登錄成功' }); this.refreshLoginQueue.forEach(([resolve]) => resolve()); this.refreshLoginLocked = false;
})
.catch(err => {
wx.hideLoading();
wx.showToast({ icon: 'none', title: '登錄失敗' }); this.refreshLoginQueue.forEach(([, reject]) => reject()); this.refreshLoginLocked = false; throw err;
});
}); // ...}復(fù)制代碼我們從上面的「靜默登錄」之后,微信服務(wù)器端會(huì)下發(fā)一個(gè)session_key 給后端,而這個(gè)會(huì)在需要獲取微信開(kāi)放數(shù)據(jù)的時(shí)候會(huì)用到。

而session_key 是有時(shí)效性的,以下摘自微信官方描述:
會(huì)話(huà)密鑰 session_key 有效性
開(kāi)發(fā)者如果遇到因?yàn)?session_key 不正確而校驗(yàn)簽名失敗或解密失敗,請(qǐng)關(guān)注下面幾個(gè)與 session_key 有關(guān)的注意事項(xiàng)。
wx.login 調(diào)用時(shí),用戶(hù)的 session_key可能會(huì)被更新而致使舊 session_key 失效(刷新機(jī)制存在短周期,如果同一個(gè)用戶(hù)短時(shí)間內(nèi)多次調(diào)用 wx.login,并非每次調(diào)用都導(dǎo)致 session_key 刷新)。開(kāi)發(fā)者應(yīng)該在明確需要重新登錄時(shí)才調(diào)用 wx.login,及時(shí)通過(guò) auth.code2Session 接口更新服務(wù)器存儲(chǔ)的 session_key。
微信不會(huì)把 session_key 的有效期告知開(kāi)發(fā)者。我們會(huì)根據(jù)用戶(hù)使用小程序的行為對(duì) session_key 進(jìn)行續(xù)期。用戶(hù)越頻繁使用小程序,session_key 有效期越長(zhǎng)。
開(kāi)發(fā)者在 session_key 失效時(shí),可以通過(guò)重新執(zhí)行登錄流程獲取有效的 session_key。使用接口 wx.checkSession可以校驗(yàn) session_key 是否有效,從而避免小程序反復(fù)執(zhí)行登錄流程。
當(dāng)開(kāi)發(fā)者在實(shí)現(xiàn)自定義登錄態(tài)時(shí),可以考慮以 session_key 有效期作為自身登錄態(tài)有效期,也可以實(shí)現(xiàn)自定義的時(shí)效性策略。
翻譯成簡(jiǎn)單的兩句話(huà):
session_key 時(shí)效性由微信控制,開(kāi)發(fā)者不可預(yù)測(cè)。
wx.login 可能會(huì)導(dǎo)致session_key 過(guò)期,可以在使用接口之前用 wx.checkSession 檢查一下。
而對(duì)于第二點(diǎn),我們通過(guò)實(shí)驗(yàn)發(fā)現(xiàn),偶發(fā)性的在session_key 已過(guò)期的情況下,wx.checkSession 會(huì)概率性返回true
社區(qū)也有相關(guān)的反饋未得到解決:
小程序解密手機(jī)號(hào),隔一小段時(shí)間后,checksession:ok,但是解密失敗
wx.checkSession有效,但是解密數(shù)據(jù)失敗
checkSession判斷session_key未失效,但是解密手機(jī)號(hào)失敗
所以結(jié)論是:wx.checkSession可靠性是不達(dá) 100% 的。
基于以上,我們需要對(duì)session_key 的過(guò)期做一些容錯(cuò)處理:
發(fā)起需要使用session_key 的請(qǐng)求前,做一次wx.checkSession 操作,如果失敗了刷新登錄態(tài)。
后端使用session_key 解密開(kāi)放數(shù)據(jù)失敗之后,返回特定錯(cuò)誤碼(如:DECRYPT_WX_OPEN_DATA_FAIL),前端刷新登錄態(tài)。
示例代碼:
// 定義檢查 session_key 有效性的操作const ensureSessionKey = async () => { const hasSession = await new Promise(resolve => {
wx.checkSession({ success: () => resolve(true), fail: () => resolve(false),
});
});
if (!hasSession) {
logger.info('sessionKey 已過(guò)期,刷新登錄態(tài)'); // 接上面提到的刷新登錄邏輯
return session.refreshLogin();
} return Promise.resolve();
}// 在發(fā)起請(qǐng)求的時(shí)候,先做一次確保 session_key 新的操作(以 fly.js 作為網(wǎng)絡(luò)請(qǐng)求層為例)const updatePhone = async (params) => { await ensureSessionKey(); const res = await fly.post('https://xxx', params);
}// 添加響應(yīng)攔截器, 監(jiān)聽(tīng)網(wǎng)絡(luò)請(qǐng)求返回fly.interceptors.response.use( (response) => { const code = res.data;
// 登錄態(tài)過(guò)期或失效
if ( ['DECRYPT_WX_OPEN_DATA_FAIL'].includes(code)) { // 刷新登錄態(tài)
await session.refreshLogin();
// 由于加密場(chǎng)景的加密數(shù)據(jù)由用戶(hù)點(diǎn)擊產(chǎn)生,session_key 可能已經(jīng)更改,需要用戶(hù)重新點(diǎn)擊一遍。
wx.showToast({ title: '網(wǎng)絡(luò)出小差了,請(qǐng)稍后重試', icon: 'none' });
}
}
)在用戶(hù)信息和手機(jī)號(hào)獲取的方式上,微信是以<button open-type='xxx' /> 的方式,讓用戶(hù)主動(dòng)點(diǎn)擊授權(quán)的。
那么為了讓代碼更解耦,我們?cè)O(shè)計(jì)這樣三個(gè)組件:
<user-contaienr getUserInfo="onUserInfoAuth">: 包裝點(diǎn)擊交互,通過(guò)<slot> 支持點(diǎn)擊區(qū)域的自定義UI。
<phone-container getPhonenNmber="onPhoneAuth"> : 與<user-container> 同理。
<auth-flow>: 根據(jù)業(yè)務(wù)需要,組合<user-container>、<phone-container> 組合來(lái)定義不同的授權(quán)流程。
以開(kāi)頭的業(yè)務(wù)場(chǎng)景的流程為例,它有這樣的要求:
有多個(gè)步驟。
如果中途斷掉了,可以從中間接上。
有些場(chǎng)景中,只要求達(dá)到「用戶(hù)信息授權(quán)」,而不需要完成「用戶(hù)手機(jī)號(hào)」。

那么授權(quán)的階段可以分三層:
// 用戶(hù)登錄的階段export enum AuthStep { // 階段一:只有登錄態(tài),沒(méi)有用戶(hù)信息,沒(méi)有手機(jī)號(hào)
ONE = 1, // 階段二:有用戶(hù)信息,沒(méi)有手機(jī)號(hào)
TWO = 2, // 階段三:有用戶(hù)信息,有手機(jī)號(hào)
THREE = 3,
}復(fù)制代碼AuthStep 的推進(jìn)過(guò)程是不可逆的,我們可以定義一個(gè)nextStep 函數(shù)來(lái)封裝 AuthStep 更新的邏輯。外部使用的話(huà),只要無(wú)腦調(diào)用nextStep 方法,等待回調(diào)結(jié)果就行。
示例偽代碼:
// auth-flow componentComponent({ // ...
data: { // 默認(rèn)情況下,只需要到達(dá)階段二。
mustAuthStep: AuthStep.TWO
},
// 允許臨時(shí)更改組件的需要達(dá)到的階段。
setMustAuthStep(mustAuthStep: AuthStep) { this.setData({ mustAuthStep });
},
// 根據(jù)用戶(hù)當(dāng)前的信息,計(jì)算用戶(hù)處在授權(quán)的階段
getAuthStep() { let currAuthStep;
// 沒(méi)有用戶(hù)信息,尚在第一步
if (!session.hasUser() || !session.hasUnionId()) {
currAuthStep = AuthStepType.ONE;
} // 沒(méi)有手機(jī)號(hào),尚在第二步
if (!session.hasPhone()) {
currAuthStep = AuthStepType.TWO;
} // 都有,尚在第三步
currAuthStep = AuthStepType.THREE; return currAuthStep;
}
// 發(fā)起下一步授權(quán),如果都已經(jīng)完成,就直接返回成功。
nextStep(e) { const { mustAuthStep } = this.data; const currAuthStep = this.updateAuthStep();
// 已完成授權(quán)
if (currAuthStep >= mustAuthStep || currAuthStep === AuthStepType.THREE) { // 更新全局的授權(quán)狀態(tài)機(jī),廣播消息給訂閱者。
return getApp().status.auth.success();
} // 第一步:更新用戶(hù)信息
if (currAuthStep === AuthStepType.ONE) { // 已有密文信息,更新用戶(hù)信息
if (e) session.updateUser(e); // 更新到視圖層,展示對(duì)應(yīng)UI,等待獲取用戶(hù)信息
else this.setData({ currAuthStep }); return;
} // 第二步:更新手機(jī)信息
if (currAuthStep === AuthStepType.TWO) { // 已有密文信息,更新手機(jī)號(hào)
if (e) this.bindPhone(e); // 未有密文信息,彈出獲取窗口
else this.setData({ currAuthStep }); return;
} console.warn('auth.nextStep 錯(cuò)誤', { currAuthStep, mustAuthStep });
},
// ...});復(fù)制代碼那么我們的<auth-flow> 中就可以根據(jù)currAuthStep 和mustAuthStep 來(lái)去做不同的 UI 展示。需要注意的是使用<user-container>、<phone-container> 的時(shí)候連接上nextStep(e) 函數(shù)。
示例偽代碼:
<view class="auth-flow">
<!-- 已完成授權(quán) -->
<block wx:if="{{currAuthStep === mustAuthStep || currAuthStep === AuthStep.THREE}}">
<view>已完成授權(quán)</view>
</block>
<!-- 未完成授權(quán),第一步:授權(quán)用戶(hù)信息 -->
<block wx:elif="{{currAuthStep === AuthStep.ONE}}">
<user-container bind:getuserinfo="nextStep">
<view>授權(quán)用戶(hù)信息</view>
</user-container>
</block>
<!-- 未完成授權(quán),第二步:授權(quán)手機(jī)號(hào) -->
<block wx:elif="{{currAuthStep === AuthStep.TWO}}">
<phone-container bind:getphonenumber="nextStep">
<view>授權(quán)手機(jī)號(hào)</view>
</phone-container>
</block>
</view>復(fù)制代碼到這里,我們制作好了用來(lái)承載授權(quán)流程的組件<auth-flow> ,那么接下來(lái)就是決定要使用它的時(shí)機(jī)了。
我們梳理需要授權(quán)的場(chǎng)景:
點(diǎn)擊某個(gè)按鈕,例如:購(gòu)買(mǎi)某個(gè)商品。
對(duì)于這種場(chǎng)景,常見(jiàn)的是通過(guò)彈窗完成授權(quán),用戶(hù)可以選擇關(guān)閉。

瀏覽某個(gè)頁(yè)面,例如:訪問(wèn)個(gè)人中心。
對(duì)于這種場(chǎng)景,我們可以在點(diǎn)擊跳轉(zhuǎn)某個(gè)頁(yè)面的時(shí)候,進(jìn)行攔截,彈窗處理。但這樣的缺點(diǎn)是,跳轉(zhuǎn)到目標(biāo)頁(yè)面的地方可能會(huì)很多,每個(gè)都攔截,難免會(huì)錯(cuò)漏。而且當(dāng)目標(biāo)頁(yè)面作為「小程序落地頁(yè)面」的時(shí)候,就避免不了。
這時(shí)候,我們可以通過(guò)重定向到授權(quán)頁(yè)面來(lái)完成授權(quán)流程,完成之后,再回來(lái)。

那么我們定義一個(gè)枚舉變量:
// 授權(quán)的展示形式export enum AuthDisplayMode { // 以彈窗形式
POPUP = 'button', // 以頁(yè)面形式
PAGE = 'page',
}復(fù)制代碼我們可以設(shè)計(jì)一個(gè)mustAuth 方法,在點(diǎn)擊某個(gè)按鈕,或者頁(yè)面加載的時(shí)候,進(jìn)行授權(quán)控制。
偽代碼示例:
class Session { // ...
mustAuth({
mustAuthStep = AuthStepType.TWO, // 需要授權(quán)的LEVEL,默認(rèn)需要獲取用戶(hù)資料
popupCompName = 'auth-popup', // 授權(quán)彈窗組件的 id
mode = AuthDisplayMode.POPUP, // 默認(rèn)以彈窗模式
} = {}): Promise<void> {
// 如果當(dāng)前的授權(quán)步驟已經(jīng)達(dá)標(biāo),則返回成功
if (this.currentAuthStep() >= mustAuthStep) return Promise.resolve(); // 嘗試獲取當(dāng)前頁(yè)面的 <auth-popup id="auth-popup" /> 組件實(shí)例
const pages = getCurrentPages(); const curPage = pages[pages.length - 1]; const popupComp = curPage.selectComponent(`#${popupCompName}`); // 組件不存在或者顯示指定頁(yè)面,跳轉(zhuǎn)到授權(quán)頁(yè)面
if (!popupComp || mode === AuthDisplayMode.PAGE) { const curRoute = curPage.route; // 跳轉(zhuǎn)到授權(quán)頁(yè)面,帶上當(dāng)前頁(yè)面路由,授權(quán)完成之后,回到當(dāng)前頁(yè)面。
wx.redirectTo({ url: `authPage?backTo=${encodeURIComponent(curRoute)}` }); return Promise.resolve();
}
// 設(shè)置授權(quán) LEVEL,然后調(diào)用 <auth-popup> 的 nextStep 方法,進(jìn)行進(jìn)一步的授權(quán)。
popupComp.setMustAuthStep(mustAuthStep);
popupComp.nextStep(); // 等待成功回調(diào)或者失敗回調(diào)
return new Promise((resolve, reject) => { const authStatus = getApp().status.auth;
authStatus.onceSuccess(resolve);
authStatus.onceFail(reject);
});
}
// ...}復(fù)制代碼那么我們就能在按鈕點(diǎn)擊,或者頁(yè)面加載的時(shí)候進(jìn)行授權(quán)攔截:
Page({ onLoad() {
session.mustAuth().then(() => { // 開(kāi)始初始化頁(yè)面...
});
}
onClick(e) {
session.mustAuth().then(() => { // 開(kāi)始處理回調(diào)邏輯...
});
}
})復(fù)制代碼當(dāng)然,如果項(xiàng)目使用了 TS 的話(huà),或者支持 ES7 Decorator 特性的話(huà),我們可以為mustAuth 提供一個(gè)裝飾器版本:
export function mustAuth(option = {}) { return function(
_target,
_propertyName,
descriptor, ) { // 劫持目標(biāo)方法
const method = descriptor.value;
// 重寫(xiě)目標(biāo)方法
descriptor.value = function(...args: any[]) { return session.mustAuth(option).then(() => { // 登錄完成之后,重放原來(lái)方法
if (method) return method.apply(this, args);
});
};
};
}復(fù)制代碼那么使用方式就簡(jiǎn)單一些了:
Page({
@mustAuth(); onLoad() { // 開(kāi)始初始化頁(yè)面...
}
@mustAuth(); onClick(e) { // 開(kāi)始處理回調(diào)邏輯...
}
});復(fù)制代碼作為一套可復(fù)用的小程序登錄方案,當(dāng)然需要去定義好前后端的交互協(xié)議。
那么整套登錄流程下來(lái),需要的接口有這么幾個(gè):

靜默登錄 silentLogin
后端利用 code 跟微信客戶(hù)端換取用戶(hù)標(biāo)識(shí),然后注冊(cè)并登錄用戶(hù),返回自定義登錄態(tài)token 給前端
token 前端會(huì)存起來(lái),每個(gè)請(qǐng)求都會(huì)帶上
userInfo 需要包含nickname和phone字段,前端用于計(jì)算當(dāng)前用戶(hù)的授權(quán)階段。當(dāng)然這個(gè)狀態(tài)的記錄可以放在后端,但是我們認(rèn)為放在前端,會(huì)更加靈活。
token: 自定義登錄態(tài)憑證
userInfo: 用戶(hù)信息
code: 產(chǎn)自 wx.login()
入?yún)ⅲ?/p>
出參:
說(shuō)明:
更新用戶(hù)信息 updateUser
后端解密微信開(kāi)放數(shù)據(jù),獲取隱蔽數(shù)據(jù),如:unionId等
后端支持更新包括nickname等用戶(hù)基本信息。
前端會(huì)把 userInfo 信息更新到session 中,用于計(jì)算授權(quán)階段。
userInfo:更新后的新用戶(hù)信息
nickname: 用戶(hù)昵稱(chēng)
encrypt: 微信開(kāi)放數(shù)據(jù)相關(guān)的iv,encryptedData
以及其他如性別地址等非必要字段
入?yún)ⅲ?/p>
出參:
說(shuō)明:
更新用戶(hù)手機(jī)號(hào) updatePhone
后端解密開(kāi)放式局,獲取手機(jī)號(hào),并更新到用戶(hù)信息中。
前端會(huì)把 userInfo 信息更新到session 中,用于計(jì)算授權(quán)階段。
userInfo:更新后的新用戶(hù)信息
encrypt:微信開(kāi)放數(shù)據(jù)相關(guān)的iv,encryptedData
入?yún)ⅲ?/p>
出參:
說(shuō)明:
解綁手機(jī)號(hào) unbindPhone
入?yún)ⅲ?
出參:-
說(shuō)明:后端解綁用戶(hù)手機(jī)號(hào),成功與否,走業(yè)務(wù)定義的前后端協(xié)議。
登錄 logout
入?yún)ⅲ?
出參:-
說(shuō)明:后端主動(dòng)過(guò)期登錄態(tài),成功與否,走業(yè)務(wù)定義的前后端協(xié)議。
最后我們來(lái)梳理一下整體的「登錄服務(wù)」的架構(gòu)圖:

由「登錄服務(wù)」和「底層建設(shè)」組合提供的通用服務(wù),業(yè)務(wù)層只需要去根據(jù)產(chǎn)品需求,定制授權(quán)的流程<auth-flow> ,就能滿(mǎn)足大部分場(chǎng)景了。
本篇文章通過(guò)一些常見(jiàn)的登錄授權(quán)場(chǎng)景來(lái)展開(kāi)來(lái)描述細(xì)節(jié)點(diǎn)。
整理了「登錄」、「授權(quán)」的概念。
然后分別針對(duì)「登錄」介紹了一些關(guān)鍵的技術(shù)實(shí)現(xiàn):
靜默登錄
靜默登錄異步狀態(tài)的處理
自定義登錄態(tài)過(guò)期的容錯(cuò)處理
微信session_key 過(guò)期的容錯(cuò)處理
而對(duì)于「授權(quán)」,會(huì)有設(shè)計(jì)UI部分的邏輯,還需要涉及到組件的拆分:
組件拆分與設(shè)計(jì)
權(quán)限攔截的處理
然后,梳理了這套登錄授權(quán)方案所依賴(lài)的后端接口,和給出最簡(jiǎn)單的參考協(xié)議。
最后,站在「秉著沉淀一套通用的小程序登錄方案和服務(wù)為目標(biāo)」的角度,梳理了一下架構(gòu)層面上的分層。
業(yè)務(wù)定制層
登錄服務(wù)層
底層建設(shè)
以上是“微信小程序登錄前端設(shè)計(jì)與實(shí)現(xiàn)的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
網(wǎng)頁(yè)標(biāo)題:微信小程序登錄前端設(shè)計(jì)與實(shí)現(xiàn)的示例分析-創(chuàng)新互聯(lián)
當(dāng)前地址:http://chinadenli.net/article32/diiosc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、響應(yīng)式網(wǎng)站、網(wǎng)站設(shè)計(jì)公司、網(wǎng)站收錄、建站公司、虛擬主機(jī)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容