把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

微信小程序源码解析

最近在写小程序,转载一些别人的文章,学习下经验。

学以致用123 
2018.05.09 13:46 字数 1536 阅读 475评论 0

关键字:微信小程序登录、login、request、getuserinfo

本文是对使用 wafer2 实现登录的分析,对使用 wafer2 框架的小程序适用,不涉及 腾讯云音视频登录功能外的其它功能。

参考:https://github.com/tencentyun/wafer2-client-sdk

简介

腾讯云音视频小程序的 demo 代码使用的是腾讯云 wafer2 框架。

wafer2 简化了用户登录方法,在 app.js 中设置 qcloud 的 LoginUrl 然后调用 qcloud.login 或者使用 login 参数设置为 true 的 qcloud.request 请求即可。

这里先贴出经过下面分析得出的使用 qcloud 的 login 和 request 的方法。

首先,在 app.js 中设置 qcloud 的 LoginUrl :

qcloud.setLoginUrl(config.url + 'login');

如果只需要后台实现登录请求,那么使用 qcloud.login:

qcloud.login({
    success: function (userInfo) {
        console.log('登录成功', userInfo);
    },
    fail: function (err) {
        console.log('登录失败', err);
    }
});

setLoginUrl 中设定的 url 的后台视图需要解析 login 的 GET请求的请求头获得code、encryptedData 和 iv ,返回的响应至少需要包含 code,如果成功执行,code 需要设为 0 而且还需要返回 data ,data 中至少需要包含 skey 、userinfo 和 iminfo 三项内容。

如果后台请求之前需要自动登录,那么使用 qcloud.request :

qcloud.setLoginUrl(myloginUrl);
qcloud.request({
    login: true,
    url: myUrl,
    success: function (response) {
        console.log(response);
    },
    fail: function (err) {
        console.log(err);
    }
});

注意,qcloud.request 通过构造请求头向后台传送数据。

下面的内容是具体代码分析。

现有代码分析

demo 代码并没有使用 wafer2 内置的登录函数,而是在与 config.js 同一个目录下的 getloginInfo.js 中通过 wx.login 和 wx.getUserInfo 实现登录的。这里的实现代码为:

// 获取微信登录信息,用于获取openid
function getLoginInfo(options) {
  wx.login({
    success: function (res) {
      if (res.code) {
        console.log('获取code成功',res.code);
        options.code = res.code;
        // 获取用户信息
        wx.getUserInfo({
          withCredentials: false,
          success: function (ret) {
            options.userName = ret.userInfo.nickName;
            proto_getLoginInfo(options);
          },
          fail: function() {
            proto_getLoginInfo(options);
          }
        });
      } else {
        console.log('获取用户登录态失败!' + res.errMsg);
        options.fail && options.fail({
          errCode: -1,
          errMsg: '获取用户登录态失败,请退出重试'
        });
      }
    },
    fail: function (res) {
      console.log('获取用户登录态失败!' + res.errMsg);
      if (ret.errMsg == 'request:fail timeout') {
        var errCode = -1;
        var errMsg = '网络请求超时,请检查网络状态';
      }
      options.fail && options.fail({
        errCode: errCode || -1,
        errMsg: errMsg || '获取用户登录态失败,请退出重试'
      });
    }
  });
}

这里的代码逻辑非常清晰,用流程图表示是这样的:


 
getlogininfo_wxlogin.jpg

上述流程中 wx.login 及 wx.getUserInfo 的详细信息请查看官网文档。

代码执行成功会调用 proto_getLoginInfo 函数,如果失败,则运行调用输入参数的失败处理函数。

proto_getLoginInfo 在 getLoginInfo 的下面,主要实现腾讯云的 RoomService 登录功能,但是由于 RoomService 登录需要传入后台计算得到的用户签名,这里使用了 wx.request 向后台发送 GET 请求,发送请求的具体代码为:

function proto_getLoginInfo(options) {
  wx.request({
    url: config.serverUrl + '/weapp/utils/get_login_info',
    data: { userIDPrefix: 'weixin', code: options.code },
    method: 'GET',
    header: {
      'content-type': 'application/json' // 默认值
    },
    success: function (ret) {
      if (ret.data.code) {
        console.log('获取登录信息失败,调试期间请点击右上角三个点按钮,选择打开调试');
        options.fail && options.fail({
          errCode: ret.data.code,
          errMsg: ret.data.message + '[' + ret.data.code + ']'
        });
        return;
      }
      console.log('获取IM登录信息成功: ', ret.data);
      ...
    },
    fail: function (ret) {
      console.log('获取IM登录信息失败: ', ret);
      if (ret.errMsg == 'request:fail timeout') {
        var errCode = -1;
        var errMsg = '网络请求超时,请检查网络状态';
      }
      options.fail && options.fail({
        errCode: errCode || -1,
        errMsg: errMsg || '获取登录信息失败,调试期间请点击右上角三个点按钮,选择打开调试'
      });
    }
  });
}

