无痛刷新token续接401请求

在小程序开发中,我们都知道小程序是没有cookie的,那么用户身份是如何确定的,后段颁发token,前端每次请求头部附带token。

既然是token,那么肯定有它的过期时间,没有一个token是永久的,永久的token就相当于一串永久的密码,是不安全的,

那么既然有刷新时间,问题就来了

1.前后端交互的过程中token如何存储?

2.token过期时,前端该怎么处理?

3.当用户正在操作时,遇到token过期该怎么办?直接跳回登陆页面?

token如何存储?

cookie的大小约4k,兼容性在ie6及以上 都兼容,在浏览器和服务器间来回传递,因此它得在服务器的环境下运行,而且可以设定过期时间,默认的过期时间是session会话结束。

localStorage的大小约5M,兼容性在ie7及以上都兼容,有浏览器就可以,不需要在服务器的环境下运行, 会一直存在,除非手动清除 。

答案大致分为2种

存在cookie中

存在localStorage中

token过期时,前端该怎么处理?

1.第一种:跳回登陆页面重新登陆

2.第二种:拦截401重新获取token

class HttpClient {

  /**

  * Create a new instance of HttpClient.

  */

  constructor() {

    this.interceptors = {

      request: [],

      response: []

    };

  }

  /**

  * Sends a single request to server.

  *

  * @param {Object} options - Coming soon.

  */

  sendRequest(options) {

    let requestOptions = options;

    if (!requestOptions.header) {

      requestOptions.header = {};

    }

    // 重新设置 Accept 和 Content-Type

    requestOptions.header = Object.assign(

      {

        Accept: 'application/json, text/plain, */*',

        'Content-Type': 'application/json;charset=utf-8'

      },

      requestOptions.header

    );

    this.interceptors.request.forEach((interceptor) => {

      const request = interceptor(requestOptions);

      requestOptions = request.options;

    });

    // 将以 Promise 返回数据, 无 success、fail、complete 参数

    // let response = uni.request(requestOptions);

    // 使用Promise包装一下, 以 complete方式来接收接口调用结果

    let response = new Promise((resolve, reject) => {

      requestOptions.complete = (res) => {

        const { statusCode } = res;

        const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

        if(statusCode==401){ //拦截401请求

          uni.reLaunch({ //关闭所有页面直接跳到登陆页面

            url: '/pages/login/login'

          });

        }

        if (isSuccess) {

          if(res.data.code==1){

            resolve(res.data);

          }else{

            reject(res);

          }

        } else {

          reject(res);

        }

      };

      requestOptions.requestId = new Date().getTime();

      uni.request(requestOptions);

    });

    this.interceptors.response.forEach((interceptor) => {

      response = interceptor(response);

    });

    return response;

  }

}

export default HttpClient;

这种方法适用余有登陆页面的小程序,但同样存在问题,假如用户在填写表单,填写完毕你却告诉我重新登陆,确定用户不回卸掉你的APP???

有人说了  异常退出 我会本地缓存填写的表单内容,当然你要是能接受这种我也无话可说!!!

我们要做的是无痛刷新toekn,那么首先y要在401拦截的时候去重新登陆获取新的token

继续优化改造

import store from "../store/index.js";

class HttpClient {

  /**

  * Create a new instance of HttpClient.

  */

  constructor() {

    this.interceptors = {

      request: [],

      response: []

    };

  }

  /**

  * Sends a single request to server.

  *

  * @param {Object} options - Coming soon.

  */

  sendRequest(options) {

    let requestOptions = options;

    if (!requestOptions.header) {

      requestOptions.header = {};

    }

    // 重新设置 Accept 和 Content-Type

    requestOptions.header = Object.assign(

      {

        Accept: 'application/json, text/plain, */*',

        'Content-Type': 'application/json;charset=utf-8'

      },

      requestOptions.header

    );

    this.interceptors.request.forEach((interceptor) => {

      const request = interceptor(requestOptions);

      requestOptions = request.options;

    });

    // 将以 Promise 返回数据, 无 success、fail、complete 参数

    // let response = uni.request(requestOptions);

    // 使用Promise包装一下, 以 complete方式来接收接口调用结果

    let response = new Promise((resolve, reject) => {

      requestOptions.complete = (res) => {

        const { statusCode } = res;

        const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

        if(statusCode==401){ //拦截401请求

          store

            .dispatch("auth/login")    //调用vueX中的登陆将登陆信息保存到VueX

            .then(()=>{ //提示用户刚才操作无效重新操作一次

              uni.showToast({

                title: '请重新操作',

                duration: 2000,

                icon: "none",

              });

            })

            .catch(()=>{

              uni.showToast({

                title: '账户异常请重启程序',

                duration: 2000,

                icon: "none",

              });

            })

        }

        if (isSuccess) {

          if(res.data.code==1){

            resolve(res.data);

          }else{

            reject(res);

          }

        } else {

          reject(res);

        }

      };

      requestOptions.requestId = new Date().getTime();

      uni.request(requestOptions);

    });

    this.interceptors.response.forEach((interceptor) => {

      response = interceptor(response);

    });

    return response;

  }

}

export default HttpClient;

到此我们实现的在401错误时候去重新登陆获取新的token,且告知用户重新操作一次

到此你会发现一个问题,当页面存在一个请求,目前方案毫无问题,但是当存在两个、三个、四个请求,你会骂娘

失败3个请求会重新调用3次登陆会刷新3次token

那么此时要做的就是保证不多次登陆

思路加一个开关,当在登陆过程中后续错误不再走登陆接口

import store from "../store/index.js";

// 是否正在重新登陆刷新的标记

var loginRefreshing = false

class HttpClient {

  /**

  * Create a new instance of HttpClient.

  */

