一、 需求与逻辑分析
产品需求为进入小程序,实现用户的自动登录。由于已经完成了app端和h5端的产品,用户登录也加入了第三方登录,在后台区分用户是根据手机号,因此各个平台的登录最主要的是与手机号绑定。而qq,微信公众号和小程序平台决定用互通的unionId,不使用OpenId登录,最终将uniodId与对应的手机号关联。
小程序平台有两种登录方式,一种是账户密码登录,另一种是微信自动登录。
二、自动登录的切入点和冲突
将登录页作为初始页,在进入登录页时先执行自动登录,如果通过wx.login可以获取uniodId,用户也绑定了手机号,就可以成功登录,否则跳转相应的页面。
起初是决定将自动登录放进app.js的生命周期onShow里执行,保证每次进入小程序都可以执行自动登录,但是这里就有了冲突。
如果是进入了其他页面,例如登录页->a->b->c,这时候突然退出刷了下微博,小程序进入了后台状态,再进入小程序,还是c页面,但这时候app.js也会重新执行。
假定c页面是我的资料页面,接口也会审查用户登录状态,如果登录状态掉线,未登录c.js的处理逻辑是跳转登录页。app.js和c.js是异步的,这俩js都检查到了用户未登录状态,这时候就会导致跳转两次登录页。
删除掉c.js接口相关的判断显然是不合理的。那么该如何处理这种app.js和进入的页面.js的冲突呢?
两种方案:
1.app.js的onShow周期下确保初始页为登录页时,执行自动登录。其他的等待页面接口判断用户是否登录,再跳转用户登录。这种的坏处是其他页面需要增加的逻辑比较多,稍微复杂。
2.进入小程序onLaunch执行登录,其他情况自动登录根据页面黑白名单验证的判断,在需要判断登录的页面加上登录状态过滤,进入页面需要登录的就用loginCheck,点击页面某个方法判断未登录状态跳转登录的调goLogin。大型项目提前做好构建后续会省事很多(由于篇幅原因,第二种方案我会另写一篇文章:微信小程序自动登录逻辑分析与实现,页面黑白名单管理(下))。
3.将app.js和需要判断用户登录状态页面.js的异步操作改成同步,这样确保app.js执行完登陆后再处理其他页面逻辑,就不会造成重复登录的状态。
三、使用
1.如果小程序页面较少,单个页面处理逻辑也很方便,采用第一种方案:
//app.js
App({
onLaunch: function () {
this.firstLogin();
},
firstLogin: function () {
let that = this;
wx.login({
success: res => {
if (res.code) {
ajax.request({
url: '/api/system/wx?code=' + res.code,
method: 'get',
success: res => {
if (res.statusCode == 200 && res.data.openid) {
that.globalData.openId = res.data.openid;
let nowRoute = ajax.getCurrentPageUrlWithArgs();
// 判断当前页是否为入口登录页,如果是,自动登录;如果不是,等待用户在页面的操作判断接口是否需要登录
if (nowRoute == '/pages/login/index') {
// 如果有unionid就自动登录
if (res.data.unionid) {
that.goLogin(res.data.unionid);
} else {
// F-1-2-3:判断当前是否是登录页,不是跳转登录页
// 当前页保存在缓存
let nowRoute = ajax.getCurrentPageUrlWithArgs();
wx.redirectTo({
url: '/pages/login/index'
})
}
}
}
},
fail: function (error) {
console.log(error)
}
});
} else {
// 微信登录失败
wx.showToast({
title: '网络异常,请稍后重试',
duration: 2000,
icon: 'none'
})
}
},
fail: res => { },
complete: res => { },
})
},
// 微信登录
goLogin: function (unionid) {
let that = this;
ajax.request({
url: '/api/user/第三方登录',
data: {
LoginType: 'weixin',
unionid: unionid
},
method: 'post',
success: res => {
// unionid保存在全局,保存手机号
that.globalData.unionid = unionid;
if (that.unionidCallback) {
that.unionidCallback(unionid);
}
if (res.statusCode == 200) {
if (res.data.code == 0 && res.data.data.usertoken) {
// 登录成功,保存相关信息,跳转首页
wx.redirectTo({
url: "/pages/index"
})
} else if (res.data.code == -1){
//用户未绑定手机号,选择绑定手机或者使用账户密码登录
wx.showModal({
title: '提示',
content: '该微信账户未绑定手机号,是否使用账户密码登录?',
cancelText: '密码登录',
confirmText:'绑定手机',
success(res) {
if (res.confirm) {
// 绑定手机号
wx.redirectTo({
url: '/pages/login/phone/index'
})
} else if (res.cancel) {
// 使用账户密码登录
wx.redirectTo({
url: '/pages/login/index'
})
}
}
})
} else{
wx.showToast({
title: '网络异常,请稍后重试',
duration: 10000,
icon: 'none'
})
}
}
},
fail: function (error) {
console.log(error)
wx.showToast({
title: '登录失败,请检查网络稍后重试',
duration: 2000,
icon: 'none'
})
}
});
},
globalData: {
openId: '',
unionid: '',
}
})
重点贴一下无unionid跳转的中间授权页的代码,解密encryptedData用了decryption.js+crypto.js
// decryption.js
const CryptoJS = require('./crypto-js/crypto-js.js');
// 获取用户信息解密
function decryptUserInfo(session_key, encryptedData, iv) {
// var encryptedData = CryptoJS.enc.Base64.parse(encryptedData);
var key = CryptoJS.enc.Base64.parse(session_key);
var iv = CryptoJS.enc.Base64.parse(iv);
try {
// 解密
var decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
asBpytes: true,
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
var decryptResult = CryptoJS.enc.Utf8.stringify(decrypted).toString();
var value = JSON.parse(decryptResult);
} catch (err) {
console.log(err);
}
return value;
}
module.exports = {
decryptUserInfo: decryptUserInfo
}
// pages/fastlogin/fastlogin.js
const app = getApp();
const jiemi = require('../../../utils/decryption.js');
Component({
/**
* 组件的初始数据
*/
data: {
nickname: '',
photopath: '',
gender: ''
},
/**
* 组件的方法列表
*/
methods: {
// 1.关注用户是否授权,未授权页面停留,授权后获取加密信息
onGotUserInfo(e) {
let that = this;
if (e.detail.errMsg == "getUserInfo:ok") {
console.log(e, '已经授权用户信息');
this.getLcode(e.detail.encryptedData, e.detail.iv);
} else {
console.log("用户未同意授权")
}
},
// 2.解密unionId
getLcode(encryptedData, iv) {
let that = this;
wx.login({
success: res => {
if (res.code) {
ajax.dotnetRequest({
url: '/api/system/WX?type=2&code=' + res.code,
method: 'get',
success: sessionRes => {
if (sessionRes.statusCode == 200 && sessionRes.data.session_key) {
// 2-1:解密unionid
let jiemiRes = jiemi.decryptUserInfo(sessionRes.data.session_key, encryptedData, iv);
let unionId = jiemiRes.unionId;
// 2-2:获取成功,执行登录
if (unionId) {
app.goLogin(unionId)
}
}
},
fail: function (error) {
console.log(error)
}
});
} else {
console.log(res, 'code获取失败');
}
},
fail: res => {
console.log(res, 'code获取失败');
},
complete: res => { },
})
},
}
})
···