这里的 wx.request 向后台发送 code 参数,后台使用 code 换取 openid 然后计算用户签名,代码中的登录功能结束。

然而, wafer2 的处理用户登录流程是这样的:

 
wxlogin.png

由于这里指向后台传入了 code ,所以后台只能实现红色框中的内容。前台也没有实现紫色框中的保存会话功能。

我们可以将 wx.getUserInfo 的 withCredentials 设置为 true ,然后为 wx.request 传入 encryptedData 和 iv ,完成上图中后台的工作,并使用 wx.setStorageSync(KEY,DATA) 保存会话。

然而,我们完全可以使用 wafer2 内置函数实现登录。

使用 wafer2 内置函数实现登录

查看 app.js 文件,我们可以看到:

var qcloud = require('./lib/index');

App({
  onLaunch: function () {
    // 展示本地存储能力
    // qcloud.setLoginUrl(config.url + 'getwxinfo');
    // qcloud.setLoginUrl(config.url + 'login');
  },
  globalData: {
    userInfo: null
  }
})

这里注释掉了 qcloud.setLoginUrl 。打开定义为 qcloud 的 ./lib/index 文件,代码包括:

var constants = require('./constants');
var login = require('./login');
var Session = require('./session');
var request = require('./request');
var Tunnel = require('./tunnel');
var SocketTunnel = require('./socketTunnel');

var exports = module.exports = {
    login: login.login,
    setLoginUrl: login.setLoginUrl,
    LoginError: login.LoginError,

    clearSession: Session.clear,

    request: request.request,
    RequestError: request.RequestError,

    Tunnel: Tunnel,
    SocketTunnel: SocketTunnel,
};

// 导出错误类型码
Object.keys(constants).forEach(function (key) {
    if (key.indexOf('ERR_') === 0) {
        exports[key] = constants[key];
    }
});

从上面的代码中,我们可以看到,除了 setLoginUrl ,qcloud 还有 login、clearSession、request、Tunnel、SocketTunnel、LoginError、RequestError 等方法。

login 函数需要在 app.js 中使用 setLoginUrl 设置与后台交互的 url 。设置 url 后,我们需要了解 login 是如何工作的,从而确认后台如何与 login 交互。

login.js 分析

打开 wxlite/lib/login.js 文件,我们可以看到模块输出三个方法:

module.exports = {
    LoginError: LoginError,
    login: login,
    setLoginUrl: setLoginUrl,
};

setLoginUrl

我们先看 setLoginUrl ,这个函数的功能非常简单,设置后台登录 url :

var setLoginUrl = function (loginUrl) {
    defaultOptions.loginUrl = loginUrl;
};

代码中,只是将 defaultOptions 的 loginUrl 设置为输入的 loginUrl 。这里 defaultOptions 是 login.js 定义的全局变量,它的初始化设置为:

var defaultOptions = {
    method: 'GET',
    success: noop,
    fail: noop,
    loginUrl: null,
};

这里的 noop 为:

var noop = function noop() {};

LoginError

LoginError 是处理登录错误的,其代码为:

var LoginError = (function () {
    function LoginError(type, message) {
        Error.call(this, message);
        this.type = type;
        this.message = message;
    }

    LoginError.prototype = new Error();
    LoginError.prototype.constructor = LoginError;

    return LoginError;
})();

login

流程

login 方法用于实现登录功能,代码流程为:

 
login流程.jpg

这里,实际的动作为 获取 session,检查 session并进行相应操作。

获取 session 使用的 Session.get() 方法,打开 Session 对应的 session.js 文件,get() 方法的功能为从微信缓存中获取 'weapp_session_F2C224D4-2BCE-4C64-AF9F-A6D872000D1A' 对应的值。这个值是在第一运行 doLogin 时设置的。那么,我们接下来分析 doLogin 。

doLogin

doLogin 的代码为:

