健壯高效的小程式登入方案

發表於2018-10-12

健壯高效的小程式登入方案

登入是一項核心基礎功能,通過登入對使用者進行唯一標識,繼而才可以提供各種跟蹤服務,如收藏、下單、留言、訊息、釋出、個性化推薦等。小程式功能的方方面面大多會直接/間接涉及登入,因而,登入功能健壯與否高效與否是值得重點關注與保障的。

登入涉及的面比較多:觸發場景上,各種頁面各種互動路徑都可能觸發登入;互動過程上,既需要使用者提供/證明id,也需要後端記錄維護,還需要保證安全性;複用場景上,既是通用功能,需要多場景多頁面甚至多小程式複用,又是定製功能,需要各場景/頁面/小程式區分處理。要做到各種情形下都有良好的互動體驗,且健壯、高效、可複用、可擴充套件、可維護,還是相對比較複雜的。

本文將探討小程式登入過程中的一些主要需求和問題,以漸進迭代的方式提出並實現一個健壯、高效的登入方案。

順帶一提,es6語法中的async/await、Promise、decorator等特性對於複雜時序處理相當有增益,在本文中也會有所體現。

基礎流程

如上圖所示,基礎登入流程為:

  • 呼叫微信登入介面wx.login獲取微信登入態
  • 呼叫微信使用者資訊介面wx.getUserInfo獲取微信使用者資訊
  • 呼叫後端登入介面,根據微信使用者標識及資訊,記錄維護自己的使用者體系

該流程主要基於以下考慮:

  • 互動上,使用者只需在微信的授權彈窗上點選確認,不需要輸入賬號密碼等複雜操作;
  • 體驗上,可以直接獲取微信暱稱頭像等作為初始使用者資訊,使用起來更親切,傳播時好友辨識度也更高;
  • 開發上,可以直接使用或對映微信使用者標識,無需自己進行標識的生成和驗證;
  • 安全上,微信已經在使用者資訊的獲取、傳輸、解密等環節做了許多處理,安全性相對有保障。

健壯流程

拒絕授權問題

問題:

獲取微信使用者資訊時,會出現一個授權彈窗,需要使用者點選“允許”才能正常獲取;
若使用者點選“拒絕”,不僅當次登入會失敗,一定時間內後續登入也會失敗,因為短期內再次呼叫微信使用者資訊介面時,微信不會再向使用者展示授權彈窗,而是直接按失敗返回。
這樣導致使用者只要拒絕過一次,即使後來對小程式感興趣了願意授權了,也難以再次操作。

方案:

健壯高效的小程式登入方案

如上圖所示,增加以下流程以處理拒絕授權問題:

  • 獲取微信使用者資訊失敗時,判斷是否近期內拒絕授權導致;
  • 若為拒絕授權導致,則提示並開啟許可權皮膚,供使用者再次操作;
  • 若使用者依然未授權,則本次登入失敗,否則繼續後續流程。

這樣,使用者拒絕授權只會影響本次登入,不至於無法進行下次嘗試。

登入態過期問題

問題:

  • 微信登入態有效期不可控健壯高效的小程式登入方案
    上圖截自微信官方文件,從中可以看出:
    • 後端session_key隨時可能失效,什麼時候失效開發者不可控;
    • 要保證呼叫介面時後端session_key不失效,只能在每次呼叫前先使用wx.checkSession檢查有效期或直接重新執行微信登入介面;
    • 前端不能隨便重新執行微信登入介面,可能導致正在進行的其它後端任務session_key失效;此外,實踐中發現,wx.checkSession平均耗時約需200ms,每次介面呼叫前都先檢查一遍,開銷還是蠻大的。如何既保證介面功能正確有效,又不用每次耗費高額的查詢開銷,成為了一個問題。
  • 後端登入態過期
    後端自身的登入態有效期也存在類似的問題,有可能在呼叫介面時才發現後端登入態已過期。

方案:

健壯高效的小程式登入方案

如上圖所示,增加以下流程以處理登入態過期問題:

  • 呼叫資料介面時顯式指明是否需要登入態,若需要則在介面呼叫前後自動加入登入態校驗邏輯;
  • 介面呼叫前只校驗前端登入態,不校驗後端登入態,也不校驗微信登入態,以節省每次校驗開銷;
  • 介面呼叫後校驗後端及微信登入態,若後端返回登入態相關錯誤碼,則重置前端登入態、重新登入、重新呼叫資料介面。

