小程序授权登录的踩坑

2019-12-19 散崖 4074人已阅读

官方文档 (点击这里)先撸一遍,流程图一看好像会,开始动手立马跪。那跪在哪里必须知道:有时候授权用户信息或者手机号码会失败。搜索了资料以后发现问题是code的频繁刷新,我们不能授权用户信息和手机号码每次都wx.login去刷新code,这个必然导致授权失败!

代码地址:https://gitee.com/efri-yang/XiaoChengXu-cheYiBaoTest/tree/%E5%B0%8F%E7%A8%8B%E5%BA%8F-%E7%99%BB%E5%BD%95%E6%8E%88%E6%9D%83/

微信授权在解密数据的时候 new WXBizDataCrypt($appid, $sessionKey);我们只要保证sessionKey不是过期,那么授权必然会成功!而sessionKey是通过code去获取的,所以前端只要通过wx.checkSession判断session是否过过期决定是否去从新获取sessionkey,这样子授权就不会出现“第一次失败,点击第二次的时候才会成功”。所以在封装HttpRequst的时候,多次请求的时候,容易掉坑。看图

page1

 

一、session的会话机制

用户一进入小程序就要保持会话,简单的方式就是onLaunch的时候执行asyncLogin,但是asyncLogin是异步的,当小程序启动时触发onLaunch,但是同时也在触发所进入页面(pageA)的onLoad,往往会出现ukey还没获取,就执行了pageA的请求,没有ukey必然导致请求数据失败!所以有了

方案1

//app.js中的onLaunch中,asyncLogin就是获取ukey的方法
app.asyncLogin().then(res=>{
    this.globalData.ukey=res.data.ukey;
    this.callBackFn(res);
});


//pageA.js中的onLoad中
if (app.globalData.ukey && app.globalData.ukey != '') {
    // 如果有值,说明接口已经返回,所以直接赋值使用
    HttpRequest()
} else {
    //如果没有,给app定义一个回调,拿到接口返回参数
    app.callBackFn= data=> {
        HttpRequest(data)
    }
}

看起来没问题,但是如果pageA页面有多个请求A1,A2,A3的时候,这样的封装方法的调用显然是很冗余和不方便。假设三个前端同时进行,如果能把sessionkey会话封装进HttpRequest请求中(如上图的流程),那么请求的接口调用将更加简洁方便。所以我们采用另一种方案!

方案2

app.js

//app.js
App({
  onLaunch: function () {},
  globalData: {
    loginRuning: false,
    5: [],
    userInfo: '',
    location:'',
    ukey: ""
  },

  setStorage: function (key, data, callback = function () {}, reject = function () {}) {
    try {
      wx.setStorageSync(key, data);
      this.globalData[key] = data;
      // console.log(`Method:setStorage(success)`);
      callback(data)
    } catch (e) {
      reject(e)
    }
  },
  getStorageByKey: function (key) {
    var storage;
    if (!!this.globalData[key]) {
      return this.globalData[key];
    } else {
      try {
        const value = wx.getStorageSync(key)
        if (value) {
          storage = value;
          return storage;
        }
      } catch (e) {
        console.warn("getStorageByKey-fail:" + key);
      }
    }
    return !!storage ? storage : "";
  }
})

setStorage函数:设置本地存储,同时将值赋到globalData上;

getStorageByKey函数:是通过key获取本地存储;

loginRuning:判断此时是否属于wx.login的执行状态;

loginCollect:请求收集,在wx.login期间,其他请求需要先收集挂起,等待ukey获取后方能执行

 

httpRequest.js

import Config from 'config';
let app = getApp();
function HttpRequest(loading, url, sessionChoose, params, method, callBack, reject) {
  let ukey = app.getStorageByKey("ukey");
  if (!!ukey) {
    if (loading == true) {
      wx.showLoading();
    }
    let paramSession = sessionChoose == 1 ? {
      'content-type': 'application/x-www-form-urlencoded'
    } : {
        'content-type': 'application/json'
      };
    var params = Object.prototype.toString.call(params) == "[object Object]" ? params : {};
    params["ukey"] = ukey;
    wx.request({
      url: Config.api.baseUrl + url,
      data: params,
      dataType: "json",
      header: paramSession,
      method: method,
      success: function (res) {
        if (loading == true) {
          wx.hideLoading(); //隐藏提示框
        }
        //ukey存在服务端返回408,这个时候就要强制重新登陆
        if (res.data.code == 408) {
          asyncLogin(true).then((res) => {
            HttpRequest(loading, url, sessionChoose, params, method, callBack, reject)
          })
        } else if (res.data.code == 400){
          wx.showToast({
            title: res.data.msg.toString(),
            icon: '',
            duration: 2000
          })
        }else {
          callBack(res.data);
        }
      },
      fail: function (res) {
        console.warn("HttpRequst当中wx.request fail 执行了:调用失败")
      }
    })
  } else {
    //不存在(或者) 重走登录流程
    asyncLogin().then((res) => {
      HttpRequest(loading, url, sessionChoose, params, method, callBack, reject)
    })
  }
}

function asyncLogin(flag) {
  return new Promise((reslove, reject) => {
    if (app.globalData.loginRuning) {
      //已经在执行了,其他的request先收集挂起,等到ukey存在的时候再执行
      app.globalData.loginCollect.push(reslove);
    } else {
      app.globalData.loginRuning = true;
      walk(reslove, flag)
    }
  })
}


