关于前端:微信小程序登录的前端设计与实现

3次阅读

共计 13413 个字符,预计需要花费 34 分钟才能阅读完成。

欢送来我的博客浏览:「微信小程序登录的前端设计与实现」」

一. 前言

对于登录 / 注册的设计如此精雕细琢的目标,当然是想让这个作为利用的根底能力,有足够的健壮性,避免出现全站性的阻塞。

同时要充分考虑如何解耦和封装,在发展新的小程序的时候,能更快的去复用能力,防止反复采坑。

登录注册这模块,就像个冰山,咱们认为它就是「输出账号密码,就实现登录了」,但理论上面还有各种须要思考的问题。

在此,跟在座的各位分享一下,最近做完一个小程序登录 / 注册模块之后,积淀下来的一些设计教训和想法。

二. 业务场景

在用户浏览小程序的过程中,由业务须要,往往须要获取用户的一些根本信息,常见的有:

  1. 微信昵称
  2. 微信手机号

而不同的产品,对于用户的信息要求不尽相同,也会有不一样的受权流程。

第一种,常见于电商零碎中,用户购买商品的时候,为了辨认用户多平台的账号,往往用手机号去做一个分割,这时候须要用户去受权手机号。

第二种,为了让用户信息失去根本的初始化,往往须要更进一步获取用户信息:如微信昵称,unionId 等,就须要询问用户受权。

第三种,囊括第一种,第二种。

三. 概念

秉着积淀一套通用的小程序登录计划和服务为指标,咱们去剖析一下业务,得出变量。

在做技术设计之前,讲点必要的废话,对一些概念进行根本调频。

2.1 对于「登录」

登录在英文中是「login」,对应的还有「logout」。而登录之前,你须要领有一个账号,就要「register」(or sign up)。

话说一开始的产品是没有登录 / 注册性能的,用的人多了就缓缓有了。出于产品自身的需要,须要对「用户」进行身份辨认。

在事实社会中,咱们每个人都有一个身份 ID:身份证。当我到了 16 岁的时候,第一次去公安局领身份证的时候,就实现了一次「注册」行为。而后我去网吧上网,身份证刷一下,实现了一次「登录」行为。

那么对于虚拟世界的互联网来说,这个身份证明就是「账号 + 明码」。

常见的登录 / 注册形式有:

  1. 账号密码注册

    在互联网的晚期,集体邮箱和手机覆盖度小。所以,就须要用户本人想一个账号名,咱们注册个 QQ 号,就是这种模式。

  2. 邮箱地址注册

    千禧年之后,PC 互联网时代疾速遍及,咱们都创立了属于本人的集体邮箱。加上 QQ 也自带邮箱账号。因为邮箱具备集体私密性,且可能进行信息的沟通,因而,大部分网站开始采纳邮箱账号作为用户名来进行注册,并且会在注册的过程中要求登录到相应邮箱内查收激活邮件,验证咱们对该注册邮箱的所有权。

  3. 手机号码注册

    在互联网遍及之后,智能手机与挪动互联网倒退迅猛。手机也成为每个人必不可少的挪动设施,同时挪动互联网也曾经深深融入每个人的古代生存当中。所以,相较于邮箱,目前手机号码与集体的分割更加严密,而且越来越多的挪动利用呈现,采纳手机号码作为用户名的注册形式也失去了宽泛的应用。

到了 2020 年,微信用户规模达 12 亿。那么,微信账号,起码在中国,已成为新一代互联网世界的「身份标识」。

而对微信小程序而言,人造就能晓得以后用户的微信账号 ID。微信容许小程序利用,能在用户无感知的状况下,悄无声息的「登录」到咱们的小程序利用中去,这个就是咱们常常称之为的「静默登录」。

其实微信小程序的登录,跟传统 Web 利用的「单点登录」实质是一样的概念。

  1. 单点登录:在 A 站登录了,C 站和 B 站能实现疾速的「静默登录」。
  2. 微信小程序登录:在微信中,登录了微信账号,那么在整个小程序生态中,都能够实现「静默登录」。

因为 Http 原本是无状态的,业界根本对于登录态的个别做法:

  1. cookie-session:罕用于浏览器利用中
  2. access token:罕用于挪动端等非浏览器利用

