美文网首页微信小程序前端学习程序员
微信小程序 微信授权前后端解决方案 (更新至5.10授权机制)

微信小程序 微信授权前后端解决方案 (更新至5.10授权机制)

作者: 风殇嶄往 | 来源:发表于2018-05-04 16:55 被阅读29次

    一、基本说明

    微信小程序中对于用户的登录授权机制,是非常重要的。许多的业务都必须拿到具体的微信数据才能进行。我们在之前的微信小程序开发中,因为业务比较简单只有首页,并且在进入小程序的时候就必须进行授权,所以在操作的时候比较简单。随着业务的不断增加,业务场景的增多,之前的授权机制无法覆盖所有的业务场景。本次是在之前的授权机制的场景下,进行优化和改版。前端和后台进行配合,完善微信的授权机制,达到适应现有的业务。本文将讲解从老版本到现版本的微信授权机制的改版优化过程。
    注:5月10日的授权方案可在第四部分直接查看

    二、代码说明

    (1) 1.0版本微信授权

    本文对具体的小程序端和后台之间的微信授权不做具体的说明,详情请看文档。对于小程序我们封装了两个主要的工具模块类。用户模块user.js,请求模块request.js。
    user.js主要处理用户的基本信息模块,包括授权管理和登录,用户其他数据的绑定等。
    var user = {
      config:{},
      userinfo:null,
      // 初始化user模块,校验ukey
      init(data){
        this.config = data;
        var self = this;
        wx.checkSession({
          success:function(res){
            //存在登录状态
            self.checkUkey();
          },
          fail:function(){
            //过期-重新登录
            self.login();
          }
        });
      },
      // 发起微信登录接口 获取登录凭证code
      login(){
        var self = this;
        wx.login({
          success:function(res){
            self.config.code = res.code;
            wx.setStorageSync(self.config.storage.code,res.code);
            self.getWXUserInfo(true);
          },
          fail:function(res){
            console.log("user login fail")
          }
        })
      },
      // 获取用户信息
      getWXUserInfo(isthrough){
        var self = this;
        wx.getUserInfo({
          success:function(res){
            self.userinfo = res.userInfo;
            wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
            if (isthrough) {
              self.getUserUkey(res.rawData);
            }
          },
          fail:function(res){
            wx.openSetting({
              success:function(res){
                if(res.authSetting["scope.userInfo"]){
                  wx.getUserInfo({
                    success: res => {
                      // 可以将 res 发送给后台解码出 unionId
                      self.userinfo = res.userInfo;
                      wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
                      if (isthrough) {
                        self.getUserUkey(res.rawData);
                      }
                    }
                  });
                }
              }
            })
          }
        })
      },
      // 获取Ukey
      getUserUkey(rawData){
        var self = this;
        var data = {
          code: wx.getStorageSync(self.config.storage.code),
          rawData: rawData
        };
        wx.request({
          url:self.config.requrl+"/Wdservice/api/wxLogin",
          data: data,
          method:"GET",
          success:function(res){
            res = res.data;
            console.log(res);
            if(res.code==1){
              // 缓存Ukey          
               wx.setStorageSync(self.config.storage.ukey,res.result.ukey);
    
             // 缓存Ukey存储时间
              wx.setStorageSync(self.config.storage.ukeytime,new Date().getTime());
           }
          }
        })
      },
      // 检验ukey是否过期
      checkUkey(){
        var ukey = wx.getStorageSync(this.config.storage.ukey);
        var ukeyTime = wx.getStorageSync(this.config.storage.ukeytime);
        var tmpTime = new Date().getTime();
        var token = wx.getStorageSync(this.config.storage.ukey_licaiyi);
        if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12 || !token){
          ukey = "";
          this.login();
        } else {
          this.getWXUserInfo(false);
        }
      }
    }
    
    module.exports = user;
    
    
    request.js主要对请求的再封装,统一加入ukey。可对接口统一管理,增加扩展性。
    var app = getApp();
    
    // 打包ukey和参数
    function ukeyData (data) {
    
      var copyData = JSON.parse(JSON.stringify(data));
      var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
      var okUkeyData = Object.assign(copyData, ukey);
      return okUkeyData;
    }
    
    function post(data){
    
      req({
        url:data.url,
        method:"POST",
        data:ukeyData(data.data),
        succ:data.succ,
        fail:data.fail,
        complete:data.complete
      })
    }
    
    function get(data){
      req({
        url:data.url,
        method:"GET",
        data: ukeyData(data.data),
        succ:data.succ,
        fail:data.fail,
        complete:data.complete
      })
    }
    
    function req(data){
      wx.request({
        url: data.url,
        data: ukeyData(data.data),
        method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
        header: {
          'content-type':'application/x-www-form-urlencoded',
        }, // 设置请求的 header
        success: function(res){
          // success
          res = res.data;
          if(typeof data.succ === "function"){
            data.succ(res);
          }
        },
        fail:function(res){
          if(typeof data.fail === "function"){
            data.fail(res);
          }
        },
        complete:function(res){
          if(typeof data.complete === "function"){
            data.complete(res);
          }
        }
      })
    }
    
    const request = {
      post:post,
      get:get
    }
    
    module.exports = request;
    
    
    具体使用:在app.js中引用user.js初始化该组件即可。每次程序启动的时候会自动校验ukey的有效性,如果没有登录或者ukey过期会重新登录。
    var user = require("/tools/user");
    onLaunch: function () {
        // 检测用户
        user.init(config);
    }
    
    

    (2) 2.0版本微信授权

    随着业务的增加,小程序添加了扫码支付的功能。该功能在1.0版本的情况下会出现很重大的bug。虽然在app.js中初始化了user.js组件并得到ukey等信息。但是跳转到扫码页面的时候,因为登录请求都是异步执行的,在app.js完成授权之前,界面上的接口已经调用了。由于当时可能还没有获取到ukey导致界面上的接口报错,而获取不到需要的数据,界面一片空白,又没有刷新机制,导致出现严重的授权bug。对于这个业务场景下1.0的微信授权已经无法解决我们的问题,所以我们对user.js进行优化,以适应该业务。
    在user.js中添加登录完成的回调succ
    var user = {
      config:{},
      userinfo:null,
      init(data){
        this.config = data;
        var self = this;
        wx.checkSession({
          success:function(res){
            //存在登录状态
            self.checkUkey();
          },
          fail:function(){
            //过期-重新登录
            self.login(null);
          }
        });
      },
      login(succ){
        var self = this;
        wx.login({
          success:function(res){
            self.config.code = res.code;
            wx.setStorageSync(self.config.storage.code,res.code);
            self.getWXUserInfo(true, succ);
          },
          fail:function(res){
            console.log("user login fail")
          }
        })
      },
      getWXUserInfo(isthrough, succ){
        var self = this;
        wx.getUserInfo({
          success:function(res){
            self.userinfo = res.userInfo;
            wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
            if (isthrough) {
              self.getUserUkey(res.rawData, succ);
            }
          },
          fail:function(res){
            wx.openSetting({
              success:function(res){
                if(res.authSetting["scope.userInfo"]){
                  wx.getUserInfo({
                    success: res => {
                      // 可以将 res 发送给后台解码出 unionId
                      self.userinfo = res.userInfo;
                      wx.setStorageSync(self.config.storage.wxUser, res.userInfo);
                      if (isthrough) {
                        self.getUserUkey(res.rawData, succ);
                      }
                    }
                  });
                }
              }
            })
          }
        })
      },
      getUserUkey(rawData, succ){
        var self = this;
        var data = {
          code: wx.getStorageSync(self.config.storage.code),
          rawData: rawData
        };
        wx.request({
          url:self.config.requrl+"/Invest/api/wxLogin",
          data: data,
          method:"GET",
          success:function(res){
            res = res.data;
            if(res.code==1){
              wx.setStorageSync(self.config.storage.ukey,res.result.ukey);
              wx.setStorageSync(self.config.storage.ukeytime,new Date().getTime());
              self.getAuthenticationUserUkey(rawData, res.result.oid, succ);
            }
          }
        })
      },
      getAuthenticationUserUkey(rawData, openId, succ){
        var self = this;
        var data = {
          code: wx.getStorageSync(self.config.storage.code),
          rawData: JSON.parse(rawData),
          openId: openId,
          source: 2,
        };
        wx.request({
          url: self.config.couresurl + "/WechatApi/authentication",
          data: data,
          method: "POST",
          success: function (res) {
            res = res.data;
            if (res.resCode == 1) {
              wx.setStorageSync(self.config.storage.ukey_licaiyi, res.resObject);
              if (succ) {
                succ();
              }
            }
          }
        })
      },
      checkUkey(){
        var ukey = wx.getStorageSync(this.config.storage.ukey);
        var ukeyTime = wx.getStorageSync(this.config.storage.ukeytime);
        var tmpTime = new Date().getTime();
        var token = wx.getStorageSync(this.config.storage.ukey_licaiyi);
        if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12 || !token){
          ukey = "";
          this.login(null);
        } else {
          this.getWXUserInfo(false, null);
        }
      }
    }
    
    module.exports = user;
    
    
    在扫码的界面上我们首先校验ukey,如果存在直接调用详细的接口,如果获取不到ukey,说明微信尚未授权,我们主动调用user模块的login方法,在登录的成功回调成功之后再执行接口,便可解决该问题。
    if (wx.getStorageSync(app.globalData.config.storage.ukey)) {
        this.starToConfig();
     } else {
        app.globalData.wxUser.login(function(){
           self.starToConfig();
        });
     }
    
    该解决方案具有很大的局限性,如果添加一个新的业务场景我们就需要多次判断,扩展性很差。但是对于只有这样一个特殊场景来说,还是适用的。

    (3) 3.0版本微信授权

    随着业务的增加,微信小程序进行了更大的改版,首页不再强制需要授权。我的页面则需要授权。在这样的背景下,我们对授权机制和请求机制进行联合,把授权的时机判断放在后台进行联合判断。这样就可以解决2.0遗漏下来的授权比较局限的问题。对于所有需要授权的地方后台如果检测到我们的接口用户没有授权,便返回统一的未授权的错误码,小程序端对该状态进行统一的操作,强制用户登录并在回调后执行后续的接口。
    在request.js中, 我们对返回状态进行了细分,分为请求成功,请求错误,微信未授权等。
    var app = getApp();
    
    // 添加返回标识
    var ResultCode = {
      ResultCodeSucc: 1,
      ResultCodeFail: -1,
      ResultCodeNoAccredit: -2
    };
    
    function ukeyData (data) {
    
      var okUkeyData = null;
      if (data) {
        var copyData = JSON.parse(JSON.stringify(data));
        var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
        okUkeyData = Object.assign(copyData, ukey);
      } else {
        var ukey = { "ukey": wx.getStorageSync(app.globalData.config.storage.ukey) };
        okUkeyData = ukey;
      }
      return okUkeyData;
    }
    
    function post(data){
      req({
        url:data.url,
        method:"POST",
        data:ukeyData(data.data),
        succ:data.succ,
        error:data.error,
        no_accredit:data.no_accredit,
        fail:data.fail,
        complete:data.complete
      })
    }
    
    function get(data){
      req({
        url:data.url,
        method:"GET",
        data: ukeyData(data.data),
        succ:data.succ,
        error: data.error,
        no_accredit: data.no_accredit,
        fail:data.fail,
        complete:data.complete
      })
    }
    
    function req(data){
      wx.request({
        url: data.url,
        data: ukeyData(data.data),
        method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
        header: {
          'content-type':'application/x-www-form-urlencoded',
        }, // 设置请求的 header
        success: function(res){
          // success
          res = res.data;
         // 根据返回码 分发行为
         if (res.code == ResultCode.ResultCodeSucc) {
            // 请求成功
            if (typeof data.succ === "function") {
              data.succ(res);
            }
          } else if (res.code == ResultCode.ResultCodeNoAccredit) {
            if (typeof data.no_accredit === "function") {
              // 微信未授权
              app.globalData.wxUser.login(function () {
                data.no_accredit(res);
              });
            }
          } else {
            // 请求错误
            if (typeof data.error === "function") {
              data.error(res);
            }
          }
        },
        fail:function(res){
          // 请求失败
          if(typeof data.fail === "function"){
            data.fail(res);
          }
        },
        complete:function(res){
          if(typeof data.complete === "function"){
            data.complete(res);
          }
        }
      })
    }
    
    const request = {
      post:post,
      get:get
    }
    
    module.exports = request;
    
    
    接口改造:
    // 预约活动
      person_appointment_tap: function (e) {
        if (!this.data.isAppointment) {
          var self = this;
          request.post({
            url: app.globalData.config.requrl + "/Invest/Wx/subscribeMsg",
            data: {
              formid: e.detail.formId,
              activity_id: self.data.activity_id
            },
            header: {
              'content-type': 'application/x-www-form-urlencoded'
            },
            succ: function (res) {
              // 成功
              wx.showModal({
                title: '预约成功',
                content: "我们会在活动开始前给您发送消息提醒,请您及时关注",
                showCancel: false,
                confirmColor: "#ffcf00",
                success: function (res) {
                }
              })
              self.setData({ isAppointment: true });
            },
            error: function (res) {
              // 失败
              prompt.showModal(res.code == 2 ? "您已经预约过了!" : "预约失败,请稍后再试!", null);
              if (res.code == 2) {
                self.setData({ isAppointment: true });
              }
            },
            no_accredit: function (res) {
              // 在授权成功后再次调用该接口即可
              self.person_appointment_tap(e);
            }
          })
        }
      }
      
    
    我们在app.js中删除用户初始化代码,在需要用户第一次授权的地方就进行初始化即可。在3.0中,我们将初始化授权代码放在我的单页的onLoad方法中,之后只要在后台需要授权权限的地方,检测到未授权,就可提示到前端,让前端进行用户登录授权了。

    (4) 4.0版本微信授权(5月10日微信小程序授权调整)

    5月10日微信小程序官方突然发布了新的微信小程序授权机制,用户首次授权必须使用按钮的方式进行显示授权。对于这次更新我们也在原有的基础上进行了改版,因为小程序的入口繁多而且微信没有继承的概念和像window一样的顶级浮层,就导致我们必须对授权机制进行统一管理。我们这次删除了user.js,将授权及整个登陆机制放在封装好的授权组件内进行统一管理。并在接口处处理登陆失效的情况,这样就能很好的解决这次微信新授权机制对我们的影响。
    微信小程序获取用户信息接口优化调整文章 : 地址
    (1)comp-auto组件
    // index.js
    var app = getApp();
    
    Component({  
      properties: {
      },  
      data: {  
        show:false,
      },  
      methods: { 
        open(){
          this.setData({show:true});
        },
        close(){
          this.setData({show:false});
        },
          // 微信授权btn授权完成后会将用户信息返回,我们在这个方法中可以直接获取到用户数据
        userauth(){
          var self = this;
          wx.getUserInfo({
            success:function(res){
              wx.setStorageSync(app.globalData.config.storage.wxUser, res.userInfo);
              self.getUserUkey(res.rawData,true);
            },
            fail:function(res){
              console.log("userinfo fail",res);
            }
          })
        },
        checkUkey() {
          var ukey = wx.getStorageSync(app.globalData.config.storage.ukey);
          var ukeyTime = wx.getStorageSync(app.globalData.config.storage.ukeytime);
          var tmpTime = new Date().getTime();
          if (tmpTime - ukeyTime >= 1000 * 60 * 60 * 12) {
            wx.setStorageSync(app.globalData.config.storage.ukey, "");
            this.login();
          } else {
            this.getWXUserInfo();
          }
        },
        login(){
          var self = this;
          wx.login({
            success:function(res){
              wx.setStorageSync(app.globalData.config.storage.code,res.code);
              self.getWXUserInfo();
            },
            fail:function(res){
              console.log("user login fail")
            }
          })
        },
        getWXUserInfo() {
          var self = this;
          wx.getUserInfo({
            success: function (res) {
              wx.setStorageSync(app.globalData.config.storage.wxUser, res.userInfo);
              self.getUserUkey(res.rawData, false);
            },
            fail: function (res) {
              // 防止已授权的用户,主动在设置中关闭授权后,可以弹出授权组件
              self.open();
            }
          })
        },
        getUserUkey(rawData, close) {
          var data = {
            code: wx.getStorageSync(app.globalData.config.storage.code),
            rawData: rawData
          }
          var self = this;
          wx.request({
            url: app.globalData.config.requrl + "",
            data: data,
            method: "GET",
            success: function (res) {
              res = res.data;
              if (res.code == 1) {
                wx.setStorageSync(app.globalData.config.storage.ukey, res.result.ukey);
                wx.setStorageSync(app.globalData.config.storage.ukeytime, new Date().getTime());
                if (close) {
                  self.close();
                }
              }
            },
            fail: function (res) {
              console.log(res);
            },
            complete: function () {
    
              // 授权完成并获取到用户数据后,给页面提供的回调
              self.triggerEvent("callback");
            }
          })
        },
        
        // 校验是否授权
        judgeAuth: function () {
    
          var self = this;
          this.close();
          wx.getSetting({
            success(res) {
              if (res.authSetting['scope.userInfo']) {
                // 已经授权关闭授权窗口
                // 直接调用已授权的方法
                self.close();
                self.triggerEvent("callback");
                return;
              } else {
                // 未授权去授权
                self.checkSession();
              }
            }
          })
        },
    
        checkSession: function () {
    
          var self = this;
          wx.checkSession({
            success: function (res) {
              //存在登录状态
              self.checkUkey();
            },
            fail: function () {
              //过期-重新登录
              self.login();
            }
          });
        }
      },
    })
    
    // index.wxml 用户可将授权button放在需要的地方
        <button class="auth_btn" type="default" open-type="getUserInfo" bindgetuserinfo="userauth">微信授权</button> 
    
    (2)request.js
    修改之前接口未获取到用户信息的处理,删除自动登录代码将回调直接返回.request.js在3中给出了源码,这边就不再展示了。我们修改req方法。
    function req(data){
      wx.request({
        url: data.url,
        data: ukeyData(data.data),
        method: data.method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
        header: {
          'content-type':'application/x-www-form-urlencoded',
        }, // 设置请求的 header
        success: function(res){
          // success
          res = res.data;
          if (res.code == ResultCode.ResultCodeSucc) {
            if (typeof data.succ === "function") {
              data.succ(res);
            }
          } else if (res.code == ResultCode.ResultCodeNoAccredit) {
            // 用户登录失效,将回调直接放回
            if (typeof data.no_accredit === "function") {
              data.no_accredit(res);
            }
          } else {
            if (typeof data.error === "function") {
              data.error(res);
            }
          }
        },
        fail:function(res){
          if(typeof data.fail === "function"){
            data.fail(res);
          }
        },
        complete:function(res){
          if(typeof data.complete === "function"){
            data.complete(res);
          }
        }
      })
    }
    
    (3)在page中使用comp-auto组件
    1.在onShow方法中校验授权情况,未授权展示授权组件,让用户授权
      /**
       * 生命周期函数--监听页面显示
       */
      onShow: function () {
        this.selectComponent("#compauth").judgeAuth();
      },
    
    2.书写授权组件回调,在此方法中书写只有授权才能调用的接口
      callback: function () {
        this.person_appointment_tap();
      }
    
    3.修改需要授权才能使用的接口
     // 预约活动
      person_appointment_tap: function (e) {
        if (!this.data.isAppointment) {
          var self = this;
          request.post({
            url: app.globalData.config.requrl + "/Invest/Wx/subscribeMsg",
            data: {
              formid: e.detail.formId,
              activity_id: self.data.activity_id
            },
            succ: function (res) {
              // 成功
              wx.showModal({
                title: '预约成功',
                content: "我们会在活动开始前给您发送消息提醒,请您及时关注",
                showCancel: false,
                confirmColor: "#ffcf00",
                success: function (res) {
                }
              })
              self.setData({ isAppointment: true });
            },
            error: function (res) {
              // 失败
              prompt.showModal(res.code == 2 ? "您已经预约过了!" : "预约失败,请稍后再试!", null);
              if (res.code == 2) {
                self.setData({ isAppointment: true });
              }
            },
            no_accredit: function (res) {
              // 吊起授权组件登录方法,授权结束后会调用回调方法callback,再次请求该接口
            self.selectComponent("#compauth").login();
            }
          })
        }
      }
    
    4.在index.json中添加组件
     {
      "usingComponents": {
       "compauth": "../../components/comp-auth/index"
      }
    }
    
    5.在index.wxml中添加组件
    <compauth id="compauth" bind:callback="callback"></compauth>
    
    这样优化后可以完善授权机制但是也有很大的弊端。因为微信没有顶层视图,所以导致了我们必须在每个需要授权的页面添加组件。如果再修改授权机制,每个页面都要进行维护,会产生很多的冗余代码,让项目变得难以维护。希望微信小程序之后可以考虑添加顶层视图,减少码农的痛苦。

    本文将持续对授权机制的处理进行更新,谢谢大家的观看,有建议或者疑问欢迎给我留言。

    相关文章

      网友评论

        本文标题:微信小程序 微信授权前后端解决方案 (更新至5.10授权机制)

        本文链接:https://www.haomeiwen.com/subject/ljzdrftx.html