小程序的登錄與靜默續期


每一個有數據交互的小程序,都會涉及到登錄、token 等問題,openid 又是什么呢?怎么使用靜默續期,來提升用戶體驗呢?

小程序登錄


登錄時序

一切的一切,都要從這么一張小程序登錄時序圖說起:

通常情況下,我們的小程序都會有業務身份,如何將微信帳號和業務身份關聯起來呢?這個時候我們需要上圖的步驟:

  1. 小程序調用wx.login()獲取臨時登錄憑證code
  2. 小程序將code傳到開發者服務器。
  3. 開發者服務器以code換取用戶唯一標識openid和會話密鑰session_key
  4. 開發者服務器可綁定微信用戶身份id和業務用戶身份。
  5. 開發者服務器可以根據用戶標識來生成自定義登錄態,用於后續業務邏輯中前后端交互時識別用戶身份。

相關數據或參數

上面的登錄時序中,我們會涉及到一些數據和參數,先來了解下它們都是用來做啥的。

臨時登錄憑證 code
在小程序中調用wx.login(),能拿到一個code作為用戶登錄憑證(有效期五分鍾)。在開發者服務器后台,開發者可使用code換取openidsession_key等信息(code只能使用一次)。

code的設計,主要用於防止黑客使用窮舉等方式把業務側個人信息數據全拉走。

AppId 與 AppSecret
為了確保拿code過來換取身份信息的人就是對應的小程序開發者,到微信服務器的請求要同時帶上AppIdAppSecret

session_key
會話密鑰session_key是對用戶數據進行加密簽名的密鑰。為了應用自身的數據安全,開發者服務器不應該把會話密鑰下發到小程序,也不應該對外提供這個密鑰。

設計session_key主要是為了節省流程消耗,如果每次都通過小程序前端wx.login()生成微信登錄憑證code去微信服務器請求信息,步驟太多會造成整體耗時比較嚴重。

使用接口wx.checkSession()可以校驗session_key是否有效。用戶越頻繁使用小程序,session_key有效期越長。session_key失效時,可以通過重新執行登錄流程獲取有效的session_key

openid
openid是微信用戶id,可以用這個id來區分不同的微信用戶。
微信針對不同的用戶在不同的應用下都有唯一的一個openid, 但是要想確定用戶是不是同一個用戶,就需要靠unionid來區分。

unionid
如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程序),可通過unionid來區分用戶的唯一性。同一用戶,對同一個微信開放平台下的不同應用,unionid是相同的。

加鎖的登錄

在某些情況下,我們或許多個地方會同時觸發登錄邏輯(如多個接口同時拉取,發現登錄態過期的情況)。一般來說,我們會簡單地給請求加個鎖來解決:

    1. 使用isLogining來標志是否請求中。
    2. 方法返回 Promise,登錄態過期時靜默續期后重新發起。
    3. 使用sessionId來記錄業務側的登錄態。
// session 參數 key(后台吐回)
        export const SESSION_KEY = 'sessionId';
        let isLogining = false;
        export function doLogin() {
            return new Promise((resolve, reject) => {
                const session = wx.getStorageSync(SESSION_KEY);
                if (session) {
// 緩存中有 session
                    resolve();
                } else if (isLogining) {
// 正在登錄中,請求輪詢稍后,避免重復調用登錄接口
                    setTimeout(() => {
                        doLogin()
                            .then(res => {
                                resolve(res);
                            })
                            .catch(err => {
                                reject(err);
                            });
                    }, 500);
                } else {
                    isLogining = true;
                    wx.login({
                        success: (res) => {
                            if (res.code) {
                                const reqData: ILoginRequest = {
                                    code: res.code
                                }
                                wx.request({
                                    url: API.login,
                                    data: reqData,
// method: "POST",
                                    success: (resp) => {
                                        const data = resp.data;
                                        isLogining = false;
// 保存登錄態
                                        if (data.return_code === 0) {
                                            wx.setStorageSync(SESSION_KEY, data[SESSION_KEY]);
                                            resolve();
                                        } else {
                                            reject(data.return_msg);
                                        }
                                    },
                                    fail: err => {
// 登錄失敗,解除鎖,防止死鎖
                                        isLogining = false;
                                        reject(err);
                                    }
                                });
                            } else {
// 登錄失敗,解除鎖,防止死鎖
                                isLogining = false;
                                reject();
                            }
                        },
                        fail: (err) => {
// 登錄失敗,解除鎖,防止死鎖
                            isLogining = false;
                            reject(err);
                        }
                    });
                }
            });
        }

  


登錄態靜默續期的實現

checkSession

前面也提到,微信不會把session_key的有效期告知開發者,因此需要使用接口wx.checkSession()來校驗session_key是否有效。

這里我們:

  1. 使用isCheckingSession來標志是否查詢中。
  2. 返回 Promise。
  3. 使用isSessionFresh來標志session_key是否有效。
import {doLogin} from "./doLogin";
        import {SESSION_KEY} from "./doLogin";
        let isCheckingSession = false;
        let isSessionFresh = false;
        export function checkSession(): Promise<string> {
            return new Promise((resolve, reject) => {
                const session = wx.getStorageSync(SESSION_KEY);
                if (isCheckingSession) {
                    setTimeout(() => {
                        checkSession().then(res => {
                            resolve(res);
                        }).catch(err => {
                            reject(err);
                        });
                    }, 500);
                } else if (!isSessionFresh && session) {
                    isCheckingSession = true;
                    wx.checkSession({
                        success: () => {
                            // session_key 未過期,並且在本生命周期一直有效
                            isSessionFresh = true;
                            resolve();
                        }, fail: () => {
                            // session_key 已經失效,需要重新執行登錄流程
                            wx.removeStorage({
                                key: "skey", complete: () => {
                                    doLogin().then(() => {
                                        resolve();
                                    }).catch(err => {
                                        reject(err);
                                    });
                                }
                            });
                        }, complete: () => {
                            isCheckingSession = false;
                        }
                    });
                } else {
                    doLogin().then(res => {
                        resolve(res);
                    }).catch(err => {
                        reject(err);
                    });
                }
            });
        }
        }
        }

  

靜默續期的接口請求

至此,我們可以封裝一個簡單的接口,來在每次登錄態過期的時候自動續期:

  1. 在請求前,使用checkSession()檢車本次周期內session_key是否有效,無效則doLogin()拉起登錄獲取sessionId
  2. 請求接口,若返回特定登錄態失效錯誤碼(此處假設為LOGIN_FAIL_CODE),則doLogin()拉起登錄獲取sessionId
  3. 使用tryLoginCount來標志重試次數,TRY_LOGIN_LIMIT來標志重試次數上限,避免進入死循環。
import {doLogin} from "./doLogin";
        import {SESSION_KEY} from "./doLogin";
        import {checkSession} from "./checkSession";
        // 會話過期錯誤碼,需要重新登錄
        export const LOGIN_FAIL_CODES = [10000];
        const TRY_LOGIN_LIMIT = 3;
        export function request(obj: any = {}): Promise<object> {
            return new Promise((resolve, reject) => {
                checkSession().then(() => {
                    let session = wx.getStorageSync(SESSION_KEY);
                    const {url, data, method, header, dataType} = obj;
                    let tryLoginCount = obj.tryLoginCount || 0;
                    // 如果需要通過 data 把登錄態 sessionId 帶上const 
                    dataWithSession = {...data, [SESSION_KEY]: session, appid: APPID};
                    wx.request({
                        url, data: dataWithSession, method, header, dataType, success: (res: any) => {
                            if (res.statusCode === 200) {
                                const data: ICommonResponse = res.data;
                                // 登陸態失效特定錯誤碼判斷,且重試次數未達到上限
                                if (LOGIN_FAIL_CODES.indexOf(data.return_code) > -1 && tryLoginCount < TRY_LOGIN_LIMIT) {
                                    doLogin().then(() => {
                                        obj.tryLoginCount = ++tryLoginCount;
                                        request(obj).then(res => {
                                            resolve(res);
                                        }).catch(err => {
                                            reject(err);
                                        });
                                    });
                                } else {
                                    resolve(res);
                                }
                            } else {
                                reject(res);
                            }
                        }, fail: function (err) {
                            reject(err);
                        }
                    });
                }).catch(err => {
                    reject(err);
                });
            });
        }

  

至此,我們大概包裝了一個能自動登錄或是進行靜默續期的一個請求接口。

參考

結束語


小程序的登錄和登錄態管理,大概是大部分小程序都需要的能力。codesession_key的設計,做了哪些事情來保護用戶的數據。
如何在全局范圍地保證登錄態的有效性,微信側的登錄態也好,業務側的登錄態也好,靜默續期的能力能給用戶帶來不少的體驗提升。

查看Github有更多內容噢:https://github.com/godbasin

本文轉載自:https://godbasin.github.io,如有侵權,請聯系作者。

 

【PHPer技術棧】專注后端開發,倡導開源文化,做一個好玩、有趣、有靈魂的PHPer工程師,歡迎大家關注!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM