先讲讲微信小程序的机制,当用户首次打开一个小程序浏览时,此时可认为是“游客模式”,就像我们浏览一个网页一样,我们不知道用户的任何信息,但由于小程序是以微信为载体,所以在静默的情况下,我们可以拿到一部分用户的信息,那就先从这种“静默登录”开始讲起。

一、静默登录

说明:其实现在的用户相当注重“用户体验”和隐私,我只想随便看看你的小程序,这时候各种弹窗,需要授权,用户可能会觉得很烦,我就看看,你凭什么要我的授权要我的隐私?所以这种情况下,对于一些业务和用户信息关联性不是很大的小程序,可以采取“静默登录”,类似于一些 新闻信息类小程序,资讯展示类小程序,简单的电商购物小程序,都可以使用“静默登录”,前端获取到用户的code,发送给后端,后端利用code使用微信提供的接口得到用户的openId,一切登录行为没有任何弹窗、确认,用户只要进入小程序就能自动完成这个步骤,所以“静默登录”是用户体验最好的一种方式;

优点:在客户端界面上,没有任何弹窗、确认,静默执行,用户体验最好;

缺点:只能得到用户的openId,无法得到用户的头像、昵称等更多信息,一家公司如有多个小程序,用户信息无法同步;

原理:前端通过小程序wx.login()接口静默获取到用户的code点击查看官方wx.login的说明),前端将code发送给后端服务器后台调用 auth.code2Session,使用 code 换取 openid 和 session_key 等信息点击查看官方auth.code2Session的说明),那么数据库就可以以openId作为每个用户的唯一身份标识,记录该账号的信息;

代码:

/**
 * @description: 小程序登录
 * @param {type} 
 */
const login = () => {
    const promise = new Promise((resolve, reject) => {
        const _member_id = getStorage('member_id');
        if (_member_id) {
            resolve(console.log('has _member_id'));
        } else {
            showLoading();
            wx.login({
                success: (result) => {
                    post('/auth/wxmini/user', {
                        code: result.code,
                    }).then(res => {
                        wx.hideLoading(); //隐藏loading
                        const resData = res.data;
                        if (resData.status) {
                            const _member_id = Number(resData.data.member_id);
                            wx.setStorageSync('member_id', _member_id);
                        } else {
                            console.log(resData.message);
                        }
                        resolve(console.log('get user_id Done'));
                    }).catch(err => {
                        console.log(err);
                    });
                },
            });
        }
    });
    return promise;
};

我封装的小程序登录方法逻辑是这样的:

1.先获取localStorage中的用户ID,如果已经存在,跳出方法;

2.如果不存在用户ID,则通过wx.login静默获取到用户的code,将code发送给后端,后端解析成功后,将该用户的ID再次返给前端,前端将用户ID存储至localStorage中,需要用的时候直接从localStorage中取出即可;

具体业务场景:当时我们开发最初版本的小程序时,非常重视用户体验,能不弹窗绝不弹窗,绝不用任何方式骚扰用户、诱导用户留下隐私信息,当时的小程序核心功能有2个,一个是扫码支付,一个是用豆豆(虚拟积分)在商城内购买商品,用户扫码支付时,需要生成订单,用户发生购买行为时,也需要生成订单,所以需要静默登录,获取openId来识别,至于用户发生购买行为后,收货人收货地址等信息,是需要用户主动去填写的,所以静默登录完全没有问题,我们从后台管理系统中,只能看到张三购买了XXX,收货地址在哪里,收货手机号码是多少,但张三的微信昵称、头像等一概不知;

随着业务逐渐发展,运营部门对我们有了新的要求,后台需要知道使用小程序人群的具体信息,昵称、头像、性别、在什么省什么市,越详细越好,与此同时,可能有跨小程序(或微信其它应用)用户信息同步的需求,所以我们需要unionid(同一用户,对同一个微信开放平台下的不同应用,unionid是相同的,点击查看官方的unionid说明),此时此刻,为了运营,为了信息同步,我们只能牺牲一部分用户体验,对用户开启弹窗授权,这就是接下来要讲的小程序授权。