在微信小程序来说,对于「JS 逻辑层」并不是一个浏览器环境,天然没有 Cookie,那么通常会应用 access token 的形式。

2.2 对于「受权」

对于须要更进一步获取用的用户昵称、用户手机号等信息的产品来说。微信出于用户隐衷的思考,须要用户被动批准受权。小程序利用能力获取到这部分信息,这就有了目前风行的小程序「受权用户信息」、「受权手机号」的交互了。

出于不同的用户信息敏感度不同的思考,微信小程序对于不同的用户信息提供「受权」的形式不尽相同:

  1. 调用具体 API 形式,弹窗受权。

    1. 例如调用 wx.getLocation() 的时候,如果用户未受权,则会弹出地址受权界面。
    2. 如果回绝了,就不会再次弹窗,wx.getLocation()间接返回失败。
  2. <button open-type="xxx" /> 形式。

    1. 仅反对:用户敏感信息,用户手机号,须要配合后端进行对称加解密,方能拿到数据。
    2. 用户已回绝,再次点击按钮,依然会弹窗。
  3. 通过 wx.authorize(),提前询问受权,之后须要获取相干信息的时候不必再次弹出受权。

四. 具体设计

梳理分明了概念之后,咱们模块的划分上,能够拆分为两大块:

  1. 登录:负责与服务端创立起一个会话,这个会话实现静默登录以及相干的容错解决等,模块命名为:Session
  2. 受权:负责与用户交互,获取与更新信息,以及权限的管制解决等,模块命名为:Auth

3.1 登录的实现

3.1.1 静默登录

微信官网提供的登录计划,总结为三步:

  1. 前端通过 wx.login() 获取一次性加密凭证 code,交给后端。
  2. 后端把这个 code 传输给微信服务器端,换取用户惟一标识 openId 和受权凭证 session_key。(用于后续服务器端和微信服务器的非凡 API 调用,具体看:微信官网文档 - 服务端获取凋谢数据)。
  3. 后端把从微信服务器获取到的用户凭证与自行生成的登录态凭证(token),传输给前端。前端保存起来,下次申请的时候带给后端,就能辨认哪个用户。

如果只是实现这个流程的话,挺简略的。

但要实现一个强壮的登录过程,还须要留神更多的边界状况:

  1. 收拢 wx.login() 的调用

    因为 wx.login() 会产生不可预测的副作用,例如会可能导致 session_key 生效,从而导致后续的受权解密场景中的失败。咱们这里能够提供一个像 session.login() 的办法,把握 wx.login() 控制权,对其做一系列的封装和容错解决。

  2. 调用的机会

    通常咱们会在利用启动的时候(app.onLaunch()),去发动静默登录。但这里会由小程序生命周期设计问题而导致的一个异步问题:加载页面的时候,去调用一个须要登录态的后端 API 的时候,后面异步的动态登录过程有可能还没有实现,从而导致申请失败。

    当然也能够在第一个须要登录态的接口调用的时候以异步阻塞的形式发动登录调用,这个须要联合良好设计的接口层。

    以上讲到的两种场景的具体设计思路下文会讲到。

  3. 并发调用的问题

    在业务场景中,难免会呈现多处代码须要触发登录,如果遇到极其状况,这多处代码同工夫发动调用。那就会造成短时间屡次发动登录过程,只管之前的申请还没有实现。针对这种状况,咱们能够以第一个调用为阻塞,后续调用期待后果,就像精子和卵子联合的过程。

  4. 未过期调用的问题

    如果咱们的登录态未过期,齐全能够失常应用的,默认状况就不需再去发动登录过程了。这时候咱们能够默认状况下先去查看登录态是否可用,不能用,咱们再发动申请。而后还能够提供一个相似 session.login({force: true})的参数去强行发动登录。

3.1.2 静默登录异步状态的解决

1. 利用启动的时候调用

因为大部分状况都须要依赖登录态,咱们会很自然而然的想到把这个调用的机会放到利用启动的时候(app.onLaunch())来调用。

然而因为原生的小程序启动流程中,AppPageComponent 的生命周期钩子函数,都不反对异步阻塞。

那么咱们很容易会遇到 app.onLaunch 发动的「登录过程」在 page.onLoad 的时候还没有实现,咱们就无奈正确去做一些依赖登录态的操作。

针对这种状况,咱们设计了一个状态机的工具:status

基于状态机,咱们就能够编写这样的代码:

import {Status} from '@beautywe/plugin-status';

// on app.js
App({
    status: {login: new Status('login');
    },

    onLaunch() {
        session
            // 发动静默登录调用
            .login()

            // 把状态机设置为 success
            .then(() => this.status.login.success())
      
            // 把状态机设置为 fail
            .catch(() => this.status.login.fail());
    },
});


// on page.js
Page({onLoad() {const loginStatus = getApp().status.login;
      
      // must 外面会进行状态的判断,例如登录中就期待,登录胜利就间接返回,登录失败抛出等。loginStatus().status.login.must(() => {// 进行一些须要登录态的操作...});
    },
});

2. 在「第一个须要登录态接口」被调用的时候去发动登录

更进一步,咱们会发现,须要登录态的更深层次的节点是在发动的「须要登录态的后端 API」的时候。

那么咱们能够在调用「须要登录态的后端 API」的时候再去发动「静默登录」,对于并发的场景,让其余申请期待一下就好了。

以 fly.js 作为 wx.request() 封装的「网络申请层」,做一个简略的例子:

// 发动申请,并表明该申请是须要登录态的
fly.post('https://...', params, { needLogin: true});

// 在 fly 拦截器中解决逻辑
fly.interceptors.request.use(async (req)=>{

  // 在申请须要登录态的时候
  if (req.needLogin !== false) {

    // ensureLogin 外围逻辑是:判断是否已登录,如否发动登录调用,如果正在登录,则进入队列期待回调。await session.ensureLogin();
    
    // 登录胜利后,获取 token,通过 headers 传递给后端。const token = await session.getToken();
    Object.assign(req.headers, { [AUTH_KEY_NAME]: token });
  }
  
  return req;
});

3.1.3 自定义登录态过期的容错解决

当自定义登录态过期的时候,后端须要返回特定的状态码,例如:AUTH_EXPIREDAUTH_INVALID 等。

前端能够在「网络申请层」去监听所有申请的这个状态码,而后发动刷新登录态,再去重放失败的申请:

// 增加响应拦截器
fly.interceptors.response.use((response) => {
      const code = res.data;
        
      // 登录态过期或生效
      if (['AUTH_EXPIRED', 'AUTH_INVALID'].includes(code) ) {
      
        // 刷新登录态
        await session.refreshLogin();
        
        // 而后从新发动申请
        return fly.request(request);
      }
    }
)

那么如果并发的发动多个申请,都返回了登录态生效的状态码,上述代码就会被执行屡次。

咱们须要对 session.refreshLogin() 做一些非凡的容错解决:

  1. 申请锁:同一时间,只容许一个正在过程中的网络申请。
  2. 期待队列:申请被锁定之后,调用该办法的所有调用,都推入一个队列中,期待网络申请实现之后共用返回后果。
  3. 熔断机制:如果短时间内屡次调用,则进行响应一段时间,相似于 TCP 慢启动。

示例代码:

class Session {
  // ....
  
  // 刷新登录保险丝,最多反复 3 次,而后熔断,5s 后复原
  refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT;
  refreshLoginFuseLocked = false;
  refreshLoginFuseRestoreTime = 5000;

  // 熔断管制
  refreshLoginFuse(): Promise<void> {if (this.refreshLoginFuseLocked) {return Promise.reject('刷新登录 - 保险丝已熔断,请稍后');
    }
    if (this.refreshLoginFuseLine > 0) {
      this.refreshLoginFuseLine = this.refreshLoginFuseLine - 1;
      return Promise.resolve();} else {
      this.refreshLoginFuseLocked = true;
      setTimeout(() => {
        this.refreshLoginFuseLocked = false;
        this.refreshLoginFuseLine = REFRESH_LOGIN_FUSELINE_DEFAULT;
        logger.info('刷新登录 - 保险丝熔断解除');
      }, this.refreshLoginFuseRestoreTime);
      return Promise.reject('刷新登录 - 保险丝熔断!!');
    }
  }

