神一样的少年,冲冲冲!

小程序简单开发(一)-关于登录的一些事

梳理登录流程

这里有几个点是要注意的:

  1. 要注意 es6语法使用,es6的语法会在小程序里面更加的有用,其中最关键的地方就是小程序的 api 大部分都是异步的,旧有的方式要异步就必须要要回调,而回调的就会导致代码逻辑容易发生混乱,所以需要使用promise并且进行接口的封装。
  2. 要注意效率,例如对于 code 的获取,首次和非首次要注意首次获取后保存下来,然后非首次获取就可以从缓存里面直接获取,类似这种有好几个地方,所以需要注意好。
  3. 要注意先理清楚业务逻辑和登录逻辑和产品体验逻辑,一定要先画好流程图,因为小程序的逻辑相较一般的逻辑要稍微“绕”,对,是很绕的绕,所以需要注意理顺流畅。
  4. 要注意接口的调用先后顺序,跟流程图的关系很大,对比官方文档,一步步处理,又因为使用异步,所以需要弄清楚 promise 异步的 resolve 和 reject,在多重promise 里面会比较麻烦,不过也是有技巧可以回避的,详情下面会说。
  5. 需要注意签名解密处理,虽然这是服务器端做的,但是也需要了解好是怎么一个操作,因为也需要解析给服务器端的同事,如何配合小程序来做处理。
  6. 需要理解各个关键的变量元素的意思,code,session_key,3rd_session,openid,unioinid的意义,这样才能协助理解文档的整体思路。
  7. 需要注意 openId 和 unioinId的使用

微信小程序登录流程图

引用Yinjie 的图,因为这个图比官方的要看得明白一点。

图一

代码逻辑流程图

引用Yinjie 的图,因为这个图比官方的要看得明白一点。

图二

腾讯 weapp-session的代码流程图

引用腾讯 weapp-session 的图
图三

他们还出了一个比较详细的,一步步的代码处理流程,可以对比自己的程序进行检查。

  1. 客户端(微信小程序)发起请求 request
  2. weapp-session-client 包装 request
    • 首次请求
      • 调用 wx.login()wx.getUserInfo() 接口获得 coderawDatasignature
      • requset 的头部带上 coderawDatasignature
      • 保存 code 供下次调用
    • 非首次请求
      • request 的头部带上保存的 code
  3. 服务器收到请求 request,中间件从头部提取 coderawDatasignature 字段
    • 如果 code 为空,跳到第 4
    • 如果 code 不为空,且 rawData 不为空,需要进行签名校验
      • 使用 codeappidapp_secret 请求微信接口获得 session_keyopenid
        • 如果接口失败,响应 ERR_SESSION_KEY_EXCHANGE_FAILED
      • 使用签名算法通过 rawDatasession_key 计算签名 signature2
      • 对比 signaturesignature2
        • 签名一致,解析 rawDatawxUserInfo
          • openid 写入到 wxUserInfo
          • (code, wxUserInfo) 缓存到 Redis
          • wxUserInfo 存放在 request.$wxUserInfo
          • 跳到第 4
        • 签名不一致,响应 ERR_UNTRUSTED_RAW_DATA
    • 如果 code 不为空,但 rawData 为空,从 Redis 根据 code 查询缓存的用户信息
      • 找到用户信息,存放在 request.$wxUserInfo 字段里,跳到第 4
      • 没找到用户信息(可能是过期),响应 ERR_SESSION_EXPIRED
  4. request 被业务处理,可以使用 request.$wxUserInfo 来获取用户信息(request.$wxUserInfo 可能为空,业务需要自行处理)

流程图总结

  1. code 是微信用户的登录凭证,打开小程序登录的时候获取的只属于微信这个用户的登录凭证,需要注意的是,这个登录凭证只供微信小程序使用的。
  2. session_key 是微信用户在小程序里面的登录态信息,相当于是微信给这个用户颁发的一个登录 session,官网地址
    • 他有一个过期时间{"session_key":"...","expires_in":7200,"openid":"..."},需要定期使用wx.checkSession检测。
  3. openId ,用户的唯一标识
  4. unioinId,如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。
  5. 一般来说,openId 就是微信用户的唯一标识,但是因为微信产品很多,所以会出现多个微信产品使用不同的 openId 来标识用户,但是对于我们做业务接入的话,就买办法使用了,所以建议是统一使用 unioinid,因为一般来说,一般的业务都会有公众号,小程序联合使用的。
  6. 3rd_session 是一般是指我们自己公司的服务器的 session,一般来说,可以跟原来的业务的 session 一起使用,不过这个 session 的过期时间一定要比小程序的session_key 的过期时间要长,这样可以减少 session 的多次重复创建,另外一般我们自己公司的服务器的 session 管理都会使用类似 redis 之类的数据库进行管理的,这个大致了解一下,因为其他文章会提到。
  7. 为什么要用2个 sessionsession_key3rd_session),那是因为session_key是微信的登录态,3rd_session是我们业务系统的登录态,两边各有一个登录态,所以需要将2个登录态合并为一个 session,在这里面是合并为3rd_session,并保存到我们业务系统上,然后每次需要使用的时候,小程序带上这个3rd_session访问我们的业务系统,通过处理,可以返回我们需要 unioinid 和其他 session 信息而不用每次都去获取一个新的session_key(因为微信有限制code 的使用,要用 code 换 session_key),总的来说,就是使用3rd_session来管理小程序的登录态