二、弹窗授权

先看看长什么样子

说明:就是一个恶心的弹窗,微信为了保护用户的隐私,需要用户主动去点击“允许”,才能获取到对应信息,如用户头像、昵称、地区、性别、encryptedData(包括敏感数据在内的完整用户信息的加密数据,后端可以换取unionid),点击查看微信官方说明

优点:可以一次性拿到用户的所有相关信息,便于支持运营、数据分析、跨微信应用信息同步;

缺点:会弹出弹窗,让用户主动点击“允许”,大部分用户比较反感,有可能直接关闭小程序;

原理:用户主动点击“允许”后,前端将获取到的用户详细信息发送给后端,补全用户之前没有的信息;

注意点:

1.授权和登录完全没有冲突,不论是“静默登录”还是授权,使用wx.login()获取code给后端换openId都是必须的!这个是必不可少的!做授权的目的是为了得到更多用户信息以及unionid

2.由于小程序从2018年更新后,获取用户详细信息的接口wx.getUserInfo基本上等于作废,现在必须要用户点击button按钮后,才能弹出该窗口;

3.用户有可能点击“取消”,这里需要做优雅降级处理;

4.授权时,最好单独开一个页面,里面有一个按钮,文案内容类似于“点击开始使用”或“一键登录”,提升用户好感度;

5.如果只是一个单独的授权页面,用户很有可能点击手机的物理返回键跳出该页面 或 使用全面屏手势滑动退出该页面,这种情况必须要考虑到,具体何时授权,怎么授权,下面会做详细讲解;

代码:

先不用管具体何时授权,怎么授权,我们先做一个单独的授权页面出来,该页面的核心点在于button按钮

<view class="wrap">
    <image class="bg" mode="widthFix" src="/static/image/auth/bg.jpg"></image>
    <button open-type="getUserInfo" bindgetuserinfo="onGotUserInfo" class="auth">登录开启兑换</button>
</view> 

代码非常简单,一张背景图,加一个按钮就行,但这个按钮,不能自己随便写,必须是小程序组件button,点击查看button按钮详情说明

通过官方文档,我们可以看到,button有一个open-type的属性,首先将button的open-type设置为getUserInfo,意思是当前按钮的功能为获取用户信息:


在button中,还有一个属性,叫作bindgetuserinfo,用户点击该按钮时,会返回获取到的用户信息,回调的detail数据与wx.getUserInfo返回的一致,open-type="getUserInfo"时有效:

 


所以在代码中,将button组件的open-type属性与bindgetuserinfo属性定义好,就能获取到用户信息;在wxml中,定义好bindgetuserinfo的具体方法名,js中调用,点击后就能获取到用户信息;

  /** 
   * @description: 点击授权按钮,获取用户信息
   * @param {type} 
   */
  onGotUserInfo(e) {
    if (e.detail.errMsg === 'getUserInfo:ok') { //用户同意授权
      this.setData({
        userInfo: e.detail.userInfo,
        iv: e.detail.iv,
        encryptedData: e.detail.encryptedData
      });
    } else { //用户拒绝授权
      core.toast('请授权后使用', 2000);
    }
  },

从js方法中可以看到,如果用户同意了授权,那么就能拿到一系列信息,可以暂存在本地,后面将信息全部上传到服务器即可;

这里也做了用户拒绝授权的优雅降级处理,当用户拒绝授权时,授权页面保持不动,弹出提示,让用户授权后再使用小程序,既然我们已经做了弹窗授权,在某些业务场景下,务必保证拿到用户信息后,才能执行后续操作。


其实到这里,登录和授权流程已经结束了,但要在实际的业务场景中更好地操作处理,你应该接着看下去:

