小程序登陆设计-耐心看,讲的很明白 - 新闻资讯 - 云南小程序开发|云南网站建设-昆明葵宇信息科技有限公司

159-8711-8523

云南网建设/小程序开发/软件开发

知识

不管是网站,软件还是小程序,都要直接或间接能为您产生价值,我们在追求其视觉表现的同时,更侧重于功能的便捷,营销的便利,运营的高效,让网站成为营销工具,让软件能切实提升企业内部管理水平和效率。优秀的程序为后期升级提供便捷的支持!

您当前位置>首页 » 新闻资讯 » 小程序相关 >

小程序登陆设计-耐心看,讲的很明白

发表时间:2021-4-13

发布人:葵宇科技

浏览次数:2

 

小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识,快速建立小程序内的用户体系。

即「静默登录」,通过调用 wx.login 获取到 code ,将其发送到开发者后端,开发者后端通过接口去微信后端换取到 openid 和 sessionKey(现在会将 unionid 也一并返回)后,然后把自定义登录态 3rd_session(本业务命名为auth-token) 返回给前端,就已经完成登录行为了。

理论上,开发者后端可以通过 openid识别用户,也能通过unionid关联同主体的多个小程序、公众号、app,实现数据互通,从而为每一个用户创建独一无二的uid(本业务自定义的用户 id),在「微信生态」中建立成熟用户体系。

然而,对于复杂的电商跨端应用,比如pch5小程序,不同渠道注册的uid是不同的,用户登录后难以对各个渠道的交易、促销、收藏等数据进行整合。因此,要实现跨端的用户体系数据互通,就需要提供一个唯一的用户标识——手机号。这便是本文重点讲述的「用户登录」,即「游客态」转变成「会员态」的过程。

2. 「用户登录」流程

当新用户第一次进入小程序时,便会触发「静默登录」,这个过程对用户是无感知的。但此时开发者服务端已经为该用户定义了uid,并下发auth-token给小程序端,对于一些需要鉴权的请求,服务端可以根据请求携带的auth-token精确识别是哪个用户发起的行为。

然而,类似加购下单领券等用户行为,涉及到跨端数据的整合,在执行用户操作之前,会判断用户是否登录,如若用户未登录,则跳转登录页面,整个流程如下所示:

比如在「用户中心」页面点击「我的订单」,由于此时用户未登录,跳转到登录页面,可以选择以下两种登录方式:

  1. 选择 「微信授权登录」,弹出授权手机号信息弹窗,点击「允许」,此时用户登录成功。
  2. 选择 「手机快捷登录」,输入手机号,使用 「验证码」 或者 「密码」 进行登录,登录成功跳转回到「用户中心」页面。

上述步骤已经完成了「用户登录」,用户可以正常的执行加购、领券、下单等操作。 为了提升用户体验,需要对 「会员信息」 进行维护 ,比如昵称、头像、性别、生日等信息,最简单的方法是 获取「微信授权用户信息」。触发时机分为以下两种:

  1. 用户第一次选择 「微信授权登录」 成功后跳转授权用户信息页面,点击 「授权用户信息」,弹出授权用户信息弹窗。点击「允许」,跳转回「用户中心」页面。
  2. 在「用户中心」页面点击头像昵称区域,弹出授权用户信息弹窗,点击「允许」,更新「会员信息」并跳转用户信息编辑页面。

3. 「用户登录」方案设计

3.1 架构

「用户登录」方案架构如上图所示,将所有登录相关功能抽象到 「service 层」(本项目将其命名为session),供 「业务层」 调用。该 「service 层」 主要分为以下两个模块:

3.1.1 libs - 提供登录相关的类方法供「业务层」调用

  1. 封装session类,提供类方法供「业务层」调用。主要有以下几种方法:
方法名功能使用场景
silentLogin发起静默登录-
login登录,silentLogin方法的一层封装用于小程序启动时发起静默登录
refreshLogin刷新登录态,silentLogin方法的一层封装用于登录态过期时发起静默登录
ensureSessionKey验证sessionKey是否过期,过期则刷新登录态绑定微信授权手机号时验证是否过期,过期则得重新弹窗授权
bindPhone绑定微信授权手机号微信授权手机号弹窗点击「允许」触发
updateUser绑定微信授权用户信息微信授权用户信息点击「允许」触发
getCurrentAuthStep获取当前用户登录所属阶段详见下文
mustAuth各种触发场景拦截判断是否需要登录详见下文

当然,session类中还封装了一些方法用于与storage交互,比如获取storage中的auth-token用于各种鉴权请求携带等等。session类也提供的一些拓展方法,比如注销账号、解绑手机号等等用于后续需求迭代。

  1. 装饰器:

    • must-auth: mustAuth类方法的装饰器,便于业务层各种场景触发登录。
    • fuse-line: 熔断机制,如果短时间内多次调用,则停止响应一段时间,类似于 TCP 慢启动。用于解决refreshLoginlogin等方法的并发处理问题。
    • single-queue: 单队列模式,同一时间,只允许一个正在过程中的网络请求。请求被锁定之后,同样的请求都会被推入队列,等待进行中的请求返回后,消费同一个结果。用于解决refreshLoginlogin等方法的并发处理问题。

