了解微信小程式登入的前端設計與實現

coldplay.xixi
發布: 2020-10-28 17:24:42
轉載
3624 人瀏覽過

今天微信小程式開發欄位介紹小程式登入的前端設計與實作。

一. 前言

對於登入/註冊的設計如此精雕細琢的目的,當然是想讓這個作為應用的基礎能力,有足夠的健壯性,避免出現全站性的阻塞。

同時要充分考慮如何解耦和封裝,在開展新的小程式的時候,能更快的去復用能力,避免重複採坑。

登入註冊這模組,就像個冰山,我們以為它就是「輸入帳號密碼,就完成登入了」,但實際下面還有各種需要考慮的問題。

在此,跟在座的各位分享一下,最近做完一個小程式登入/註冊模組之後,沉澱下來的一些設計經驗和想法。

二. 業務場景

在使用者瀏覽小程式的過程中,由業務需要,往往需要獲取使用者的一些基本信息,常見的有:

  1. 微信暱稱
  2. 微信手機號碼

而不同的產品,對於使用者的資訊要求不盡相同,也會有不一樣的授權流程。

第一種,常見於電商系統中,用戶購買商品的時候,為了識別用戶多平台的帳號,往往用手機號去做一個聯繫,這時候需要用戶去授權手機號。

了解微信小程式登入的前端設計與實現

第二種,為了讓用戶資訊得到基本的初始化,往往需要更進一步獲取用戶資訊:如微信暱稱,unionId 等,就需詢問用戶授權。

了解微信小程式登入的前端設計與實現

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

了解微信小程式登入的前端設計與實現

三.概念

秉著沉澱一套通用的小程式登入方案和服務為目標,我們去分析一下業務,得出變數。

在做技術設計之前,講點必要的廢話,對一些概念進行基本調頻。

2.1 關於「登入」

登入在英文中是 “login”,對應的還有 “logout”。而登入之前,你需要擁有一個帳號,就要 「register」(or sign up)。

話說一開始的產品是沒有登入/註冊功能的,用的人多了就慢慢有了。出於產品本身的需求,需要對「使用者」進行身分識別。

在現實社會中,我們每個人都有一個身分ID:身分證。當我到了16歲的時候,第一次去公安局領身分證的時候,就完成了一次「註冊」行為。然後我去網咖上網,身分證刷一下,完成了一次「登入」行為。

那麼對於虛擬世界的網路來說,這個身分證明就是「帳號 密碼」。

常見的登入/註冊方式有:

  1. 帳號密碼註冊

    在網路的早期,個人信箱和手機覆蓋度小。所以,就需要用戶自己想一個帳號名,我們註冊個QQ號,就是這種形式。

    from 汽车之家

  2. 郵件地址註冊

    千禧年之後,PC互聯網時代快速普及,我們都創建了屬於自己的個人信箱。加上QQ也自備信箱帳號。由於郵箱具有個人私密性,且能夠進行資訊的溝通,因此,大部分網站開始採用郵箱帳號作為用戶名來進行註冊,並且會在註冊的過程中要求登錄到相應郵箱內查收激活郵件,驗證我們對該註冊郵箱的所有權。

    from 支付宝

  3. 手機號碼註冊

    在網路普及之後,智慧型手機與行動網路發展迅速。手機也成為每個人必不可少的行動設備,同時行動互聯網也已經深深融入每個人的現代生活當中。所以,相較於郵箱,目前手機號碼與個人的聯繫更加緊密,而且越來越多的行動應用程式出現,採用手機號碼作為用戶名的註冊方式也得到了廣泛的使用。

    from 知乎

到了 2020 年,微信用戶規模達 12 億。那麼,微信帳號,起碼在中國,已成為新一代網路世界的「身分識別」。

而對微信小程式而言,天然就能知道目前使用者的微信帳號ID。微信允許小程式應用,能在使用者無感知的情況下,悄無聲息的「登入」到我們的小程式應用程式中去,這個就是我們常稱之為的「靜默登入」。

其實微信小程式的登錄,跟傳統 Web 應用的「單一登入」本質是一樣的概念。

  1. 單一登入:在 A 站登入了,C 站和 B 站能實現快速的「靜默登入」。
  2. 微信小程式登入:在微信中,登入了微信帳號,那麼在整個小程式生態中,都可以實現「靜默登入」。

由於Http 本來是無狀態的,業界基本上對於登入態的一般做法:

  1. cookie-session:常用於瀏覽器應用程式中
  2. access token:常用於行動端等非瀏覽器應用

在微信小程式來說,對於「JS邏輯層」並不是一個瀏覽器環境,自然沒有Cookie,那麼通常會使用access token 的方式。

2.2 關於「授權」

對於需要更進一步取得用的使用者暱稱、使用者手機號碼等資訊的產品。微信出於用戶隱私的考慮,需要用戶主動同意授權。小程式應用程式才能獲取到這部分訊息,這就有了目前流行的小程式「授權使用者資訊」、「授權手機號」的交互了。

出於不同的使用者資訊敏感度不同的考慮,微信小程式對於不同的使用者資訊提供「授權」的方式不盡相同:

  1. 呼叫具體API 方式,彈跳窗授權。
    1. 例如呼叫 wx.getLocation() 的時候,如果使用者未授權,則會彈出位址授權介面。
    2. 如果拒絕了,就不會再彈跳窗,wx.getLocation()直接回傳失敗。
  2. <button open-type="xxx"></button> 方式。
    1. 僅支援:用戶敏感資訊,用戶手機號,需要配合後端進行對稱加解密,方能拿到資料。
    2. 用戶已拒絕,再次點擊按鈕,仍然會彈出視窗。
  3. 透過 wx.authorize(),事先詢問授權,之後需要取得相關資訊的時候不用再彈出授權。

四. 詳細設計

梳理清楚了概念之後,我們模組的分割上,可以分割為兩大塊:

  1. 登入:負責與服務端建立起一個會話,這個會話實作靜默登入以及相關的容錯處理等,模組命名為:Session
  2. ##授權:負責與使用者交互,取得與更新訊息,以及權限的控制處理等,模組命名為:Auth
3.1 登入的實作

##3.1 .1 靜默登入

了解微信小程式登入的前端設計與實現微信官方提供的登入方案,總結為三個步驟:

前端透過
    wx.login()
  1. 取得一次性加密憑證code,交給後端。 後端把這個 code 傳送給微信伺服器端,換取使用者唯一識別碼
  2. openId
  3. 和授權憑證 session_key。 (用於後續伺服器端和微信伺服器的特殊 API 調用,具體看:微信官方文件-服務端取得開放資料)。 後端把從微信伺服器取得到的使用者憑證與自行產生的登入態憑證(token),傳送給前端。前端保存起來,下次請求的時候帶給後端,就能辨識哪個使用者。
  4. 如果只是實作這個流程的話,還挺簡單的。

但要實現一個健全的登入過程,還需要注意更多的邊界情況:

  1. 收攏

    wx.login() 的呼叫由於

    wx.login()

    會產生不可預測的副作用,例如會可能導致session_key失效,進而導致後續的授權解密場景中的失敗。我們這裡可以提供一個像 session.login() 的方法,掌握 wx.login() 控制權,對其做一系列的封裝和容錯處理。

  2. 呼叫的時機

    通常我們會在應用程式啟動的時候(

    app.onLaunch()

    ),去發起靜默登入。但這裡會由小程式生命週期設計問題而導致的一個非同步問題:載入頁面的時候,去呼叫一個需要登入態的後端API 的時候,前面非同步的靜態登入過程有可能還沒有完成,從而導致請求失敗。 <p>當然也可以在第一個需要登入態的介面調用的時候以非同步阻塞的方式發起登入調用,這個需要結合良好設計的介面層。 </p> <p>以講到的兩種場景的詳細設計思維下文會講到。 </p>

  3. 並發調用的問題

    在業務場景中,難免會出現多處程式碼需要觸發登錄,如果遇到極端情況,這多處程式碼同時間發起呼叫。那就會造成短時間多次發起登入過程,儘管先前的請求還沒有完成。針對這種情況,我們可以以第一個調用為阻塞,後續調用等待結果,就像精子和卵子結合的過程。

  4. 未過期呼叫的問題

    如果我們的登入態未過期,完全可以正常使用的,預設情況就不需要再去發起登入過程了。這時候我們可以預設先去檢查登入態是否可用,不能用,我們再發起請求。然後也可以提供一個類似 session.login({ force: true })的參數去強行發起登入。

