1. 程式人生 > >小程式 登入(轉)

小程式 登入(轉)

轉自:https://segmentfault.com/a/1190000012731720

梳理登入流程

這裡有幾個點是要注意的:

  1. 要注意 es6語法使用,es6的語法會在小程式裡面更加的有用,其中最關鍵的地方就是小程式的 api 大部分都是非同步的,舊有的方式要非同步就必須要要回調,而回調的就會導致程式碼邏輯容易發生混亂,所以需要使用promise並且進行介面的封裝。
  2. 要注意效率,例如對於 code 的獲取,首次和非首次要注意首次獲取後儲存下來,然後非首次獲取就可以從快取裡面直接獲取,類似這種有好幾個地方,所以需要注意好。
  3. 要注意先理清楚業務邏輯和登入邏輯和產品體驗邏輯,一定要先畫好流程圖,因為小程式的邏輯相較一般的邏輯要稍微“繞”,對,是很繞的繞,所以需要注意理順流暢。
  4. 要注意介面的呼叫先後順序,跟流程圖的關係很大,對比官方文件,一步步處理,又因為使用非同步,所以需要弄清楚 promise 非同步的 resolve 和 reject,在多重promise 裡面會比較麻煩,不過也是有技巧可以迴避的,詳情下面會說。
  5. 需要注意簽名解密處理,雖然這是伺服器端做的,但是也需要了解好是怎麼一個操作,因為也需要解析給伺服器端的同事,如何配合小程式來做處理。
  6. 需要理解各個關鍵的變數元素的意思,code,session_key,3rd_session,openid,unioinid的意義,這樣才能協助理解文件的整體思路。
  7. 需要注意 openId 和 unioinId的使用

微信小程式登入流程圖

引用Yinjie 的圖,因為這個圖比官方的要看得明白一點。

clipboard.png

程式碼邏輯流程圖

引用Yinjie 的圖,因為這個圖比官方的要看得明白一點。

clipboard.png

騰訊 weapp-session的程式碼流程圖

clipboard.png

他們還出了一個比較詳細的,一步步的程式碼處理流程,可以對比自己的程式進行檢查。
  1. 客戶端(微信小程式)發起請求 request
    • 首次請求

      • 呼叫 wx.login() 和 wx.getUserInfo() 介面獲得 coderawData 和 signature
      • requset 的頭部帶上 coderawData 和 signature
      • 儲存 code 供下次呼叫
    • 非首次請求

      • request 的頭部帶上儲存的 code
  2. 伺服器收到請求 request

    ,中介軟體從頭部提取 coderawData 和 signature 欄位

    • 如果 code 為空,跳到第 4 步
    • 如果 code 不為空,且 rawData 不為空,需要進行簽名校驗

      • 使用 codeappidapp_secret 請求微信介面獲得 session_key 和 openid

        • 如果介面失敗,響應 ERR_SESSION_KEY_EXCHANGE_FAILED
      • 使用簽名演算法通過 rawData 和 session_key 計算簽名 signature2
      • 對比 signature 和 signature2

        • 簽名一致,解析 rawData 為 wxUserInfo

          • 把 openid 寫入到 wxUserInfo
          • 把 (code, wxUserInfo) 快取到 Redis
          • 把 wxUserInfo 存放在 request.$wxUserInfo 裡
          • 跳到第 4 步
        • 簽名不一致,響應 ERR_UNTRUSTED_RAW_DATA
    • 如果 code 不為空,但 rawData 為空,從 Redis 根據 code 查詢快取的使用者資訊

      • 找到使用者資訊,存放在 request.$wxUserInfo 欄位裡,跳到第 4 步
      • 沒找到使用者資訊(可能是過期),響應 ERR_SESSION_EXPIRED
  3. request 被業務處理,可以使用 request.$wxUserInfo 來獲取使用者資訊(request.$wxUserInfo 可能為空,業務需要自行處理)

