小程序简单开发:关于登录的一些事
梳理登录流程
这里有几个点是要注意的:
- 要注意 es6语法使用,es6的语法会在小程序里面更加的有用,其中最关键的地方就是小程序的 api 大部分都是异步的,旧有的方式要异步就必须要要回调,而回调的就会导致代码逻辑容易发生混乱,所以需要使用promise并且进行接口的封装。
- 要注意效率,例如对于 code 的获取,首次和非首次要注意首次获取后保存下来,然后非首次获取就可以从缓存里面直接获取,类似这种有好几个地方,所以需要注意好。
- 要注意先理清楚业务逻辑和登录逻辑和产品体验逻辑,一定要先画好流程图,因为小程序的逻辑相较一般的逻辑要稍微“绕”,对,是很绕的绕,所以需要注意理顺流畅。
- 要注意接口的调用先后顺序,跟流程图的关系很大,对比官方文档,一步步处理,又因为使用异步,所以需要弄清楚 promise 异步的 resolve 和 reject,在多重promise 里面会比较麻烦,不过也是有技巧可以回避的,详情下面会说。
- 需要注意签名解密处理,虽然这是服务器端做的,但是也需要了解好是怎么一个操作,因为也需要解析给服务器端的同事,如何配合小程序来做处理。
- 需要理解各个关键的变量元素的意思,code,session_key,3rd_session,openid,unioinid的意义,这样才能协助理解文档的整体思路。
- 需要注意 openId 和 unioinId的使用
微信小程序登录流程图
引用Yinjie 的图,因为这个图比官方的要看得明白一点。
代码逻辑流程图
引用Yinjie 的图,因为这个图比官方的要看得明白一点。
腾讯 weapp-session的代码流程图
引用腾讯 weapp-session 的图
他们还出了一个比较详细的,一步步的代码处理流程,可以对比自己的程序进行检查。
- 客户端(微信小程序)发起请求
request
- weapp-session-client 包装
request
- 首次请求
- 调用
()
和()
接口获得code
、rawData
和signature
requset
的头部带上code
、rawData
和signature
- 保存
code
供下次调用
- 调用
- 非首次请求
- 首次请求
- 服务器收到请求
request
,中间件从头部提取code
、rawData
和signature
字段- 如果
code
为空,跳到第4
步 - 如果
code
不为空,且rawData
不为空,需要进行签名校验- 使用
code
,appid
、app_secret
请求微信接口获得session_key
和openid
- 如果接口失败,响应
ERR_SESSION_KEY_EXCHANGE_FAILED
- 如果接口失败,响应
- 使用签名算法通过
rawData
和session_key
计算签名signature2
- 对比
signature
和signature2
- 签名一致,解析
rawData
为wxUserInfo
- 把
openid
写入到wxUserInfo
- 把
(code, wxUserInfo)
缓存到 Redis - 把
wxUserInfo
存放在request.$wxUserInfo
里 - 跳到第
4
步
- 把
- 签名不一致,响应
ERR_UNTRUSTED_RAW_DATA
- 签名一致,解析
- 使用
- 如果
code
不为空,但rawData
为空,从 Redis 根据code
查询缓存的用户信息- 找到用户信息,存放在
request.$wxUserInfo
字段里,跳到第4
步 - 没找到用户信息(可能是过期),响应
ERR_SESSION_EXPIRED
- 找到用户信息,存放在
- 如果
request
被业务处理,可以使用request.$wxUserInfo
来获取用户信息(request.$wxUserInfo
可能为空,业务需要自行处理)
流程图总结
code
是微信用户的登录凭证,打开小程序登录的时候获取的只属于微信这个用户的登录凭证,需要注意的是,这个登录凭证只供微信小程序使用的。session_key
是微信用户在小程序里面的登录态信息,相当于是微信给这个用户颁发的一个登录 session,官网地址- 他有一个过期时间
{"session_key":"...","expires_in":7200,"openid":"..."}
,需要定期使用检测。
- 他有一个过期时间
openId
,用户的唯一标识unioinId
,如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid
来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的unionid
是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid
是相同的。- 一般来说,
openId
就是微信用户的唯一标识,但是因为微信产品很多,所以会出现多个微信产品使用不同的openId
来标识用户,但是对于我们做业务接入的话,就买办法使用了,所以建议是统一使用unioinid
,因为一般来说,一般的业务都会有公众号,小程序联合使用的。 3rd_session
是一般是指我们自己公司的服务器的session
,一般来说,可以跟原来的业务的session
一起使用,不过这个session
的过期时间一定要比小程序的session_key
的过期时间要长,这样可以减少session
的多次重复创建,另外一般我们自己公司的服务器的session
管理都会使用类似 redis 之类的数据库进行管理的,这个大致了解一下,因为其他文章会提到。- 为什么要用2个
session
(session_key
和3rd_session
),那是因为session_key
是微信的登录态,3rd_session
是我们业务系统的登录态,两边各有一个登录态,所以需要将2个登录态合并为一个session
,在这里面是合并为3rd_session
,并保存到我们业务系统上,然后每次需要使用的时候,小程序带上这个3rd_session
访问我们的业务系统,通过处理,可以返回我们需要 unioinid 和其他session
信息而不用每次都去获取一个新的session_key
(因为微信有限制code 的使用,要用 code 换 session_key),总的来说,就是使用3rd_session
来管理小程序的登录态
关于解密
根据官方文档: ,数据校验是为了提高数据的安全性,我们需要获取用户的 unioinid,需要调用接口来获取,但是一般情况下,获取出来的数据是
{
"nickName": "Band",
"gender": 1,
"language": "zh_CN",
"city": "Guangzhou",
"province": "Guangdong",
"country": "CN",
"avatarUrl": ""
}
对于需要业务接入的话,没有 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) {
//发起网络请求
({
url: serverHost + api,
data: data,
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: function (res) {
if (.errno === 0) {
// 需要下一步处理的就用 resolve 返回
resolve();
}
else {
("http fail:api:" + api + "res:" + (res));
// 需要跳出循环处理的就用 reject
reject();
}
}, fail: function (res) {
("http fail:api:" + api + "res:" + (res));
// 需要跳出循环处理的就用 reject
reject(res);
}
})
})
};
在多重 promise 的情况下,则需要注意2个地方:
- 需要返回一个可使用的 promise 实例
- 即使有异步并且出现分支的情况下,尽量集中在一个统一的流里面处理,用统一的 reject 跳出,而不是各个异步单独跳出,这样流程会更加统一和方便管理。
getCode() // 获取 wx code
.then(code => {
wxCode = code;
// 这里getSetting是一个返回的 promise实例,如上面的那个
return getSetting(); // 获取 setting
})
.then(res => {
if ([""]) {
// 这里getUserInfo是一个返回的 promise实例
return getUserInfo(self);
} else {
("first auth none:" + (res));
// 类似
return util.showModal("亲,还没有授权!")
.then(res => {
return getAuth("userInfo");
}).then(res => {
// 检查授权是否正常
return getSetting();
})
.then(res => {
if ([""]) {
return getUserInfo(self);
} else {
return Promise.reject(res);
}
});
}
})
.catch(err=>{
("err"+err);
})
注意友好的提示
微信小程序“授权失败”场景需要优雅处理,提升用户体验,参考这里:可以稍微看到是如何生效的:
通过在里面插入一个判断处理,判断没有权限即弹出 modal 提示框:
({
success: function success(res) {
();
var authSetting = ;
if (authSetting[''] === false) {
({
title: '用户未授权',
content: '如需XXXXXXX。',
showCancel: false,
success: function (res) {
if () {
('用户点击确定')
({
success: function success(res) {
}
});
}
}
})
}
}
});
THE END