3.1.2 靜默登入非同步狀態的處理

#1. 應用程式啟動的時候呼叫

因為大部分情況都需要依賴登入態,我們會很自然而然的想到把這個呼叫的時機放到應用程式啟動的時候( app.onLaunch() )來呼叫。

但由於原生的小程式啟動流程中, AppPageComponent 的生命週期鉤子函數,都不支援非同步阻塞。

那麼我們很容易會遇到app.onLaunch 發起的「登入過程」在page.onLoad 的時候還沒完成,我們就無法正確去做一些依賴登入態的操作。

針對這種情況,我們設計了一個狀態機的工具:status

了解微信小程式登入的前端設計與實現

基於狀態機,我們就可以寫這樣的程式碼:

import { Status } from '@beautywe/plugin-status';// on app.jsApp({    status: {       login: new Status('login');
    },    onLaunch() {
        session            // 发起静默登录调用
            .login()            // 把了解微信小程式登入的前端設計與實現设置为 success
            .then(() => this.status.login.success())      
            // 把了解微信小程式登入的前端設計與實現设置为 fail
            .catch(() => this.status.login.fail());
    },
});// on page.jsPage({    onLoad() {      const loginStatus = getApp().status.login;      
      // must 里面会进行状态的判断,例如登录中就等待,登录成功就直接返回,登录失败抛出等。
      loginStatus().status.login.must(() => {        // 进行一些需要登录态的操作...
      });
    },
});复制代码
登入後複製

2. 在「第一個需要登入態介面」被呼叫的時候去發起登入

更進一步,我們會發現,需要登入態的更深層次的節點是在發起的「需要登入態的後端API 」的時候。

那麼我們可以在呼叫「需要登入態的後端 API」的時候再去發起「靜默登入」,對於並發的場景,讓其他請求等待一下就好了。

以fly.js 作為wx.request() 封裝的「網路請求層」,做一個簡單的範例:

// 发起请求,并表明该请求是需要登录态的fly.post('https://...', params, { needLogin: true });// 在 fly 拦截器中处理逻辑fly.interceptors.request.use(async (req)=>{  // 在请求需要登录态的时候
  if (req.needLogin !== false) {    // ensureLogin 核心逻辑是:判断是否已登录,如否发起登录调用,如果正在登录,则进入队列等待回调。
    await session.ensureLogin();    
    // 登录成功后,获取 token,通过 headers 传递给后端。
    const token = await session.getToken();    Object.assign(req.headers, { [AUTH_KEY_NAME]: token });
  }  
  return req;
});复制代码
登入後複製

3.1.3 自訂登入態過期的容錯處理

當自訂登入態過期的時候,後端需要傳回特定的狀態碼,例如:AUTH_EXPIREDAUTH_INVALID 等。

前端可以在「網路請求層」去監聽所有請求的這個狀態碼,然後發起刷新登入態,再去重播失敗的請求:

// 添加响应拦截器fly.interceptors.response.use(    (response) => {      const code = res.data;        
      // 登录态过期或失效
      if ( ['AUTH_EXPIRED', 'AUTH_INVALID'].includes(code) ) {      
        // 刷新登录态
        await session.refreshLogin();        
        // 然后重新发起请求
        return fly.request(request);
      }
    }
)复制代码
登入後複製

那麼如果並發的發起多個請求,都回傳了登入態失效的狀態碼,上述程式碼就會執行多次。

我們需要對session.refreshLogin() 做一些特殊的容錯處理:

  1. 請求鎖定:同一時間,只允許一個正在過程中的網路請求。
  2. 等待隊列:請求被鎖定之後,調用該方法的所有調用,都推入一個隊列中,等待網路請求完成之後共用返回結果。
  3. 熔斷機制:如果短時間內多次調用,則停止回應一段時間,類似於 TCP 慢啟動。