流程圖總結

  1. code 是微信使用者的登入憑證,開啟小程式登入的時候獲取的只屬於微信這個使用者的登入憑證,需要注意的是,這個登入憑證只供微信小程式使用的。
  2. session_key 是微信使用者在小程式裡面的登入態資訊,相當於是微信給這個使用者頒發的一個登入 session,官網地址

    • 他有一個過期時間{"session_key":"...","expires_in":7200,"openid":"..."},需要定期使用wx.checkSession檢測。
  3. openId ,使用者的唯一標識
  4. unioinId,如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程式),可通過unionid來區分使用者的唯一性,因為只要是同一個微信開放平臺帳號下的移動應用、網站應用和公眾帳號(包括小程式),使用者的unionid是唯一的。換句話說,同一使用者,對同一個微信開放平臺下的不同應用,unionid是相同的。
  5. 一般來說,openId 就是微信使用者的唯一標識,但是因為微信產品很多,所以會出現多個微信產品使用不同的 openId 來標識使用者,但是對於我們做業務接入的話,就買辦法使用了,所以建議是統一使用 unioinid,因為一般來說,一般的業務都會有公眾號,小程式聯合使用的。
  6. 3rd_session 是一般是指我們自己公司的伺服器的 session,一般來說,可以跟原來的業務的 session 一起使用,不過這個 session 的過期時間一定要比小程式的session_key 的過期時間要長,這樣可以減少 session 的多次重複建立,另外一般我們自己公司的伺服器的 session 管理都會使用類似 redis 之類的資料庫進行管理的,這個大致瞭解一下,因為其他文章會提到。
  7. 為什麼要用2個 sessionsession_key 和3rd_session),那是因為session_key是微信的登入態,3rd_session是我們業務系統的登入態,兩邊各有一個登入態,所以需要將2個登入態合併為一個 session,在這裡面是合併為3rd_session,並儲存到我們業務系統上,然後每次需要使用的時候,小程式帶上這個3rd_session訪問我們的業務系統,通過處理,可以返回我們需要 unioinid 和其他 session 資訊而不用每次都去獲取一個新的session_key(因為微信有限制code 的使用,要用 code 換 session_key),總的來說,就是使用3rd_session來管理小程式的登入態

關於解密

根據官方文件: ,資料校驗是為了提高資料的安全性,我們需要獲取使用者的 unioinid,需要呼叫wx.getUserInfo介面來獲取,但是一般情況下,獲取出來的資料是

{
  "nickName": "Band",
  "gender": 1,
  "language": "zh_CN",
  "city": "Guangzhou",
  "province": "Guangdong",
  "country": "CN",
  "avatarUrl": "http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"
}

對於需要業務接入的話,沒有 unioinid 是沒意義的,所以需要根據官方的方式來進行解密,解密後的資料裡面有 unioinid 了

{
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":
    {
        "appid":"APPID",
    "timestamp":TIMESTAMP
    }
}

封裝網路介面

因為小程式的所有網路請求都是非同步的,那麼非同步就會出現很多重的回撥的問題,所以改成了 promise,promise 的使用要謹慎注意 resolve 和 return的處理。

例如這樣:

const httpRequest = (api, data) => {
    let serverHost = env.serverHost;

    return new Promise(function (resolve, reject) {
        //發起網路請求
        wx.request({
            url: serverHost + api,
            data: data,
            header: {
                'content-type': 'application/x-www-form-urlencoded'
            },
            success: function (res) {
                if (res.data.errno === 0) {
                    // 需要下一步處理的就用 resolve 返回
                    resolve(res.data);
                }
                else {
                    console.log("http fail:api:" + api + "res:" + JSON.stringify(res));
                    // 需要跳出迴圈處理的就用 reject
                    reject(res.data);
                }
            }, fail: function (res) {
                console.log("http fail:api:" + api + "res:" + JSON.stringify(res));
                // 需要跳出迴圈處理的就用 reject
                reject(res);
            }
        })
    })
};

在多重 promise 的情況下,則需要注意2個地方:

  1. 需要返回一個可使用的 promise 例項
  2. 即使有非同步並且出現分支的情況下,儘量集中在一個統一的流裡面處理,用統一的 reject 跳出,而不是各個非同步單獨跳出,這樣流程會更加統一和方便管理。
getCode() // 獲取 wx code
            .then(code => {
                wxCode = code;
                // 這裡getSetting是一個返回的 promise例項,如上面的那個
                return getSetting(); // 獲取 setting
            })
            .then(res => {
                if (res.authSetting["scope.userInfo"]) {
                    // 這裡getUserInfo是一個返回的 promise例項
                    return getUserInfo(self);
                } else {
                    console.log("first auth none:" + JSON.stringify(res));
                    // 類似
                    return util.showModal("親,還沒有授權!")
                        .then(res => {
                            return getAuth("userInfo");
                        }).then(res => {
                            // 檢查授權是否正常
                            return getSetting();
                        })
                        .then(res => {
                            if (res.authSetting["scope.userInfo"]) {
                                return getUserInfo(self);
                            } else {
                                return Promise.reject(res);
                            }
                        });
                }
            })
            .catch(err=>{
                console.log("err"+err);    
            })

注意友好的提示

微信小程式“授權失敗”場景需要優雅處理,提升使用者體驗,參考這裡:可以稍微看到是如何生效的:

通過在wx.getSetting裡面插入一個判斷處理,判斷沒有許可權即彈出 modal 提示框:

wx.getSetting({
    success: function success(res) {
        console.log(res.authSetting);
        var authSetting = res.authSetting;
        if (authSetting['scope.userInfo'] === false) {
                wx.showModal({
                    title: '使用者未授權',
                    content: '如需XXXXXXX。',
                    showCancel: false,
                    success: function (res) {
                        if (res.confirm) {
                            console.log('使用者點選確定')
                            wx.openSetting({
                                success: function success(res) {
                                }
                            });
                        }
                    }
                })
        }
    }
});