  // 并发回调队列
  refreshLoginQueueMaxLength = 100;
  refreshLoginQueue: any[] = [];
  refreshLoginLocked = false;

  // 刷新登录态
  refreshLogin(): Promise<void> {return Promise.resolve()
    
      // 回调队列 + 熔断 管制
      .then(() => this.refreshLoginFuse())
      .then(() => {if (this.refreshLoginLocked) {
          const maxLength = this.refreshLoginQueueMaxLength;
          if (this.refreshLoginQueue.length >= maxLength) {return Promise.reject(`refreshLoginQueue 超出容量:${maxLength}`);
          }
          return new Promise((resolve, reject) => {this.refreshLoginQueue.push([resolve, reject]);
          });
        }
        this.refreshLoginLocked = true;
      })

      // 通过前置管制之后,发动登录过程
      .then(() => {this.clearSession();
        wx.showLoading({title: '刷新登录态中', mask: true});
        return this.login()
          .then(() => {wx.hideLoading();
            wx.showToast({icon: 'none', title: '登录胜利'});
            this.refreshLoginQueue.forEach(([resolve]) => resolve());
            this.refreshLoginLocked = false;
          })
          .catch(err => {wx.hideLoading();
            wx.showToast({icon: 'none', title: '登录失败'});
            this.refreshLoginQueue.forEach(([, reject]) => reject());
            this.refreshLoginLocked = false;
            throw err;
          });
      });

  // ...
}

3.1.4 微信 session_key 过期的容错解决

咱们从下面的「静默登录」之后,微信服务器端会下发一个 session_key 给后端,而这个会在须要获取微信凋谢数据的时候会用到。

session_key 是有时效性的,以下摘自微信官网形容:

会话密钥 session_key 有效性

开发者如果遇到因为 session_key 不正确而校验签名失败或解密失败,请关注上面几个与 session_key 无关的注意事项。

  1. wx.login 调用时,用户的 session_key 可能 会被更新而以致旧 session_key 生效(刷新机制存在最短周期,如果同一个用户短时间内屡次调用 wx.login,并非每次调用都导致 session_key 刷新)。开发者应该在明确须要从新登录时才调用 wx.login,及时通过 auth.code2Session 接口更新服务器存储的 session_key。
  2. 微信不会把 session_key 的有效期告知开发者。咱们会依据用户应用小程序的行为对 session_key 进行续期。用户越频繁应用小程序,session_key 有效期越长。
  3. 开发者在 session_key 生效时,能够通过从新执行登录流程获取无效的 session_key。应用接口 wx.checkSession 能够校验 session_key 是否无效,从而防止小程序重复执行登录流程。
  4. 当开发者在实现自定义登录态时,能够思考以 session_key 有效期作为本身登录态有效期,也能够实现自定义的时效性策略。

翻译成简略的两句话:

  1. session_key 时效性由微信管制,开发者不可预测。
  2. wx.login 可能会导致 session_key 过期,能够在应用接口之前用 wx.checkSession 检查一下。

而对于第二点,咱们通过试验发现,偶发性的在 session_key 已过期的状况下,wx.checkSession 会概率性返回 true

社区也有相干的反馈未失去解决:

  • 小程序解密手机号, 隔一小段时间后,checksession:ok, 然而解密失败
  • wx.checkSession 无效,然而解密数据失败
  • checkSession 判断 session_key 未生效,然而解密手机号失败

所以论断是:wx.checkSession可靠性是不达 100% 的。

基于以上,咱们须要对 session_key 的过期做一些容错解决:

  1. 发动须要应用 session_key 的申请前,做一次 wx.checkSession 操作,如果失败了刷新登录态。
  2. 后端应用 session_key 解密凋谢数据失败之后,返回特定错误码(如:DECRYPT_WX_OPEN_DATA_FAIL),前端刷新登录态。

示例代码:

// 定义查看 session_key 有效性的操作
const ensureSessionKey = async () => {
  const hasSession = await new Promise(resolve => {
    wx.checkSession({success: () => resolve(true),
      fail: () => resolve(false),
    });
  });
  
  if (!hasSession) {logger.info('sessionKey 已过期,刷新登录态');

    // 接下面提到的刷新登录逻辑
    return session.refreshLogin();}

  return Promise.resolve();}

// 在发动申请的时候,先做一次确保 session_key 最新的操作(以 fly.js 作为网络申请层为例)const updatePhone = async (params) => {await ensureSessionKey();
  const res = await fly.post('https://xxx', params);
}