3.1.2 ui - 提供通用组件供业务层调用

  1. 基础组件: user-containerphone-container分别是获取「微信授权用户信息」和获取「微信授权手机号」的纯 UI 单元组件,给通用组件使用。
  2. behavior 类:拿到授权数据后需要发送给服务端进行存储,也需要执行一些跳转逻辑判断,这些都抽象成行为类封装在auth-flow中,供通用组件使用。
  3. 通用组件: 共用一个行为类,区别在于auth-flow-container用于页面,auth-flow-popup用于弹窗。如下所示,小程序只有微信授权功能,则可以通过弹窗完成授权。如小程序同时提供手机号验证码和密码登录等功能,则需跳转特定登录页面。

3.2 libs

3.2.1 用户身份定义

综上所示,用户登录的阶段可以分为以下三步:

// 用户登录的阶段 export enum AuthStepType {   // 阶段一:游客态:静默登录成功,未绑定手机号,无用户信息   ONE = 1,   // 阶段二:会员态:用户登录成功,已绑定手机号,无用户信息   TWO = 2,   // 阶段三:会员信息态:用户登录成功,已绑定手机号,有用户信息   THREE = 3, } 复制代码

那么如何判断用户此时处于哪个步骤,基于「静默登录」的启发,原本「静默登录」成功开发者后端会将自定义登录态 auth-token返回给前端,此处请求可以携带返回「用户信息」,同auth-token一起命名为session存储在本地storage当「用户登录」或者「更新用户信息」时,会同步更新storagekeysession的数据,从而通过这些用户数据判断当前用户处于哪一个登录阶段

以下表格列出了session存储的部分重要的属性以及在三个阶段属性对应的值。

