无痛刷新token(签名)解决方案

需求:小程序无用户登录,但是为了数据安全,API接口需要token进行签名验证,当token过期的时候,无感刷新token

  • 实现逻辑:
  1. 通过请求拦截器判断token失效时间,如果token失效,挂起需要请求的接口(一个或多个)
  2. 重新请求token接口,刷新token
  3. 将挂起的接口恢复请求
实现代码:
  import Request from 'luch-request'  import Action from './config/config' // 开发环境配置项  const { develop, production } = Action  // 根据运行环境配置  let apiAction  if (process.env.NODE_ENV === 'development') apiAction = develop  if (process.env.NODE_ENV === 'production') apiAction = production  const services = new Request()   /* 全局配置 */  services.setConfig((config) => {    config.baseURL = apiAction.BASE_URL + apiAction.API_PORT; /* 根域名 */    config.header = {      'Content-Type': 'application/json',      'X-Requested-With': 'XMLHttpRequest',      'Authorization': '',      'X-Token': ''    };    return config  })  // 正在刷新的标记  let isRefreshing = false  // 挂起请求的数组  let requests = []  // 存储token的函数  function setToken (obj) {    try {      services.config.header['X-Token'] = obj.access_token      uni.setStorage({ // 调用uni本地存储        key: 'token',        data: JSON.stringify(obj), // 注意这里需要变成字符串后才能放到Storage中        success: function () {          console.log('token本地存储成功');        }      });    } catch (e) {      console.log('token本地存储失败', e);    }  };  // 获取token  function getToken(){    let tokenObj = {}    const value = uni.getStorageSync('token');    if (value) tokenObj = JSON.parse(value);    return tokenObj  };  // 往挂起请求数组里面push请求  function subscribeTokenRefresh(cb) {    requests.push(cb);  }  // 数组中的请求得到新的token之后自执行,用新的token去请求数据  function onRrefreshed(token) {    requests.map(cb => cb(token));  }  services.interceptors.request.use(    config => {      // 这里是因为小程序规定必须使用https请求作出的判断      if (config.url.slice(0, 4) === "http" && config.url.slice(0, 5) !== "https") return console.error('请求地址不能为http,请修改为https')      if (config.url.slice(0, 4) !== "http" && config.url.slice(0, 5) !== "https" && config.url.slice(0, 1) !== "/") return console.error(config.url, '请求地址不合法')      if (config.url.slice(0, 5) === "https") config.baseURL = ''      const tokenObj = getToken();      const now = Date.now()      // 如果有token并且token没有过期,给每个请求加上token      if (Object.keys(tokenObj).length != 0 && now < tokenObj.expires_in) {        config.header['Authorization'] = 'Bearer ' + tokenObj.access_token;        return config;      }else {        // 如果token过期,并且没有在刷新中继续往下执行        if (!isRefreshing) {          isRefreshing = true;          // 密码进行加密          var crypto = require('crypto');          apiAction.PASS_WORD = crypto.createHash('md5').update("gsw").digest("hex").toLocaleUpperCase();          // 立即刷新token          uni.request({            url: apiAction.TOKEN_URL, // 请求token的地址            data: {              username: apiAction.USER_NAME,              password:  apiAction.PASS_WORD            },            method:'GET',//请求方式  或GET            success: res => {              if(res.data){                // 刷新成功, return config即是恢复当前请求                setToken({                  access_token: res.data.access_token,                  expires_in: res.data.expires_in + Date.now()                });                config.header['Authorization'] = 'Bearer ' + res.data.access_token;                onRrefreshed(res.data.access_token);              }            }          })        }        let retry = new Promise((resolve, reject) => {          /*(token) => {...}这个函数就是cb*/          subscribeTokenRefresh((access_token) => {            config.header['Authorization'] = 'Bearer ' + access_token            console.log('service.defaults.url',config);            /*将请求挂起*/            resolve(config)          })        })        return retry      }    }  );  // 响应拦截器  services.interceptors.response.use((response) => {    if (response.code  == 200 || response.data.code == 200  ) {      return response.data    } else {      uni.showToast({        title: res.data.msg,            icon: "none",        duration: 3000      })      return Promise.reject(response);    }  }, (err) => { /*  对响应错误做点什么 (statusCode !== 200)*/    return Promise.reject(err)  })  export default services