// 增加响应拦截器, 监听网络申请返回
fly.interceptors.response.use((response) => {
      const code = res.data;
        
      // 登录态过期或生效
      if (['DECRYPT_WX_OPEN_DATA_FAIL'].includes(code)) {

        // 刷新登录态
        await session.refreshLogin();
        
        // 因为加密场景的加密数据由用户点击产生,session_key 可能曾经更改,须要用户从新点击一遍。wx.showToast({title: '网络出小差了,请稍后重试', icon: 'none'});
      }
    }
)

3.2 受权的实现

3.2.1 组件拆分与设计

在用户信息和手机号获取的形式上,微信是以 <button open-type='xxx' /> 的形式,让用户被动点击受权的。

那么为了让代码更解耦,咱们设计这样三个组件:

  1. <user-contaienr getUserInfo="onUserInfoAuth">: 包装点击交互,通过 <slot> 反对点击区域的自定义 UI。
  2. <phone-container getPhonenNmber="onPhoneAuth"> : 与 <user-container> 同理。
  3. <auth-flow>: 依据业务须要,组合 <user-container><phone-container> 组合来定义不同的受权流程。

以结尾的业务场景的流程为例,它有这样的要求:

  1. 有多个步骤。
  2. 如果中途断掉了,能够从中间接上。
  3. 有些场景中,只要求达到「用户信息受权」,而不须要实现「用户手机号」。

那么受权的阶段能够分三层:

// 用户登录的阶段
export enum AuthStep {
  // 阶段一:只有登录态,没有用户信息,没有手机号
  ONE = 1,

  // 阶段二:有用户信息,没有手机号
  TWO = 2,

  // 阶段三:有用户信息,有手机号
  THREE = 3,
}

AuthStep 的推动过程是不可逆的,咱们能够定义一个 nextStep 函数来封装 AuthStep 更新的逻辑。内部应用的话,只有无脑调用 nextStep 办法,期待回调后果就行。

示例伪代码:

// auth-flow component

Component({
  // ...
  
  data: {
    // 默认状况下,只须要达到阶段二。mustAuthStep: AuthStep.TWO
  },
  
  // 容许长期更改组件的须要达到的阶段。setMustAuthStep(mustAuthStep: AuthStep) {this.setData({ mustAuthStep});
  },
  
  // 依据用户以后的信息,计算用户处在受权的阶段
  getAuthStep() {
    let currAuthStep;
    
    // 没有用户信息,尚在第一步
    if (!session.hasUser() || !session.hasUnionId()) {currAuthStep = AuthStepType.ONE;}

    // 没有手机号,尚在第二步
    if (!session.hasPhone()) {currAuthStep = AuthStepType.TWO;}

    // 都有,尚在第三步
    currAuthStep = AuthStepType.THREE;
    return currAuthStep;
  }
  
  // 发动下一步受权,如果都曾经实现,就间接返回胜利。nextStep(e) {const { mustAuthStep} = this.data;
    const currAuthStep = this.updateAuthStep();
  
    // 已实现受权
    if (currAuthStep >= mustAuthStep || currAuthStep === AuthStepType.THREE) {
      // 更新全局的受权状态机,播送音讯给订阅者。return getApp().status.auth.success();
    }

    // 第一步:更新用户信息
    if (currAuthStep === AuthStepType.ONE) {
      // 已有密文信息,更新用户信息
      if (e) session.updateUser(e);

      // 更新到视图层,展现对应 UI,期待获取用户信息
      else this.setData({currAuthStep});
      return;
    }

    // 第二步:更新手机信息
    if (currAuthStep === AuthStepType.TWO) {
      // 已有密文信息,更新手机号
      if (e) this.bindPhone(e);

      // 未有密文信息,弹出获取窗口
      else this.setData({currAuthStep});
      return;
    }

    console.warn('auth.nextStep 谬误', { currAuthStep, mustAuthStep});
  },
  
  // ...
});