範例程式碼:

class Session {  // ....
  
  // 刷新登录保险丝,最多重复 3 次,然后熔断,5s 后恢复
  refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT;
  refreshLoginFuseLocked = false;
  refreshLoginFuseRestoreTime = 5000;  // 熔断控制
  refreshLoginFuse(): Promise<void> {    if (this.refreshLoginFuseLocked) {      return Promise.reject('刷新登录-保险丝已熔断,请稍后');
    }    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('刷新登录-保险丝熔断解除');
      }, this.refreshLoginFuseRestoreTime);      return Promise.reject('刷新登录-保险丝熔断!!');
    }
  }  // 并发回调队列
  refreshLoginQueueMaxLength = 100;
  refreshLoginQueue: any[] = [];
  refreshLoginLocked = false;  // 刷新登录态
  refreshLogin(): Promise<void> {    return Promise.resolve()    
      // 回调队列 + 熔断 控制
      .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;
      })      // 通过前置控制之后,发起登录过程
      .then(() => {        this.clearSession();
        wx.showLoading({ title: '刷新登录态中', 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;
          });
      });  // ...}复制代码</void></void>
登入後複製

3.1.4 微信session_key 過期的容錯處理

我們從上面的「靜默登入」之後,微信伺服器端會下發一個session_key 給後端,而這個會在需要取得微信開放資料的時候會用到。

了解微信小程式登入的前端設計與實現

session_key 是有時效性的,以下摘自微信官方描述:

會話金鑰session_key 有效性

開發者如果遇到因為session_key 不正確而校驗簽章失敗或解密失敗,請注意下面幾個與session_key 有關的注意事項。

  1. wx.login 调用时,用户的 session_key 可能会被更新而致使旧 session_key 失效(刷新机制存在最短周期,如果同一个用户短时间内多次调用 wx.login,并非每次调用都导致 session_key 刷新)。开发者应该在明确需要重新登录时才调用 wx.login,及时通过 auth.code2Session 接口更新服务器存储的 session_key。
  2. 微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序,session_key 有效期越长。
  3. 开发者在 session_key 失效时,可以通过重新执行登录流程获取有效的 session_key。使用接口 wx.checkSession可以校验 session_key 是否有效,从而避免小程序反复执行登录流程。
  4. 当开发者在实现自定义登录态时,可以考虑以 session_key 有效期作为自身登录态有效期,也可以实现自定义的时效性策略。

翻译成简单的两句话:

  1. session_key 时效性由微信控制,开发者不可预测。
  2. wx.login 可能会导致 session_key 过期,可以在使用接口之前用 wx.checkSession 检查一下。

而对于第二点,我们通过实验发现,偶发性的在 session_key 已过期的情况下,wx.checkSession 会概率性返回 true

社区也有相关的反馈未得到解决:

  • 小程序解密手机号,隔一小段时间后,checksession:ok,但是解密失败
  • wx.checkSession有效,但是解密数据失败
  • checkSession判断session_key未失效,但是解密手机号失败

所以结论是:wx.checkSession可靠性是不达 100% 的。

基于以上,我们需要对 session_key 的过期做一些容错处理:

  1. 发起需要使用 session_key 的请求前,做一次 wx.checkSession 操作,如果失败了刷新登录态。
  2. 后端使用 session_key 解密开放数据失败之后,返回特定错误码(如:DECRYPT_WX_OPEN_DATA_FAIL),前端刷新登录态。

示例代码:

// 定义检查 session_key 有效性的操作const ensureSessionKey = async () => {  const hasSession = await new Promise(resolve => {
    wx.checkSession({      success: () => resolve(true),      fail: () => resolve(false),
    });
  });  
  if (!hasSession) {
    logger.info('sessionKey 已过期,刷新登录态');    // 接上面提到的刷新登录逻辑
    return session.refreshLogin();
  }  return Promise.resolve();
}// 在发起请求的时候,先做一次确保 session_key 最新的操作(以 fly.js 作为网络请求层为例)const updatePhone = async (params) => {  await ensureSessionKey();  const res = await fly.post('https://xxx', params);
}// 添加响应拦截器, 监听网络请求返回fly.interceptors.response.use(    (response) => {      const code = res.data;        
      // 登录态过期或失效
      if ( ['DECRYPT_WX_OPEN_DATA_FAIL'].includes(code)) {        // 刷新登录态
        await session.refreshLogin();        
        // 由于加密场景的加密数据由用户点击产生,session_key 可能已经更改,需要用户重新点击一遍。
        wx.showToast({ title: '网络出小差了,请稍后重试', icon: 'none' });
      }
    }
)复制代码
登入後複製

3.2 授权的实现

3.2.1 组件拆分与设计

在用户信息和手机号获取的方式上,微信是以 <button open-type="'xxx'"></button> 的方式,让用户主动点击授权的。

那么为了让代码更解耦,我们设计这样三个组件:

  1. <user-contaienr getuserinfo="onUserInfoAuth"></user-contaienr>: 包装点击交互,通过 <slot></slot> 支持点击区域的自定义UI。
  2. <phone-container getphonennmber="onPhoneAuth"></phone-container> : 与 <user-container></user-container> 同理。
  3. <auth-flow></auth-flow>: 根据业务需要,组合 <user-container></user-container><phone-container></phone-container> 组合来定义不同的授权流程。

以开头的业务场景的流程为例,它有这样的要求:

  1. 有多个步骤。
  2. 如果中途断掉了,可以从中间接上。
  3. 有些场景中,只要求达到「用户信息授权」,而不需要完成「用户手机号」。

了解微信小程式登入的前端設計與實現

那么授权的阶段可以分三层:

// 用户登录的阶段export enum AuthStep {  // 阶段一:只有登录态,没有用户信息,没有手机号
  ONE = 1,  // 阶段二:有用户信息,没有手机号
  TWO = 2,  // 阶段三:有用户信息,有手机号
  THREE = 3,
}复制代码
登入後複製

AuthStep 的推进过程是不可逆的,我们可以定义一个 nextStep 函数来封装 AuthStep 更新的逻辑。外部使用的话,只要无脑调用 nextStep 方法,等待回调结果就行。

示例伪代码:

// auth-flow componentComponent({  // ...
  
  data: {    // 默认情况下,只需要到达阶段二。
    mustAuthStep: AuthStep.TWO
  },  
  // 允许临时更改组件的需要达到的阶段。
  setMustAuthStep(mustAuthStep: AuthStep) {    this.setData({ mustAuthStep });
  },  
  // 根据用户当前的信息,计算用户处在授权的阶段
  getAuthStep() {    let currAuthStep;    
    // 没有用户信息,尚在第一步
    if (!session.hasUser() || !session.hasUnionId()) {
      currAuthStep = AuthStepType.ONE;
    }    // 没有手机号,尚在第二步
    if (!session.hasPhone()) {
      currAuthStep = AuthStepType.TWO;
    }    // 都有,尚在第三步
    currAuthStep = AuthStepType.THREE;    return currAuthStep;
  }  
  // 发起下一步授权,如果都已经完成,就直接返回成功。
  nextStep(e) {    const { mustAuthStep } = this.data;    const currAuthStep = this.updateAuthStep();  
    // 已完成授权
    if (currAuthStep >= mustAuthStep || currAuthStep === AuthStepType.THREE) {      // 更新全局的授权了解微信小程式登入的前端設計與實現,广播消息给订阅者。
      return getApp().status.auth.success();
    }    // 第一步:更新用户信息
    if (currAuthStep === AuthStepType.ONE) {      // 已有密文信息,更新用户信息
      if (e) session.updateUser(e);      // 更新到视图层,展示对应UI,等待获取用户信息
      else this.setData({ currAuthStep });      return;
    }    // 第二步:更新手机信息
    if (currAuthStep === AuthStepType.TWO) {      // 已有密文信息,更新手机号
      if (e) this.bindPhone(e);      // 未有密文信息,弹出获取窗口
      else this.setData({ currAuthStep });      return;
    }    console.warn('auth.nextStep 错误', { currAuthStep, mustAuthStep });
  },  
  // ...});复制代码
登入後複製

