微信小程序源码解析
最近在写小程序,转载一些别人的文章,学习下经验。
关键字:微信小程序登录、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 || \'获取用户登录态失败,请退出重试\'
});
}
});
}
这里的代码逻辑非常清晰,用流程图表示是这样的:
上述流程中 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 的处理用户登录流程是这样的:
由于这里指向后台传入了 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 方法用于实现登录功能,代码流程为:
这里,实际的动作为 获取 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 的逻辑为:
从这里我们可以看到,后台处理登录请求的视图将接收的 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 中的方法来保存和读取用户信息。
请发表评论