关于解密

根据官方文档: ,数据校验是为了提高数据的安全性,我们需要获取用户的 unioinid,需要调用wx.getUserInfo接口来获取,但是一般情况下,获取出来的数据是

{
  "nickName": "Band",
  "gender": 1,
  "language": "zh_CN",
  "city": "Guangzhou",
  "province": "Guangdong",
  "country": "CN",
  "avatarUrl": "http://wx.qlogo.cn/mmopen/vi_32/1vZvI39NWFQ9XM4LtQpFrQJ1xlgZxx3w7bQxKARol6503Iuswjjn6nIGBiaycAjAtpujxyzYsrztuuICqIM5ibXQ/0"
}

对于需要业务接入的话,没有 unioinid 是没意义的,所以需要根据官方的方式来进行解密,解密后的数据里面有 unioinid 了

{
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":
    {
        "appid":"APPID",
    "timestamp":TIMESTAMP
    }
}

封装网络接口

因为小程序的所有网络请求都是异步的,那么异步就会出现很多重的回调的问题,所以改成了 promise,promise 的使用要谨慎注意 resolve 和 return的处理。

例如这样:

const httpRequest = (api, data) => {
    let serverHost = env.serverHost;

    return new Promise(function (resolve, reject) {
        //发起网络请求
        wx.request({
            url: serverHost + api,
            data: data,
            header: {
                'content-type': 'application/x-www-form-urlencoded'
            },
            success: function (res) {
                if (res.data.errno === 0) {
                    // 需要下一步处理的就用 resolve 返回
                    resolve(res.data);
                }
                else {
                    console.log("http fail:api:" + api + "res:" + JSON.stringify(res));
                    // 需要跳出循环处理的就用 reject
                    reject(res.data);
                }
            }, fail: function (res) {
                console.log("http fail:api:" + api + "res:" + JSON.stringify(res));
                // 需要跳出循环处理的就用 reject
                reject(res);
            }
        })
    })
};

在多重 promise 的情况下,则需要注意2个地方:

  1. 需要返回一个可使用的 promise 实例
  2. 即使有异步并且出现分支的情况下,尽量集中在一个统一的流里面处理,用统一的 reject 跳出,而不是各个异步单独跳出,这样流程会更加统一和方便管理。
getCode() // 获取 wx code
            .then(code => {
                wxCode = code;
                // 这里getSetting是一个返回的 promise实例,如上面的那个
                return getSetting(); // 获取 setting
            })
            .then(res => {
                if (res.authSetting["scope.userInfo"]) {
                    // 这里getUserInfo是一个返回的 promise实例
                    return getUserInfo(self);
                } else {
                    console.log("first auth none:" + JSON.stringify(res));
                    // 类似
                    return util.showModal("亲,还没有授权!")
                        .then(res => {
                            return getAuth("userInfo");
                        }).then(res => {
                            // 检查授权是否正常
                            return getSetting();
                        })
                        .then(res => {
                            if (res.authSetting["scope.userInfo"]) {
                                return getUserInfo(self);
                            } else {
                                return Promise.reject(res);
                            }
                        });
                }
            })
            .catch(err=>{
                console.log("err"+err);    
            })

注意友好的提示

微信小程序“授权失败”场景需要优雅处理,提升用户体验,参考这里:可以稍微看到是如何生效的:

通过在wx.getSetting里面插入一个判断处理,判断没有权限即弹出 modal 提示框:

wx.getSetting({
    success: function success(res) {
        console.log(res.authSetting);
        var authSetting = res.authSetting;
        if (authSetting['scope.userInfo'] === false) {
                wx.showModal({
                    title: '用户未授权',
                    content: '如需XXXXXXX。',
                    showCancel: false,
                    success: function (res) {
                        if (res.confirm) {
                            console.log('用户点击确定')
                            wx.openSetting({
                                success: function success(res) {
                                }
                            });
                        }
                    }
                })
        }
    }
});

参考引用:

  • 鸣谢里面提到的所有引用来源的作者。
未经允许不得转载:神一样的少年edwin » 小程序简单开发(一)-关于登录的一些事

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址