那么我们的 <auth-flow></auth-flow> 中就可以根据 currAuthStepmustAuthStep 来去做不同的 UI 展示。需要注意的是使用 <user-container></user-container><phone-container></phone-container> 的时候连接上 nextStep(e) 函数。

示例伪代码:

<view>

  <!-- 已完成授权 -->
  <block>
    <view>已完成授权</view>
  </block>

  <!-- 未完成授权,第一步:了解微信小程式登入的前端設計與實現 -->
  <block>
    <user-container>
      <view>了解微信小程式登入的前端設計與實現</view>
    </user-container>
  </block>

  <!-- 未完成授权,第二步:了解微信小程式登入的前端設計與實現 -->
  <block>
    <phone-container>
      <view>了解微信小程式登入的前端設計與實現</view>
    </phone-container>
  </block>
  </view>复制代码
登入後複製

3.2.2 权限拦截的处理

到这里,我们制作好了用来承载授权流程的组件 <auth-flow></auth-flow> ,那么接下来就是决定要使用它的时机了。

我们梳理需要授权的场景:

  1. 点击某个按钮,例如:购买某个商品。

    对于这种场景,常见的是通过弹窗完成授权,用户可以选择关闭。

    了解微信小程式登入的前端設計與實現

  2. 浏览某个页面,例如:访问个人中心。

    对于这种场景,我们可以在点击跳转某个页面的时候,进行拦截,弹窗处理。但这样的缺点是,跳转到目标页面的地方可能会很多,每个都拦截,难免会错漏。而且当目标页面作为「小程序落地页面」的时候,就避免不了。

    这时候,我们可以通过重定向到授权页面来完成授权流程,完成之后,再回来。

    了解微信小程式登入的前端設計與實現

那么我们定义一个枚举变量:

// 授权的展示形式export enum AuthDisplayMode {  // 以弹窗形式
  POPUP = 'button',  // 以页面形式
  PAGE = 'page',
}复制代码
登入後複製

我们可以设计一个 mustAuth 方法,在点击某个按钮,或者页面加载的时候,进行授权控制。

伪代码示例:

class Session {  // ...
  
  mustAuth({
    mustAuthStep = AuthStepType.TWO, // 需要授权的LEVEL,默认需要获取用户资料
    popupCompName = 'auth-popup',	// 授权弹窗组件的 id
    mode = AuthDisplayMode.POPUP, // 默认以弹窗模式
  } = {}): Promise<void> {    
    // 如果当前的授权步骤已经达标,则返回成功
    if (this.currentAuthStep() >= mustAuthStep) return Promise.resolve();    // 尝试获取当前页面的 <auth-popup></auth-popup> 组件实例
    const pages = getCurrentPages();    const curPage = pages[pages.length - 1];    const popupComp = curPage.selectComponent(`#${popupCompName}`);    // 组件不存在或者显示指定页面,跳转到授权页面
    if (!popupComp || mode === AuthDisplayMode.PAGE) {      const curRoute = curPage.route;      // 跳转到授权页面,带上当前页面路由,授权完成之后,回到当前页面。
      wx.redirectTo({ url: `authPage?backTo=${encodeURIComponent(curRoute)}` });      return Promise.resolve();
    }    
    // 设置授权 LEVEL,然后调用 <auth-popup> 的 nextStep 方法,进行进一步的授权。
    popupComp.setMustAuthStep(mustAuthStep);
    popupComp.nextStep();    // 等待成功回调或者失败回调
    return new Promise((resolve, reject) => {      const authStatus = getApp().status.auth;
      authStatus.onceSuccess(resolve);
      authStatus.onceFail(reject);
    });
  }  
  // ...}复制代码</auth-popup></void>
登入後複製

那么我们就能在按钮点击,或者页面加载的时候进行授权拦截:

Page({  onLoad() {
    session.mustAuth().then(() => {      // 开始初始化页面...
    });
  }  
  onClick(e) {
    session.mustAuth().then(() => {      // 开始处理回调逻辑...
    });
  }
})复制代码
登入後複製

当然,如果项目使用了 TS 的话,或者支持 ES7 Decorator 特性的话,我们可以为 mustAuth 提供一个装饰器版本:

export function mustAuth(option = {}) {  return function(
    _target,
    _propertyName,
    descriptor,  ) {    // 劫持目标方法
    const method = descriptor.value;    
    // 重写目标方法
    descriptor.value = function(...args: any[]) {      return session.mustAuth(option).then(() => {        // 登录完成之后,重放原来方法
        if (method) return method.apply(this, args);
      });
    };
  };
}复制代码
登入後複製

那么使用方式就简单一些了:

Page({
  @mustAuth();  onLoad() {    // 开始初始化页面...
  }
  
  @mustAuth();  onClick(e) {    // 开始处理回调逻辑...
  }
});复制代码
登入後複製

3.3. 前后端交互协议整理

作为一套可复用的小程序登录方案,当然需要去定义好前后端的交互协议。

那么整套登录流程下来,需要的接口有这么几个:

了解微信小程式登入的前端設計與實現