  constructor() {

    this.interceptors = {

      request: [],

      response: []

    };

  }

  /**

  * Sends a single request to server.

  *

  * @param {Object} options - Coming soon.

  */

  sendRequest(options) {

    let requestOptions = options;

    if (!requestOptions.header) {

      requestOptions.header = {};

    }

    // 重新设置 Accept 和 Content-Type

    requestOptions.header = Object.assign(

      {

        Accept: 'application/json, text/plain, */*',

        'Content-Type': 'application/json;charset=utf-8'

      },

      requestOptions.header

    );

    this.interceptors.request.forEach((interceptor) => {

      const request = interceptor(requestOptions);

      requestOptions = request.options;

    });

    // 将以 Promise 返回数据, 无 success、fail、complete 参数

    // let response = uni.request(requestOptions);

    // 使用Promise包装一下, 以 complete方式来接收接口调用结果

    let response = new Promise((resolve, reject) => {

      requestOptions.complete = (res) => {

        const { statusCode } = res;

        const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

        if(statusCode==401){ //拦截401请求

          if(!loginRefreshing){//防止重复登陆

            loginRefreshing = true

            store

              .dispatch("auth/login")    //调用vueX中的登陆将登陆信息保存到VueX

              .then(()=>{ //提示用户刚才操作无效重新操作一次

                uni.showToast({

                  title: '请重新操作',

                  duration: 2000,

                  icon: "none",

                });

              })

              .catch(()=>{

                uni.showToast({

                  title: '账户异常请重启程序',

                  duration: 2000,

                  icon: "none",

                });

              })

              .finally(()=>{

                //销毁 是否正在重新登陆刷新的标记

                loginRefreshing = false

              });

            }

        }

        if (isSuccess) {

          if(res.data.code==1){

            resolve(res.data);

          }else{

            reject(res);

          }

        } else {

          reject(res);

        }

      };

      requestOptions.requestId = new Date().getTime();

      uni.request(requestOptions);

    });

    this.interceptors.response.forEach((interceptor) => {

      response = interceptor(response);

    });

    return response;

  }

}

export default HttpClient;

我们可以看到在遇到两个401错误时候并没有请求两次login,只请求一次,到此刷新token算是完成了,但是需要用户配合去重新操作一次,还不是真正的无痛刷线token,做到用户无感知

思路:将请求401的请求缓存起来,在重新登陆完成之后再将缓存中的请求重新发出,

废话不多说直接上代码

import AuthService from "@/services/auth.service";

import store from "../store/index.js";

// 是否正在重新登陆刷新的标记

var loginRefreshing = false

// 重试队列,每一项将是一个待执行的函数形式

let requests = []

class HttpClient {

  /**

  * Create a new instance of HttpClient.

  */

  constructor() {

    this.interceptors = {

      request: [],

      response: []

    };

  }

  /**

  * Sends a single request to server.

  *

  * @param {Object} options - Coming soon.

  */

  sendRequest(options) {

    let requestOptions = options;

    if (!requestOptions.header) {

      requestOptions.header = {};

    }

    // 重新设置 Accept 和 Content-Type

    requestOptions.header = Object.assign(

      {

        Accept: 'application/json, text/plain, */*',

        'Content-Type': 'application/json;charset=utf-8'

      },

      requestOptions.header

    );

    this.interceptors.request.forEach((interceptor) => {

      const request = interceptor(requestOptions);

      requestOptions = request.options;

    });

    // 将以 Promise 返回数据, 无 success、fail、complete 参数

    // let response = uni.request(requestOptions);

    // 使用Promise包装一下, 以 complete方式来接收接口调用结果

    let response = new Promise((resolve, reject) => {

let timeId = setTimeout(()=>{

  reject({statusCode:504});

},10000)

      requestOptions.complete = (res) => {

        clearTimeout(timeId)

        const { statusCode } = res;

        const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304;

        if(statusCode==401){ //无痛刷新token

          if(!loginRefreshing){//防止重复登陆

            loginRefreshing = true

            store.dispatch("auth/logout");

            store

              .dispatch("auth/login")

              .then(()=>{

                //所有存储到对列组中的请求重新执行。

                requests.forEach(callback=>{

                  callback(AuthService.getToken() ? AuthService.getToken() : "")

                })

                //重试队列清空

                requests = []

              })

              .catch(()=>{

                uni.showToast({

                  title: '账户异常请重启程序',

                  duration: 2000,

                  icon: "none",

                });

              })

              .finally(()=>{

                //销毁 是否正在重新登陆刷新的标记

                loginRefreshing = false

              });

          }

          return new Promise((resolve) => {

            // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行

            requests.push((token) => {

              requestOptions.header.token = token //带着登陆后的新token

              resolve(uni.request(requestOptions))

            })

          })

        }

        if (isSuccess) {

          if(res.data.code==1){

            resolve(res.data);

          }else{

            reject(res);

          }

        } else {

          reject(res);

        }

      };

      requestOptions.requestId = new Date().getTime();

      uni.request(requestOptions);

    });

    this.interceptors.response.forEach((interceptor) => {

      response = interceptor(response);

    });

    return response;

  }

}

export default HttpClient;

知识点,在重新获取新的token后要将缓存起来的请求中的token替换为重新登陆后新的token

到此无痛刷新token续接401请求的方法已经处理完毕,在用户提交表单时候遇到token失效重新获取新的token再续接表单请求,此时用户毫无感知,可能在请求时间上多了延迟,体验好感度+99,哈哈哈哈哈     

到此无痛刷新token续接401已经完成请求快去试试吧

提示:有些后段在接口请求做了签名,记得像更换token一样在重新登陆完成之后更换新的时间戳新的签名等字段

你可能感兴趣的:(无痛刷新token续接401请求)