可能有人想起我之前提到的注意点,如果用户使用物理返回键返回,或使用全面屏手势滑动,岂不是直接将授权页面跳出了?其实在最初设计这套授权流程时,我并没有考虑那么全面,后面果然出现了这个问题,用户通过快速滑动屏幕,点击返回键,能直接跳出授权页面!导致某些拼团活动的用户没有头像和昵称,所以授权的流程会在下面做详细讲解:

其实在正规的开发流程中,前端请求接口是需要发送一些信息的,比如带着token,后端验证当前用户的状态及保证安全性,因为某些接口及具体业务,必须要有用户的详细信息才能请求;

所以前端在发起请求时,也需要做一些处理,当请求结果告诉前端,用户没有登录、没有详细信息等,那么此时前端页面就要跳转到授权页面去,就算用户使用某些方法跳出了授权页也无所谓,因为执行关键操作时,接口会做验证,一旦验证失败,前端要么告知用户,要么跳转到授权页面,下面看看我封装的小程序请求流程以及其中穿插的异常处理:

关键点在于请求后,前端拿到了服务器返回的结果,其中红色文字标识的部分,都可以算作异常,同时前端做出相应处理,该跳转页面就跳转页面,该弹出错误提示就弹出错误提示,属于通用的一种做法,全局通过请求的结果去处理,也是目前最为科学、规范的方法;

代码:

/**
 * @description: 微信小程序二次封装后的请求方法
 * @param {String} url 请求地址
 * @param {Object} data 请求参数
 * @param {String} method HTTP请求方法,默认为'post'
 * @param {String} loadingType loading样式,默认为全屏遮罩显示,文字为加载中
 * @return {Object} promise
 */
const wxRequset = (url, data, method = 'post') => {
  const promise = new Promise((resolve, reject) => {
    wx.request({
      url: url ? url : null,
      header: {
        'Authorization': `Bearer ${app.globalData.accessToken}`,
        'enctype': 'raw',
      },
      data: {
        ...data,
        ...app.globalData.otherParams
      },
      method: method,
      success: (res) => {
        wx.hideLoading();   //隐藏loading
        const resData = res.data;
        const _code = Number(resData.code);
        const _status = Number(resData.status);
        //请求成功
        if(_status === 200 && _code === 0){
            resolve(resData.data);
        }
        //没有登录, 需要跳转到登录页面
        else if(_code === 401 || _code === 403){
            resolve(console.error('没有登录, 跳转登录'));
        }
        //出现错误
        else if(_status === 200 && _code !== 0){
            resolve(console.error('出现错误, 进行提示'));
        }
        //服务器故障,跳转至维护页面
        else if(_code === 500 || _code === 503){
            resolve(console.error('服务器故障,跳转至维护页面'));
        }
      },
      fail: (err) => {
        reject(err);
      }
    });
  });
  return promise;
};

但这种情况下,需要注意几个点:

业务中,是否要求了登录的时效性,多长时间后,登录会过期失效?7天、1个月、3个月?如果有此类需求,必须加入登录失效功能;

电商类小程序,当用户点击购物车、个人中心时,需要做一些优雅降级处理,其中我发现“网易严选+”小程序就做得特别好:

为什么说它做得好,什么叫优雅降级处理,就是我们没有强迫性地弹出授权,强制让用户去操作,因为购物车、个人中心,是和用户身份信息强关联的业务模块,这里采用预留信息展示,只有用户主动去点击触发,才会弹出登录页面;

所以如果按照我画的流程图那种方式,就必须注意下,前端必须先进行判断用户是否登录成功,然后在用户登录成功的前提下,再去请求购物车、个人中心接口,这样一来,用户体验就会提升很多,非关键步骤对所有用户采取“游客模式”,关键步骤(如下单购买、结算、查看购物车、进入个人中心等)采取“友好的强登录提醒”,在用户不反感的情况下,去做登录授权这件事情,就友好地多。

技术永远为人服务,人性化、令人舒适的技术,才是好的技术。