那么咱们的 <auth-flow> 中就能够依据 currAuthStepmustAuthStep 来去做不同的 UI 展现。须要留神的是应用 <user-container><phone-container> 的时候连贯上 nextStep(e) 函数。

示例伪代码:

<view class="auth-flow">

  <!-- 已实现受权 -->
  <block wx:if="{{currAuthStep === mustAuthStep || currAuthStep === AuthStep.THREE}}">
    <view> 已实现受权 </view>
  </block>

  <!-- 未实现受权,第一步:受权用户信息 -->
  <block wx:elif="{{currAuthStep === AuthStep.ONE}}">
    <user-container bind:getuserinfo="nextStep">
      <view> 受权用户信息 </view>
    </user-container>
  </block>

  <!-- 未实现受权,第二步:受权手机号 -->
  <block wx:elif="{{currAuthStep === AuthStep.TWO}}">
    <phone-container bind:getphonenumber="nextStep">
      <view> 受权手机号 </view>
    </phone-container>
  </block>
  
</view>

3.2.2 权限拦挡的解决

到这里,咱们制作好了用来承载受权流程的组件 <auth-flow>,那么接下来就是决定要应用它的机会了。

咱们梳理须要受权的场景:

  1. 点击某个按钮,例如:购买某个商品。

    对于这种场景,常见的是通过弹窗实现受权,用户能够抉择敞开。

  2. 浏览某个页面,例如:拜访集体核心。

    对于这种场景,咱们能够在点击跳转某个页面的时候,进行拦挡,弹窗解决。但这样的毛病是,跳转到指标页面的中央可能会很多,每个都拦挡,难免会错漏。而且当指标页面作为「小程序落地页面」的时候,就防止不了。

    这时候,咱们能够通过重定向到受权页面来实现受权流程,实现之后,再回来。

那么咱们定义一个枚举变量:

// 受权的展现模式
export enum AuthDisplayMode {
  // 以弹窗模式
  POPUP = 'button',

  // 以页面模式
  PAGE = 'page',
}

咱们能够设计一个 mustAuth 办法,在点击某个按钮,或者页面加载的时候,进行受权管制。

伪代码示例:

class Session {
  // ...
  
  mustAuth({
    mustAuthStep = AuthStepType.TWO, // 须要受权的 LEVEL,默认须要获取用户材料
    popupCompName = 'auth-popup',    // 受权弹窗组件的 id
    mode = AuthDisplayMode.POPUP, // 默认以弹窗模式
  } = {}): Promise<void> {
    
    // 如果以后的受权步骤曾经达标,则返回胜利
    if (this.currentAuthStep() >= mustAuthStep) return Promise.resolve();

    // 尝试获取以后页面的 <auth-popup id="auth-popup" /> 组件实例
    const pages = getCurrentPages();
    const curPage = pages[pages.length - 1];
    const popupComp = curPage.selectComponent(`#${popupCompName}`);

    // 组件不存在或者显示指定页面,跳转到受权页面
    if (!popupComp || mode === AuthDisplayMode.PAGE) {
      const curRoute = curPage.route;

      // 跳转到受权页面,带上以后页面路由,受权实现之后,回到以后页面。wx.redirectTo({url: `authPage?backTo=${encodeURIComponent(curRoute)}` });
      return Promise.resolve();}
    
    // 设置受权 LEVEL,而后调用 <auth-popup> 的 nextStep 办法,进行进一步的受权。popupComp.setMustAuthStep(mustAuthStep);
    popupComp.nextStep();

    // 期待胜利回调或者失败回调
    return new Promise((resolve, reject) => {const authStatus = getApp().status.auth;
      authStatus.onceSuccess(resolve);
      authStatus.onceFail(reject);
    });
  }
  
  // ...
}

那么咱们就能在按钮点击,或者页面加载的时候进行受权拦挡:

Page({onLoad() {session.mustAuth().then(() => {// 开始初始化页面...});}
  
  onClick(e) {session.mustAuth().then(() => {// 开始解决回调逻辑...});}
})

当然,如果我的项目应用了 TS 的话,或者反对 ES7 Decorator 个性的话,咱们能够为 mustAuth 提供一个装璜器版本:

export function mustAuth(option = {}) {
  return function(
    _target,
    _propertyName,
    descriptor,
  ) {
    // 劫持指标办法
    const method = descriptor.value;
    
    // 重写指标办法
    descriptor.value = function(...args: any[]) {return session.mustAuth(option).then(() => {
        // 登录实现之后,重放原来办法
        if (method) return method.apply(this, args);
      });
    };
  };
}

那么应用形式就简略一些了:

Page({@mustAuth();
  onLoad() {// 开始初始化页面...}
  
  @mustAuth();
  onClick(e) {// 开始解决回调逻辑...}
});

3.3. 前后端交互协定整顿

作为一套可复用的小程序登录计划,当然须要去定义好前后端的交互协定。

那么整套登录流程下来,须要的接口有这么几个:

  1. 静默登录 silentLogin

    1. 入参:

      1. code: 产自 wx.login()
    2. 出参:

      1. token: 自定义登录态凭证
      2. userInfo: 用户信息
    3. 阐明:

      1. 后端利用 code 跟微信客户端换取用户标识,而后注册并登录用户,返回自定义登录态 token 给前端
      2. token 前端会存起来,每个申请都会带上
      3. userInfo 须要蕴含 nicknamephone字段,前端用于计算以后用户的受权阶段。当然这个状态的记录能够放在后端,然而咱们认为放在前端,会更加灵便。
  2. 更新用户信息 updateUser

    1. 入参:

      1. nickname: 用户昵称
      2. encrypt: 微信凋谢数据相干的 iv, encryptedData
      3. 以及其余如性别地址等非必要字段
    2. 出参:

      1. userInfo:更新后的最新用户信息
    3. 阐明:

      1. 后端解密微信凋谢数据,获取荫蔽数据,如:unionId
      2. 后端反对更新包含 nickname等用户根本信息。
      3. 前端会把 userInfo 信息更新到 session 中,用于计算受权阶段。
  3. 更新用户手机号 updatePhone

    1. 入参:

      1. encrypt:微信凋谢数据相干的 iv, encryptedData
    2. 出参:

      1. userInfo:更新后的最新用户信息
    3. 阐明:

      1. 后端解密开放式局,获取手机号,并更新到用户信息中。
      2. 前端会把 userInfo 信息更新到 session 中,用于计算受权阶段。
  4. 解绑手机号 unbindPhone

    1. 入参:-
    2. 出参:-
    3. 阐明:后端解绑用户手机号,胜利与否,走业务定义的前后端协定。
  5. 登录 logout

    1. 入参:-
    2. 出参:-
    3. 阐明:后端被动过期登录态,胜利与否,走业务定义的前后端协定。

五. 架构图

最初咱们来梳理一下整体的「登录服务」的架构图:

由「登录服务」和「底层建设」组合提供的通用服务,业务层只须要去依据产品需要,定制受权的流程 <auth-flow>,就能满足大部分场景了。

六. 总结

本篇文章通过一些常见的登录受权场景来开展来形容细节点。

整顿了「登录」、「受权」的概念。

而后别离针对「登录」介绍了一些要害的技术实现:

  1. 静默登录
  2. 静默登录异步状态的解决
  3. 自定义登录态过期的容错解决
  4. 微信 session_key 过期的容错解决

而对于「受权」,会有设计 UI 局部的逻辑,还须要波及到组件的拆分:

  1. 组件拆分与设计
  2. 权限拦挡的解决

而后,梳理了这套登录受权计划所依赖的后端接口,和给出最简略的参考协定。

最初,站在「秉着积淀一套通用的小程序登录计划和服务为指标」的角度,梳理了一下架构层面上的分层。

  1. 业务定制层
  2. 登录服务层
  3. 底层建设

七. 参考

  1. fly.js 官网
  2. 微信官网文档 - 受权
  3. 微信官网文档 - 服务端获取凋谢数据
  4. 微信官网社区

    1. 小程序解密手机号, 隔一小段时间后,checksession:ok, 然而解密失败
    2. wx.checkSession 无效,然而解密数据失败
    3. checkSession 判断 session_key 未生效,然而解密手机号失败
正文完
 0