function _resloveRun() {
  app.globalData.loginCollect.forEach((reslove) => {
    reslove();
  });
  app.globalData.loginRuning = false;
  app.globalData.loginCollect = [];
}


function walk(reslove, flag) {
  wx.checkSession({
    success: function () {
      let ukey = app.getStorageByKey("ukey");
      if (!ukey || (!!ukey && flag)) {
        wxLogin(reslove);
      } else {
        //执行第一个request
        reslove(ukey);
        _resloveRun();
      }
    },
    fail: function () {
      //重新走登录流程
      wxLogin(reslove);
    }
  });
}
function wxLogin(reslove) {
  wx.login({
    success(res) {
      console.log(res.code)
      if (res.code) {
        wx.request({
          url: "https://wnworld.com/CheYiBao/loginShouQuan.php",
          data: { code: res.code },
          dataType: "json",
          header: { 'content-type': 'application/x-www-form-urlencoded' },
          method: "POST",
          success: function (res) {
            if (res.data.code == 200) {
              app.globalData.ukey = res.data.data.ukey;
              app.setStorage("ukey", res.data.data.ukey, function () {
                reslove();
                _resloveRun();
              })    
            }
          },
          fail: function (res) {
            console.warn("wxLogin当中wx.request fail 执行了:调用失败")
          }
        })
      } else {
        console.warn('登录失败!' + res.errMsg)
      }
    },
    fail: function () {
      console.warn("调用wx.login接口失败!")
    }
  })
}

app.asyncLogin = asyncLogin;
export { HttpRequest, asyncLogin, app }

借助场景:pageA有r1,r2,r3,r4四个请求,此时(没有会话状态),执行r1(HttpRequest),便会调用asyncLogin(),此时ri就会走walk(),而,r2,r3,r4请求被push到loginCollect()。等待walk执行完,取到ukey,然后执行loginCollect里面的请求。、

 

apiQuery.js

import Config from 'config';
import {
  HttpRequest,
  asyncLogin,
  app
} from 'httpRequest';

let apiQuery = {};
apiQuery.getUserInfo = function (params) {
  return new Promise(function (resolve, reject) {
    HttpRequest(true, Config.api.getUserInfo, 1, params, "POST", function (res) {
      console.log(res)
      app.setStorage("userInfo", res.data);
      resolve(res.data);
    }, reject)
  })
}
//用户信息授权
apiQuery.authUserInfo = function (params) {
  return new Promise(function (resolve, reject) {
    wx.getSetting({
      success(res) {
        let userInfo = app.getStorageByKey("userInfo");
        if (res.authSetting['scope.userInfo']) {
          if (!userInfo.nickname || !userInfo.headimg || (userInfo.headimg != JSON.parse(params.detail.rawData).avatarUrl)) {
            asyncLogin().then(res => {
              apiQuery.getUserInfo({
                encryptedData: params.detail.encryptedData,
                iv: params.detail.iv
              }).then(res => {
                //设置本地存储
                resolve(res)
              })
            })
          } else {
            resolve(userInfo)
          }
        }
      }
    })
  })
}
//用户手机号码授权
apiQuery.authUserPhone = function (params) {
  return new Promise(function (resolve, reject) {
    if (params.detail.errMsg.indexOf("deny") === -1) {
      asyncLogin().then(res => {
        apiQuery.getUserInfo({
          encryptedData: params.detail.encryptedData,
          iv: params.detail.iv
        }).then(res => {
          //设置本地存储
          resolve(res)
        })
      })
    } else {
      wx.showToast({
        title: '您拒绝授权,将无法进行更多操作!',
        icon: 'none',
        duration: 2000
      });
      reject();
    }
  })
}


//测试接口
apiQuery.dealerlist = function (params) {
  return new Promise(function (resolve, reject) {
    HttpRequest(true, Config.api.dealerlist, 1, params, "POST", resolve, reject)
  })
}

//测试接口
apiQuery.kjList = function (params) {
  return new Promise(function (resolve, reject) {
    HttpRequest(true, Config.api.carlist, 1, params, "POST", resolve, reject)
  })
}
export default apiQuery;

index.html

<view class="container">
  <button bindtap="getToken">刷新ukey</button>
  <view bindtap="getCode" style="padding:40px 0;">刷新code(ukey不刷新)</view>
  <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" class="mt20">手机号码授权</button>
  <button open-type="getUserInfo" bindgetuserinfo="getuserinfo" class="mt20">获取用户信息</button>
</view>

 

index.js

import apiQuery from '../../utils/apiQuery.js'
let app = getApp();
Page({
  data: {},
  getuserinfo: function (e) {
    console.log(e)
    apiQuery.authUserInfo(e).then(res => {
      console.log(res);
    })
  },
  getPhoneNumber: function (e) {
    apiQuery.authUserPhone(e).then(res => {
      console.log(res);
    })
  },
  onLoad: function () {
    //测试接口请求
    apiQuery.kjList().then(res => {
      console.log(res);
    })
    //测试接口请求
    apiQuery.dealerlist().then(res => {
      console.log(res);
    })
  },
  getCode: function () {
    wx.login({
      success(res) {
        console.log(res.code)
      }
    })
  },
  getToken: function () {
    apiQuery.asyncLogin();
  }
})

 

交换友情链接