文章目录
  1. 1. 小程序登录
    1. 1.1. 登录时序
    2. 1.2. 相关数据或参数
    3. 1.3. 加锁的登录
  2. 2. 登录态静默续期的实现
    1. 2.1. checkSession
    2. 2.2. 静默续期的接口请求
    3. 2.3. 参考
  3. 3. 结束语

每一个有数据交互的小程序,都会涉及到登录、token 等问题,openid 又是什么呢?怎么使用静默续期,来提升用户体验呢?

小程序登录


登录时序

一切的一切,都要从这么一张小程序登录时序图说起:
image

通常情况下,我们的小程序都会有业务身份,如何将微信帐号和业务身份关联起来呢?这个时候我们需要上图的步骤:

  1. 小程序调用wx.login()获取临时登录凭证code
  2. 小程序将code传到开发者服务器。
  3. 开发者服务器以code换取用户唯一标识openid和会话密钥session_key
  4. 开发者服务器可绑定微信用户身份id和业务用户身份。
  5. 开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

相关数据或参数

上面的登录时序中,我们会涉及到一些数据和参数,先来了解下它们都是用来做啥的。

临时登录凭证 code
在小程序中调用wx.login(),能拿到一个code作为用户登录凭证(有效期五分钟)。在开发者服务器后台,开发者可使用code换取openidsession_key等信息(code只能使用一次)。

code的设计,主要用于防止黑客使用穷举等方式把业务侧个人信息数据全拉走。

AppId 与 AppSecret
为了确保拿code过来换取身份信息的人就是对应的小程序开发者,到微信服务器的请求要同时带上AppIdAppSecret

session_key
会话密钥session_key是对用户数据进行加密签名的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。

设计session_key主要是为了节省流程消耗,如果每次都通过小程序前端wx.login()生成微信登录凭证code去微信服务器请求信息,步骤太多会造成整体耗时比较严重。

使用接口wx.checkSession()可以校验session_key是否有效。用户越频繁使用小程序,session_key有效期越长。session_key失效时,可以通过重新执行登录流程获取有效的session_key

openid
openid是微信用户id,可以用这个id来区分不同的微信用户。
微信针对不同的用户在不同的应用下都有唯一的一个openid, 但是要想确定用户是不是同一个用户,就需要靠unionid来区分。

unionid
如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过unionid来区分用户的唯一性。同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。

加锁的登录

在某些情况下,我们或许多个地方会同时触发登录逻辑(如多个接口同时拉取,发现登录态过期的情况)。一般来说,我们会简单地给请求加个锁来解决:

  1. 使用isLogining来标志是否请求中。
  2. 方法返回 Promise,登录态过期时静默续期后重新发起。
  3. 使用sessionId来记录业务侧的登录态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// session 参数 key(后台吐回)
export const SESSION_KEY = 'sessionId';

let isLogining = false;
export function doLogin() {
return new Promise((resolve, reject) => {
const session = wx.getStorageSync(SESSION_KEY);
if (session) {
// 缓存中有 session
resolve();
} else if (isLogining) {
// 正在登录中,请求轮询稍后,避免重复调用登录接口
setTimeout(() => {
doLogin()
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
}, 500);
} else {
isLogining = true;
wx.login({
success: (res) => {
if (res.code) {
const reqData: ILoginRequest = {
code: res.code
}
wx.request({
url: API.login,
data: reqData,
// method: "POST",
success: (resp) => {
const data = resp.data;
isLogining = false;
// 保存登录态
if (data.return_code === 0) {
wx.setStorageSync(SESSION_KEY, data[SESSION_KEY]);
resolve();
} else {
reject(data.return_msg);
}
},
fail: err => {
// 登录失败,解除锁,防止死锁
isLogining = false;
reject(err);
}
});
} else {
// 登录失败,解除锁,防止死锁
isLogining = false;
reject();
}
},
fail: (err) => {
// 登录失败,解除锁,防止死锁
isLogining = false;
reject(err);
}
});
}
});
}

登录态静默续期的实现


checkSession

前面也提到,微信不会把session_key的有效期告知开发者,因此需要使用接口wx.checkSession()来校验session_key是否有效。

这里我们:

  1. 使用isCheckingSession来标志是否查询中。
  2. 返回 Promise。
  3. 使用isSessionFresh来标志session_key是否有效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { doLogin } from "./doLogin";
import { SESSION_KEY } from "./doLogin";

let isCheckingSession = false;
let isSessionFresh = false;

export function checkSession(): Promise<string> {
return new Promise((resolve, reject) => {
const session = wx.getStorageSync(SESSION_KEY);
if (isCheckingSession) {
setTimeout(() => {
checkSession()
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
}, 500);
} else if (!isSessionFresh && session) {
isCheckingSession = true;
wx.checkSession({
success: () => {
// session_key 未过期,并且在本生命周期一直有效
isSessionFresh = true;
resolve();
},
fail: () => {
// session_key 已经失效,需要重新执行登录流程
wx.removeStorage({
key: "skey",
complete: () => {
doLogin()
.then(() => {
resolve();
})
.catch(err => {
reject(err);
});
}
});
},
complete: () => {
isCheckingSession = false;
}
});
} else {
doLogin()
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
}
});
}

静默续期的接口请求

至此,我们可以封装一个简单的接口,来在每次登录态过期的时候自动续期:

  1. 在请求前,使用checkSession()检车本次周期内session_key是否有效,无效则doLogin()拉起登录获取sessionId
  2. 请求接口,若返回特定登录态失效错误码(此处假设为LOGIN_FAIL_CODE),则doLogin()拉起登录获取sessionId
  3. 使用tryLoginCount来标志重试次数,TRY_LOGIN_LIMIT来标志重试次数上限,避免进入死循环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import { doLogin } from "./doLogin";
import { SESSION_KEY } from "./doLogin";
import { checkSession } from "./checkSession";

// 会话过期错误码,需要重新登录
export const LOGIN_FAIL_CODES = [10000];

const TRY_LOGIN_LIMIT = 3;

export function request(obj: any = {}): Promise<object> {
return new Promise((resolve, reject) => {
checkSession()
.then(() => {
let session = wx.getStorageSync(SESSION_KEY);
const { url, data, method, header, dataType } = obj;
let tryLoginCount = obj.tryLoginCount || 0;
// 如果需要通过 data 把登录态 sessionId 带上
const dataWithSession = { ...data, [SESSION_KEY]: session, appid: APPID };
wx.request({
url,
data: dataWithSession,
method,
header,
dataType,
success: (res: any) => {
if (res.statusCode === 200) {
const data: ICommonResponse = res.data;
// 登陆态失效特定错误码判断,且重试次数未达到上限
if (LOGIN_FAIL_CODES.indexOf(data.return_code) > -1 && tryLoginCount < TRY_LOGIN_LIMIT) {
doLogin().then(() => {
obj.tryLoginCount = ++tryLoginCount;
request(obj)
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
});
});
} else {
resolve(res);
}
} else {
reject(res);
}
},
fail: function(err) {
reject(err);
}
});
})
.catch(err => {
reject(err);
});
});
}

至此,我们大概包装了一个能自动登录或是进行静默续期的一个请求接口。

参考

结束语


小程序的登录和登录态管理,大概是大部分小程序都需要的能力。codesession_key的设计,做了哪些事情来保护用户的数据。
如何在全局范围地保证登录态的有效性,微信侧的登录态也好,业务侧的登录态也好,静默续期的能力能给用户带来不少的体验提升。

码生艰难,写文不易,给我家猪囤点猫粮了喵~

B站: 被删

查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢

如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢

作者:被删

出处:https://godbasin.github.io

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

文章目录
  1. 1. 小程序登录
    1. 1.1. 登录时序
    2. 1.2. 相关数据或参数
    3. 1.3. 加锁的登录
  2. 2. 登录态静默续期的实现
    1. 2.1. checkSession
    2. 2.2. 静默续期的接口请求
    3. 2.3. 参考
  3. 3. 结束语