属性定义游客态会员态会员信息态
authToken自定义登录态'0d5bad172...''0d5bad172...''0d5bad172...'
uid用户 id'001''001''001'
busiIdentity用户身份定义'VISIT''MEMBER''MEMBER'
nickName用户昵称'''u_a1bk45''rileycai'
headUrl头像链接'''''www.xx.com/image/...'
phone手机号码'''17600888888''17600888888'
...其它用户信息.........

注意: 会员态和会员信息态的busiIdentity值均为MEMBER,区分会员态和会员信息态可以通过用户昵称和头像等字段,比如用户登录成功会为用户生成以'u_'开头的默认昵称和默认为空的用户头像链接。

判断用户此时处于哪个步骤的代码如下:

  // 获取当前授权阶段   public getCurrentAuthStep(): AuthStepType {     // 切换账号登录的时候,始终返回AuthStepType.ONE     const loginMode = this.getLoginMode();     if (loginMode === LoginMode.SWITCH_ACCOUNT) return AuthStepType.ONE;      // 用户身份定义非会员返回AuthStepType.ONE     const userInfo = this.getUser();     if (userInfo?.busiIdentity !== 'MEMBER') return AuthStepType.ONE;      // 初次登录,未授权用户信息,返回AuthStepType.TWO     if (userInfo.nickName.substring(0, 2) === 'u_' && !userInfo.headUrl)       return AuthStepType.TWO;      // 都有,返回AuthStepType.THREE     return AuthStepType.THREE;   } 复制代码

3.2.2 用户登录触发场景

前面提到过,「用户登录」的 目的是为了整合各个渠道的交易、促销、收藏等数据,针对电商小程序,目前总结的需要用户登录的场景如下所示:

即当用户登录小程序时,可以正常浏览浏览商品,只有触发某些特定行为,比如领券、加购、收藏、下单等,才会判断用户是否处于登录状态,如未登录,跳转登录页面

如下所示,封装mustAuth方法进行拦截,未登录则跳转登录页面:

export default class Session {   ...   public mustAuth({     mustAuthStep = AuthStepType.TWO, // 传人参数,需要授权的LEVEL   } = {}): Promise<void> {     // 当前阶段处于会员态(2)或者会员信息态(3),执行resolve操作     if (this.getCurrentAuthStep() >= mustAuthStep) return Promise.resolve();     // 当前阶段处于游客态(1),跳转登录页     Navigator.gotoPage('/login/home');     // 执行reject操作     return Promise.reject();   } } 复制代码

上述代码是跳转页面拦截,对于弹窗而言,需要把弹窗注入base-page(每个页面都需要引入的通用组件,封装每个页面都需要使用的通用方法,比如错误处理等)中,通过 id 查找到弹窗组件,并进行调用。

export default class Session {   ...    public mustAuth({     mustAuthStep = AuthStepType.TWO, // 需要授权的LEVEL     popupCompName = 'auth-flow-popup',   } = {}): Promise<void> {     // 当前阶段处于会员态(2)或者会员信息态(3),执行resolve操作     if (this.getCurrentAuthStep() >= mustAuthStep) return Promise.resolve();     // 获取弹窗组件     const pages = getCurrentPages();     const curPage = pages[pages.length - 1];     const context = curPage.$$basePage || curPage;     const popupComp = context.selectComponent(`#${popupCompName}`);     // 容错处理     if (!popupComp) {       return Promise.reject(         new Error(           "当前页面未找到 #auth-popup 组件,请参考 'doc/登录组件的使用方式.md'",         ),       );     }     // 调用弹窗组件方法     popupComp.setMustAuthStep(mustAuthStep);     popupComp.nextStep();     // 等待授权成功回调     return this.waitAuth();   } }  复制代码

各个业务使用时可以通过session.mustAuth().then(() => {...});进行调用,为了提高使用体验,也可以使用装饰器@mustAuth()来修饰各个业务需求 类的方法,装饰器源码如下:

/**  * 登录检查装饰器,使用该装饰器的方法,会先执行授权检查,如果未授权,将跳转登录页面  */ export default function mustAuth(option = {}) {   return function(     _target: Record) {     const method = descriptor.value;     descriptor.value = function(...args: any[]) {       if (!session) return;       // 登录拦截       return session.mustAuth(option).then(() => {         if (method) return method.apply(this, args);       });     };   }; } 复制代码

3.3 UI

3.3.1 基础组件

1. phone-container 组件

因为需要用户主动触发才能发起获取微信授权手机号接口,需用 button 组件的点击来触发。组件代码如下所示:

// index.wxml    // index.ts export default class PhoneContainer extends BaseComponent {   getPhoneNumber(     e: WechatMiniprogram.Event,   ) {     this.triggerEvent('getphonenumber', { ...e.detail,  authType: AuthType.PHONE,});   } } 复制代码

phone-container是一个纯 UI 组件,通过triggerEvent事件将获取手机号数据传递给父组件,

2. user-container 组件

user-container组件是获取微信授权用户信息的纯 UI 组件,之前通过

 

2012 年 4 月 13 日之前,使用wx.getUserInfo弹出授权弹窗时,如果用户点击允许授权,那么会记录用户的行为,下次再点击时,不会弹窗而是直接将授权结果返回。4 月 13 日之后后,使用wx.getUserProfile开发者每次通过该接口获取用户个人信息均需用户确认,因此需要妥善保管用户授权的头像昵称,避免重复弹窗。

3.3.2 行为类

如下图所示,auth-flow行为类主要封装用户、小程序、服务端三者之间的交互逻辑。

在「微信授权登录」过程中,小程序拿到加密的encryptedDataiv数据,将其和携带的auth-token一起发送给开发者服务器,服务端通过auth-token鉴权识别这个用户,并使用静默登录成功获取的session_key(对称解密密钥)对encryptedDataiv数据进行对称解密,获取该用户的手机号,将手机号与uid绑定,此时该用户成功注册会员,并将会员信息返回给小程序端。

小程序端更新本地storage存储的session数据,此时busiIdentity的值已经从VISIT更新为MEMBER,用户身份转变为会员态,登录成功。

在「授权用户信息」的过程中,小程序调用wx.getUserProfile方法拿到用户数据,并将这些数据与携带的auth-token一起发送给开发者服务器,服务端通过auth-token鉴权识别这个用户,更新该用户的信息并将新的会员数据返回给小程序端。

小程序端更新本地storage存储的session数据,此时用户昵称和头像均已更新,用户身份转变为会员信息态,授权成功。

眼尖的读者一定观察到了,时序图中还对微信头像做了转存。这是因为用户在微信端修改微信头像后,之前「授权用户信息」获取的微信头像链接就会失效,因此开发者应该在自己获取用户信息后,将头像保存下来,避免微信头像 URL 失效后的异常情况。

3.3.3 通用组件

通用组件是对基础组件和行为类的二次封装,主要是为业务层提供弹窗登录和页面登录两种能力。

4. 总结

我们将用户登录能力从业务层中抽象出来,统一封装在service层,便于复用。本文主要讲述的是service层的架构,对于业务层的逻辑实现并没有多加累赘。下列表格以小程序端为例,简述了「静默登录」和「用户登录」整套方案的前后端逻辑实现。

业务场景用户感知前端处理逻辑后端处理逻辑补充说明
扫码搜索等各种方式进入小程序1、判断:当前小程序是否缓存了登录态auth-token 且使用wx.checkSeesion检查当前用户在小程序中登录态是否过期,过期执行步骤 2;
2、使用wx.login获取认证信息,请求后端wxLogin接口获取微信小程序认证默认绑定的用户身份以及登录态auth-token
1、解析微信加密信息获取认证身份openidunionId
2、查找openid是否已经绑定了对应的用户,若绑定直接返回并为其生成对应的登录态auth-token
3、新用户会根据openid为其自动生成一个用户身份uid(见右补充说明)。
a、存在聚合根标识unionId && 有用户信息:将已有聚合根用户对应的exUid直接映射到当前uid下;
b、存在聚合根标识unionId && 无用户信息:根据unionId生成对应的账号,但和opneid对应的uid一致;
c、不存在聚合根标识:直接为对应openid初始化一个uid
收藏加购下单领券等操作拦截跳转1、判断: 当前用户身份处于游客态,跳转登录页面。对应域服务后端接口可以根据请求携带的auth-token进行鉴权,判断用户是否有操作权限-
用户登录 或者 切换账号选择: