一, 需求与逻辑分析
产品需求为进入小程序, 实现用户的自动登录. 由于已经完成了 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. 只有在确保初始页为登录页时, 也就是确保小程序为重新进入, onLaunch 才执行自动登录. 其他的等待页面接口判断用户是否登录, 再跳转用户登录. 这种的坏处是其他页面需要增加的逻辑比较多, 稍微复杂. 或者直接将自动登录的逻辑写在登录页即可.
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
中间页授权获取 unionid
- // 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 => { },
- })
- },
- }
- })
- ...
来源: http://www.jianshu.com/p/3646b76175ba