  1. 静默登录 silentLogin

    1. 入参:
      1. code: 产自 wx.login()
    2. 出参:
      1. token: 自定义登录态凭证
      2. userInfo: 用户信息
    3. 说明:
      1. 后端利用 code 跟微信客户端换取用户标识,然后注册并登录用户,返回自定义登录态 token 给前端
      2. token 前端会存起来,每个请求都会带上
      3. userInfo 需要包含nicknamephone字段,前端用于计算当前用户的授权阶段。当然这个状态的记录可以放在后端,但是我们认为放在前端,会更加灵活。
  2. 更新用户信息 updateUser

    1. 入参:
      1. nickname: 用户昵称
      2. encrypt: 了解微信小程式登入的前端設計與實現相关的 iv, encryptedData
      3. 以及其他如性别地址等非必要字段
    2. 出参:
      1. userInfo:更新后的最新用户信息
    3. 说明:
      1. 后端解密了解微信小程式登入的前端設計與實現,获取隐蔽数据,如:unionId
      2. 后端支持更新包括 nickname等用户基本信息。
      3. 前端会把 userInfo 信息更新到 session 中,用于计算授权阶段。
  3. 更新用户手机号 updatePhone

    1. 入参:
      1. encrypt:了解微信小程式登入的前端設計與實現相关的 iv, encryptedData
    2. 出参:
      1. userInfo:更新后的最新用户信息
    3. 说明:
      1. 后端解密开放式局,获取手机号,并更新到用户信息中。
      2. 前端会把 userInfo 信息更新到 session 中,用于计算授权阶段。
  4. 解绑手机号 unbindPhone

    1. 入参:-
    2. 出参:-
    3. 说明:后端解绑用户手机号,成功与否,走业务定义的前后端协议。
  5. 登录 logout

    1. 入参:-

    2. 出参:-

    3. 说明:后端主动过期登录态,成功与否,走业务定义的前后端协议。

五. 架构图

最后我们来梳理一下整体的「登录服务」的架构图:

了解微信小程式登入的前端設計與實現

由「登录服务」和「底层建设」组合提供的通用服务,业务层只需要去根据产品需求,定制授权的流程 <auth-flow></auth-flow> ,就能满足大部分场景了。

六. 总结

本篇文章通过一些常见的登录授权场景来展开来描述细节点。

整理了「登录」、「授权」的概念。

然后分别针对「登录」介绍了一些关键的技术实现:

  1. 靜默登入
  2. 靜默登入非同步狀態的處理
  3. 自訂登入狀態過期的容錯處理
  4. 微信session_key 過期的容錯處理

而對於「授權」,會有設計UI部分的邏輯,還需要涉及到元件的拆分:

  1. 元件拆分與設計
  2. 權限攔截的處理

然後,梳理了這套登入授權方案所依賴的後端接口,和給出最簡單的參考協定。

最後,站在「秉著沉澱一套通用的小程式登入方案和服務為目標」的角度,整理了一下架構層面上的分層。

  1. 業務自訂層
  2. 登入服務層
  3. 底層建設

相關免費學習推薦:微信小程式開發

#

以上是了解微信小程式登入的前端設計與實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:juejin.im
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板