var doLogin = () => getWxLoginResult(function (wxLoginError, wxLoginResult) {
    if (wxLoginError) {
        options.fail(wxLoginError);
        return;
    }
    
    var userInfo = wxLoginResult.userInfo;

    // 构造请求头,包含 code、encryptedData 和 iv
    var code = wxLoginResult.code;
    var encryptedData = wxLoginResult.encryptedData;
    var iv = wxLoginResult.iv;
    var header = {};

    header[constants.WX_HEADER_CODE] = code;
    header[constants.WX_HEADER_ENCRYPTED_DATA] = encryptedData;
    header[constants.WX_HEADER_IV] = iv;

    // 请求服务器登录地址,获得会话信息
    wx.request({
        url: options.loginUrl,
        header: header,
        method: options.method,
        data: options.data,
        success: function (result) {
            var data = result.data;

            // 成功地响应会话信息
            if (data && data.code === 0 && data.data.skey) {
                var res = data.data
                if (res.userinfo) {
                    Session.set(res.skey);
                    options.success(res.iminfo);
                } else {
                    var errorMessage = '登录失败(' + data.error + '):' + (data.message || '未知错误');
                    var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, errorMessage);
                    options.fail(noSessionError);
                }

            // 没有正确响应会话信息
            } else {
                var noSessionError = new LoginError(constants.ERR_LOGIN_SESSION_NOT_RECEIVED, JSON.stringify(data));
                options.fail(noSessionError);
            }
        },

        // 响应错误
        fail: function (loginResponseError) {
            var error = new LoginError(constants.ERR_LOGIN_FAILED, '登录失败,可能是网络错误或者服务器发生异常');
            options.fail(error);
        },
    });
});

dologin 实行的是 getWxLoginResult 方法,这里定义的是方法成功或失败的回调方法。getWxLoginResult 的定义为:

var getWxLoginResult = function getLoginCode(callback) {
    wx.login({
        success: function (loginResult) {
            wx.getUserInfo({
                success: function (userResult) {
                  console.log('[userResult]', userResult);
                    callback(null, {
                        code: loginResult.code,
                        encryptedData: userResult.encryptedData,
                        iv: userResult.iv,
                        userInfo: userResult.userInfo,
                    });
                },

                fail: function (userError) {
                  console.log('[userError]', userError);
                    var error = new LoginError(constants.ERR_WX_GET_USER_INFO, '获取微信用户信息失败,请检查网络状态');
                    error.detail = userError;
                    callback(error, null);
                },
            });
        },

        fail: function (loginError) {
          console.log('[loginError]', loginError);
            var error = new LoginError(constants.ERR_WX_LOGIN_FAILED, '微信登录失败,请检查网络状态');
            error.detail = loginError;
            callback(error, null);
        },
    });
};

结合这两个方法,可以分析 doLogin 的逻辑为:

 
doLogin流程.jpg

从这里我们可以看到,后台处理登录请求的视图将接收的 code、encryptedData 和 iv 放在请求头中,后台返回的响应至少需要返回 code、data 两个元素,如果成功执行,code 需要设为 0 ,data 中至少需要包含 skey 、userinfo 和 iminfo 三项内容。

用法

我们已经了解了 login 的基本原理,那么,我们应该怎样使用 login 呢?

与 login.js 位于同一文件夹的 request.js 提供设置 login 为 ture 的 Request 请求实现先登录后请求,我们看下这个方法是怎么使用 login.js 的。

request 中,如果定义了 login 为 true ,则requirLogin 为 true,请求将执行doRequestWithLogin()。

if (requireLogin) {
    doRequestWithLogin();
} else {
    doRequest();
}

doRequestWithLogin() 定义了登录后再请求的函数:

function doRequestWithLogin() {
    loginLib.login({ success: doRequest, fail: callFail });
}

如果登录成功,将执行 doRequest() ,失败则执行 callFail()。

doRequest 执行时首先构造 authHeader 将从微信缓存中获取的 'weapp_session_F2C224D4-2BCE-4C64-AF9F-A6D872000D1A' 的值写到 X-WX-Skey 请求头中进行发送。

问题

整个流程中,如果登录过之后再次调用 login ,返回 Session.get() 中的 userinfo 。但是在首次登录成功时或者 request 设置login 为 true 的 request 成功时,Session.set() 设置的是 skey 的值,并没有 userInfo 的值。是否可以扩展 Session 中的方法来保存和读取用户信息。

posted on 2019-04-18 14:23  无影尊者  阅读(2960)  评论(0编辑  收藏  举报

导航

$(function(){ $(".audioplay").simplemusic({ autoplay:true, urls:["http://m1.music.126.net/gpi8Adr_-pfCuP7ZXk_F2w==/2926899953898363.mp3"] }); });