小程序优化系列(一)-如何实现无感刷新

191 阅读3分钟

前言

从本篇开始,我将会将我对于小程序相关优化的经验写成一个系列的文章,希望能够对大家有帮助,创作不易,希望大家多多点赞收藏!!

为什么要做无感刷新

起因是我们小程序收到用户的反馈说经常要刷新~~

image.png

都说顾客是上帝,那针对这个问题我们应该怎么去做优化呢?这就是我们本篇文章要介绍的无感刷新

什么是无感刷新

常规的token认证流程

在介绍无感刷新之前,我们先回顾一下常规的token认证流程:

image.png

我们一般的流程就是

  1. 登录的时候获取有效的token
  2. 将token保存到本地
  3. 请求其他接口时,通过拦截器在header里面携带上token
  4. 服务端校验token是否有效,如果有效则直接返回正确的响应,如果过期则返回状态码401
  5. 前端如果发现状态码是401则证明用户token已过期,强制让用户返回登录页重新登录

那这个流程会有什么问题呢?就是token会有一个过期时间,而一般出于安全考虑token的过期时间不会设置得很长,这就有一个问题就是这个时间是一个绝对时间,就是无论如何时间一到就会立即失效,这样的体验就会不是很好。因此无感刷新token就应运而生~~

无感刷新

无感刷新听起来很高大上,实际上它的原理非常简单:

在上面普通的流程的第一步,我们登录成功后要返回两个token, 一个access_token,一个refresh_tokenaccess_token的有效期较短,而refresh_token的有效期则需要长一点。然后当我们发现access_token过期后,我们就不能像普通流程一样强制用户重新登录了,而是应该拿refresh_token去尝试更新我们的access_token

流程图如下:

image.png

实现

在动手写代码之前,我们先不要着急,我们先要整理一下我们需要改动的地方

  • 登录模块: 我们需要将access_tokenrefresh_token两个token都保存到本地
  • 请求模块: 当发现请求返回状态码401,我们需要调用refreshToken接口去换token
  • 退出模块: access_tokenrefresh_token都需要清空

上面的登录模块和退出模块都比较简单,我们就不过多叙述了,我们将目光聚集在请求模块的实现。

我这里用的是UniApp, 原生小程序其实也是一样的,最重要的是意会~~

let requestQueue: UniApp.RequestOptions[] = []; // 请求队列-用于记录

else if (res.statusCode === 401) {
    // 401错误 -> 尝试刷新token, 如果刷新失败,返回登录页重新登录
    const refreshToken: string = getLocalToken("refreshToken");
    // 如果不存在refreshToken,则直接返回登录页
    if(!refreshToken) {
        goLogin();
        reject(res);
    } else {
        // 将不是刷新token的请求保存到队列中
        if(options.url.indexOf("/refreshToken") < 0) {
           requestQueue.push(options);
        }
        // 刷新token
        refreshUserToken(refreshToken).then(res => {
            const { access_token, refresh_token } = res.data;
            // 保存token
            setToken(access_token, "token");
            setToken(refresh_token, "refreshToken");
            // 将缓存队列清空
            while (requestQueue.length > 0) {
                const curTaskOptions = requestQueue.pop();
                if (curTaskOptions &&   curTaskOptions.url.indexOf("/staffAuth/refreshToken") < 0) {
                    http(curTaskOptions).then(res => {
                        resolve(res as Data<T>);
                    });
                }
            }
        }).catch(() => {
            goLogin();
            reject(res);
        });
    }
}

// 返回首页
const goLogin = () => {
  uni.showToast({
      icon: "none",
      title: "Token已过期,请重新登录"
  }).then(() => {
     clearToken(); // 清空token
     requestQueue = []; // 清空请求队列
     setTimeout(() => {
       uni.navigateTo({ url: "/pages/login/index" });
     }, 1000);
  });
};

上面的实现有个关键就是,我们需要把所有401的请求都存到一个队列中,然后当我们刷新完token之后,我们就要将队列中的请求一个一个拿出来重新执行。