這樣,只有在真正需要重新登入的時候(無前端登入態/後端登入態失效/後端被提示微信登入態失效)才會重新執行登入流程;並且,一旦需要重新登入,就會自動重新觸發登入流程。

併發問題

問題:

健壯高效的小程式登入方案

如上圖所示,頁面各元件各功能有可能同時觸發登入流程,可能會導致:

  • 額外效能開銷,登入流程重複進行,登入介面重複呼叫;
  • 體驗問題,連續多次彈窗,影響使用者互動;
  • 邏輯問題,後一次登入重新整理了前一次登入的session_key,導致前一次登入介面解碼失敗,返回異常結果。

方案:

健壯高效的小程式登入方案

如上圖所示,加入免併發邏輯:若登入流程正在進行,則不重複觸發登入流程,而是加入當前流程的監聽佇列,待登入結束時再一併處理。這樣,任一時刻最多隻有一個登入流程正在進行。

流程實現

時序控制

健壯高效的小程式登入方案

如上圖所示,目前登入流程已較為複雜,步驟較多,且大多是非同步操作,每步成功失敗需要區分處理,處理過程又會相互交織。如果直接在微信介面/網路介面提供的success/fail回撥中進行邏輯處理,會造成:

  • 回撥層層巢狀,影響程式碼書寫和閱讀;
  • 不同路徑公共步驟難以統一提取;
  • 時序邏輯不直觀,不易管理。

因而採用Promise+async/await進行時序管理:

  • 將每個步驟Promise化:
  • 使用async/await管理整體時序:

如以上程式碼所示,微信登入、獲取微信使用者資訊、提示授權、開啟許可權皮膚等每一步都是非同步操作,都要等待success/fail回撥才能獲得操作結果併發起下一個操作;但利用Promise+async/await,可以像普通流程一樣,將這些操作線性組合,順序處理。
這樣,就可以實現直觀清晰的時序管理了。

過期處理

如以上程式碼所示,單獨封裝一個requestWithLogin函式,在資料請求前後加入登入態處理邏輯,可以保證資料請求會在有後端登入態時被髮送/重新傳送。
並且,重新登入過程對資料介面呼叫方是完全透明的,呼叫方只需要知道自己的介面需不需要登入態,而無需進行任何登入態相關判斷處理,重登入過程也不會對介面呼叫返回結果造成任何影響。
這樣,就可以實現登入態過期自動重新登入了。

併發控制

如以上程式碼所示,利用Promise可以被多次then/catch的特性(亦即,一個async函式呼叫結果可以被await多次),可以使用一個Promise來記錄當前登入流程,後續呼叫直接對該Promise進行監聽。
這樣,就可以實現登入流程免併發了。


至此,我們就得到了一個功能可用、相對健壯、相對高效的登入模組。但依然還是存在優化空間的。


場景優化

二次授權問題

問題:
使用者同意授權後,小程式可以訪問到微信使用者資訊,並且一段時間內再次訪問時,也不會重新出現授權彈窗;
但是,如果使用者長時間未使用小程式,或將小程式刪除重進,則登入時會再次出現授權彈窗。
一方面會對使用者造成干擾,影響其瀏覽效率;另一方面,不利於流失使用者召回。

方案:
再次授權場景其實並不是很必要:

  • 使用者第一次授權時,開發者已經可以獲得使用者暱稱、頭像等使用者資訊和openid、unionid等使用者標識;
  • 再次授權時,雖然使用者資訊可能有更新,但完全可以等使用者進個人主頁/編輯資訊時再進行同步,沒必要剛進小程式就彈窗;
  • 再次授權時,使用者標識並不會變化;
  • 只呼叫微信登入介面,不觸發授權,已經可以獲得openid了,通過openid就可以從資料庫中查詢使用其上次授權時的使用者資訊和unionid等其它使用者標識。

因而,增加以下流程以優化二次授權場景:
健壯高效的小程式登入方案

如上圖所示,在微信登入介面呼叫成功之後,先嚐試直接根據openid完成登入過程,若失敗再去請求使用者授權。

這樣,只有新使用者才會出現授權彈窗;老使用者、迴歸使用者,都可以直接靜默完成登入過程。

場景適配問題

問題:
不同場景對登入行為可能有不同的期望:

  • 有些場景,希望只在需要時自動登入,如商品詳情頁,希望在使用者點選留言、收藏等按鈕時自動調起登入並完成留言、收藏等相應操作;
  • 有些場景,希望只嘗試靜默登入,如首頁,希望對使用者做個性化推薦和針對性投放,但又不願彈窗阻撓使用者;
  • 有些場景,希望保證前後端登入態一致,如微信介面資料解碼。

單一的登入流程很難滿足這種多元的場景需求。

方案:
呼叫登入/要求登入的資料介面時支援指定場景模式:

健壯高效的小程式登入方案

如上圖所示,登入流程支援指定不同場景模式:

  • 通用模式,為預設模式,會自動調起登入並完成相應資料請求和後續操作;
  • 靜默模式,只會嘗試靜默登入,不會嘗試授權登入,成功與否均不影響頁面功能和後續介面呼叫;
  • 強制模式,會重新登入,不管前端是否保有登入態,以保證前後端登入態同步。

實現

場景優化方案主要是增加了一些流程&判斷,使用上文中的“時序控制”基本可以解決。
主要難點在於,上文中的免併發機制不再適用。比如,靜默模式正在進行時又觸發了一個強制模式的請求,此時,應觸發授權彈窗正常登入而不是監聽使用靜默模式的登入結果。
如果拆成每個模式各自免併發,一方面,登入流程需重複書寫,不便複用;另一方面,模式之間併發也存在風險。
因而,引入公共步驟併合機制:

如以上程式碼所示,將登入免併發改為每個公共步驟免併發,登入流程中就可以根據場景模式自由地進行步驟管理。
這樣,就可以實現對不同登入場景進行定製化支援。

效果示例

簡潔起見,以下程式碼使用wepy框架寫法,原生小程式/其它框架可類似參考。

如以上程式碼所示,可以做到老使用者/迴歸使用者進入頁面時自動悄悄登入,以提供更多個性化服務;新使用者進入頁面時不進行任何干擾,直到進行留言等操作時才自動出現授權彈窗,且授權完成後自動完成該次行為,無需使用者再次操作。
並且,這些過程對業務程式碼是完全透明的,業務程式碼只需要知道自己呼叫的介面是 必須登入/最好登入/必須第一次呼叫就登入/不用登入,並相應地指定 mode=common/silent/force/不使用requestWithLogin,即可。


這樣,我們的登入模組可以在不同場景指定不同登入邏輯,從而支援設計實現更多元更精細更流暢的登入互動。


介面優化

問題:
獲取微信使用者資訊時,直接出現系統授權彈窗有時候是很突兀的;使用自定義授權介面和價值文案進行引導,得當的話可以有效提高授權成功率。
而且,從10月10號起,小程式將不再支援自動彈窗授權使用者資訊和自動開啟許可權皮膚,這兩種操作必須使用<button>元件由使用者主動觸發。彼時起,自定義介面將不再是優化,而會是必需。
這意味著登入過程必須與頁面dom耦合,之前的純js邏輯不再適用。

方案1:登入浮層
在所有頁面放置登入浮層,頁面需要登入時則調起該浮層,經由浮層按鈕完成授權及後續流程。

實現

  • 浮層引入
    各個頁面都需要存在登入浮層。可以將各種頁面公共dom元素,包括登入浮層、網路異常介面、返回首頁快捷導航、公眾號關注元件等統一抽離成一個父公共元件,編寫eslint規則要求所有頁面統一引入,以此實現&保證登入時浮層存在。
  • 浮層無縫時序

授權浮層AuthModal.wpy:

登入模組login.js:

如以上程式碼所示,雖然自定義浮層需要展示按鈕、等待使用者點選、處理點選、考慮使用者不點選直接返回,互動流程相對複雜,但依然可以利用Promise使互動細節對外透明。開啟浮層時返回一個Promise,在各個互動出口對Promise進行resolve,則使用時只需將其作為一個普通的非同步過程對待。
這樣,就可以實現無縫接入自定義浮層授權。

方案2:獨立登入頁

需要授權使用者資訊時,跳轉至一個專門的登入頁面,頁面中展示引導內容和授權<button>,使用者操作完畢後再自動返回先前頁面。

實現

  • 元素引入
    登入所需dom元素只在登入頁引入即可。
  • 頁面無縫時序
    由於小程式的程式碼包特性,各頁面可以共享全域性變數和全域性函式;並且後一頁面開啟時,前一頁面依然駐留在記憶體中,前一頁面遺留的非同步任務也依然會繼續執行。因而,可以在前一頁面設定監聽,在登入頁進行回撥:

授權全域性資料模組userAuthHub.js:

登入模組login.js:

登入頁login.wpy:

如以上程式碼所示,雖然授權過程需要進行跨頁面互動,但利用Promise和小程式程式碼包特性,可以在前一頁面設定監聽,登入頁面進行回撥。登入頁面互動結束後,前一頁面會自動繼續執行登入流程,呼叫方無需進行返回重新整理等額外處理,資料介面也會繼續呼叫,使用者無需再次操作。
這樣,就可以實現無縫接入跨頁面授權互動。

兩種方案都可以實現自定義授權介面。內嵌浮層會增加一定維護成本和少量資源開銷,但可以直接在當前頁面完成登入互動,頁面自定義空間也相對更大;獨立登入頁會來回跳轉犧牲一定的互動體驗,但可以把登入所需dom元素集中在登入頁,減少維護成本和頁面侵入。二者各有優劣,可以按需採用或混合使用。


這樣,我們的登入模組可以使用自定義授權介面,從而支援設計實現更雅觀更精緻的授權引導。


複用優化

多小程式間複用&定製

問題:
開發方可能同時維護著多個小程式,這些小程式使用著相同的後端介面和後端使用者體系,又有著各自的小程式標識和使用訴求。
一方面,希望登入模組可以統一維護,不需要每個小程式各自開發;另一方面,又希望各小程式可以進行差異化定製,包括小程式前端標識不一致等剛性差異,和授權提示文案、埋點、授權互動等個性差異。

方案&實現:

  • 統一流程+個性化配置
    公共&預設流程由登入模組統一維護,各小程式直接複用;差異流程支援各小程式以配置的形式自定義擴充套件&覆蓋。 e.g.:
  • 配置檢查
    引入配置過程會存在一個潛在風險:觸發登入時,小程式尚未完成登入模組配置。
    理論上,只要全域性都使用同一個登入例項並在app.js頂部進行配置,應該就沒有這樣的時序風險。但複用方是不會自覺的,不一定會使用同一個例項,配置過程也不一定會被放在頂部,甚至有可能被放在某些非同步資料返回之後。因而登入模組只匯出唯一例項並加入配置檢查環節以保證該邏輯健壯性:

這樣,就可以實現在多個小程式間複用登入模組,由登入模組統一維護整體時序和預設流程,同時支援各小程式進行差異性定製&擴充套件。

多頁面間複用&定製

問題:
不同頁面對登入過程有時也存在定製需求,比如授權引導文案,有些頁面可能希望提示“授權後可以免費領紅包”,有些頁面可能是“授權後可以為好友助力”/“授權後可以獲得智慧推薦”/… 諸如此類。

方案&實現:
在頁面中設定鉤子供其提供個性化配置。e.g.:

頁面xxx.wpy:

小程式級登入配置:

這樣,就可以實現所有頁面共用登入模組的同時,支援每個頁面進行定製化修改。


這樣,我們的登入模組可以在多小程式、多頁面中複用,並支援各小程式、各頁面進行差異性定製。從而實現更好的可維護性可擴充套件性:

  • 公共&預設流程統一維護,避免維護過程重複、分化、膨脹,減少整體維護成本,並降低各方迭代不及時風險;
  • 差異&定製流程各自擴充套件,擴充套件入口下放至各小程式各頁面,擴充套件過程相互獨立互不干擾,不會對其它小程式/其它頁面造成任何影響。

總結

  • 完整登入流程

    健壯高效的小程式登入方案

  • 功能
    • 通過微信授權一鍵登入
    • 支援靜默登入,使用者授權一次過後不會再次被要求授權
    • 支援多種登入場景:通用、靜默、強制
    • 支援自定義授權介面
  • 健壯性
    • 曾經拒絕授權,會提示&開啟許可權皮膚供二次操作
    • 登入態過期,會自動重新登入重新傳送資料請求並正常返回請求資料
    • 登入流程&重試機制對呼叫方完全透明,頁面使用時流程遺漏風險基本為0
  • 效能
    • 後端登入態惰性檢測,減少每次查詢開銷
    • 公共步驟併合,減少併發成本
    • 登入操作與後續介面呼叫無縫銜接,減少返回重新整理/使用者重複操作成本
  • 可複用性、可擴充套件性、可維護性
    • 支援多小程式複用,公共流程統一維護,差異特性各小程式各自擴充套件;
    • 支援多頁面複用,公共流程小程式統一配置,差異特性各頁面各自擴充套件。

轉轉的開源庫fancy-mini上附有實現原始碼,歡迎參閱;有更好的設計思路或實現方案,歡迎交流探討。

順帶一提,es6語法對於複雜時序管理相當有增益,推薦深入學習。
順帶二提,文中流程圖是用ProcessOn做的,挺方便的一個小工具,而且是線上、免費的,順手分享下。

相關文章