美文网首页
微信小程序 自动刷新 token 记录

微信小程序 自动刷新 token 记录

作者: chenhongqi | 来源:发表于2022-06-10 11:34 被阅读0次

    微信小程序 自动刷新 token 记录

    由于对 promise 不熟悉,看了不少资料,踩了不少坑。

    为了让有缘人少踩点坑。这里记录一下。

    1.封装wx.request

    import apiConsts from './apiConsts.js';

    export async function wxrequest(url, params, method, header) {
    return new Promise((resolve, reject) => {
    wx.request({
    url: url,
    method: method,
    data: params,
    header: header,
    success(response) {
    if (response.statusCode === 200) {
    if (response.data.code == apiConsts.SUCCESS) {
    console.log('业务正常,返回数据:response.data', response.data);
    console.log('业务正常,返回数据:response.data.data', response.data.data);
    resolve(response.data.data);
    } else {
    console.error('业务错误:', response.data.message);
    reject({
    errCode: response.data.code, //业务错误码
    errMsg: response.data.message
    })
    }
    } else if (response.statusCode == 401) {
    console.error('发生401错误,令牌超时无效');
    reject({
    errCode: 401,
    errMsg: 发生401错误,令牌超时无效
    })
    } else if (response.statusCode == 403) {
    console.error('发生403错误,必须登录');
    reject({
    errCode: 403,
    errMsg: 发生403错误,必须登录
    })
    } else {
    console.error('服务器端发生错误,状态码:', response.statusCode);
    reject({
    errCode: response.statusCode, //https错误码
    errMsg: 服务器端发生错误,状态码: ${response.statusCode}
    });
    }
    },
    fail(e) {
    console.error('调用api失败:', e.errMsg);
    reject({
    errCode: -1,
    errMsg: 调用api失败: ${e.errMsg}(${e.errno})
    });
    }
    })
    })
    }

    1. 获取数据方法
      这里有个重要的操作,是 getAccessToken。获取 token。用 await 标记。

    async function fetch(url, header, params, msg) {
    params.signature = sign(params); //加签名
    //这个地方如果是个 Promise,不返回的话就挂起,后面的就不执行了
    console.log("2.等待获取 accessToken......");
    //只有在更新令牌的时候才挂起
    let token = await getAccessToken();
    console.log("header", header);
    return request(url, params, header, token, msg)
    .catch(e => {
    if (e.errCode == -444) {
    toLogin();
    } else {
    return Promise.reject(e);
    }
    });
    }

    3.调用 wxrequest 的写法:这里有一个给 header 赋值 token 的操作。你可以改成其他的,看后台怎么写了。
    这里也有一个 await getAccessToken() 的调用。

    function request(url, params, header, token, msg) {
    console.log("3.请求数据......");
    console.log('request msg:', msg);
    console.log('request前的 header:', header);
    header['X-TOKEN'] = token;
    console.log('赋值token 后的 header:', header);
    return wxrequest(url, params, 'POST', header)
    .catch(async (e) => {
    if (e.errCode == 401) {
    updateToken();
    let token = await getAccessToken();
    return request(url, params, header, token, '重新请求:' + msg);
    }
    if (e.errCode == 403) {
    throw {
    errCode: -444,
    errMsg: '禁止访问,必须登录'
    };
    }
    return Promise.reject(e);
    })
    .then(data => {
    console.log("得到的数据:", data);
    return data
    });
    }

    4.let token = await getAccessToken() 的意义
    await 等待一个 promise,如果这个 promise 一直是 pending 状态的话,就会挂起,一直等待。

    在 request 之前调用,如果正在刷新令牌,则先挂起不请求。
    在 request 之后调用,如果401了,如果已经有其他的在刷新令牌了,在重新请求数据之前,也挂起来不请求。

    当刷新令牌完毕,则给挂起来的 promise,resolved 一下,挂起来的 promise 释放。

    如果你的请求不需要 token,其实只要后台不检查就行了。为不为空无所谓。唯一的缺点就是不需要token 的时候,不巧 token 过期了,也要等上一等。

    async function getAccessToken() {
    console.log("2.1 获取令牌...");
    //如果正在刷新令牌,挂起来
    if (isRefreshing) {
    console.log("2.2 令牌正在刷新,先挂起来...");
    //将被拦截的请求挂起 存到缓存池中
    const externalControl = {
    resolved: null,
    };
    这里参考了网上的一段资料,很聪明的做法
    // 这里返回了一个新的Promise变相的实现请求的挂起(只要没有resolved或rejected,请求就会一直处于pedding状态)
    // 并将Promise状态的改变放到了外部一个对象来控制 externalControl ,待定池缓存这个对象即可,待需要执行后续被拦截请求,只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
    const pendingPromise = new Promise((resolved) => {
    externalControl.resolved = resolved;
    });
    pendingRequests.push(externalControl);

    return pendingPromise; //返回一个被挂起的 Promise,await 就一直等待
    }

    //没有正在刷新令牌,返回
    const accessToken = getApp().globalData.tokenInfo?.accessToken;
    return Promise.resolve(accessToken);
    };

    1. 刷新令牌
      如果刷新成功,则将挂起来的 promise 释放掉,其实就是调用 resolve 方法,把令牌扔出来,promise 的状态改变了。则会执行后续的代码。

    async function updateToken() {
    if (isRefreshing) return;
    const refreshToken = getApp().globalData.tokenInfo?.refreshToken;
    if (!refreshToken) {
    // 注意在异步函数里 throw 异常,外部无法用 catch 捕获
    throw {
    errCode: -444,
    errMsg: '没有刷新令牌,必须登录获取'
    };
    }

    console.log("有 refreshToken,开始刷新令牌", refreshToken);
    try {
    await doUpdateToken(refreshToken);
    } catch (e) {
    if (parseInt(e.errCode) > 100000) {
    // const REFRESH_TOKEN_IS_TIME_OUT = 900211;
    // const REFRESH_TOKEN_IS_INVALID = 900212;
    //判断错误的类别,要不要重新登录
    // 注意在异步函数里 throw 异常,外部无法用 catch 捕获
    throw {
    errCode: -444,
    errMsg: '刷新令牌失败,必须登录获取'
    };
    } else {
    throw e;
    }
    }
    }

    async function doUpdateToken(refreshToken) {
    isRefreshing = true; //第一个进入的修改

    return refreshTokenRequest(refreshToken)
    .then(tokenInfo => {
    getApp().setTokenInfo(tokenInfo);
    isRefreshing = false;
    const newAccesssToken = tokenInfo.accessToken;
    // 用新的token重新发起待定池中的请求
    pendingRequests.forEach((item) => {
    item.resolved(newAccesssToken);
    });

    // 清空缓存池
    pendingRequests = [];

    return newAccesssToken;
    })
    .catch(e => {
    isRefreshing = false;
    pendingRequests = []; // 清空缓存池???
    return Promise.reject(e);
    })
    };

    async function refreshTokenRequest(refreshToken) {
    console.log("refreshToken:", refreshToken);
    let url = refreshTokenUrl;
    let method = 'POST';
    let header = headers;
    let params = {
    refreshToken: refreshToken,
    appId: apiBase.appId,
    time: new Date().getTime(), //milliseconds
    nonce: '',
    signature: ''
    }
    params.signature = sign(params);
    try {
    let data = await wxrequest(url, params, method, header);
    return Promise.resolve(data.token);
    } catch (e) {
    return Promise.reject(e);
    }
    }


    不知道看明白了没?
    下面是完整点的代码,试了试,基本还行。
    当然还能改造,欢迎高手改造代码


    import apiBase from './apiBase.js';
    import apiConsts from './apiConsts.js';
    import sign from '../utils/sign.js';
    import throwIf from '../utils/assert.js';
    import {
    headers
    } from './apiHeaders.js';
    import {
    refreshTokenUrl
    } from './apiUrls.js';

    import {
    wxrequest
    } from './request.js';

    let isRefreshing = false; // 用于拦截鉴权失败的请求
    let pendingRequests = []; // 被拦截请求的缓存池

    async function fetch(url, header, params, msg) {
    params.signature = sign(params); //加签名
    //这个地方如果是个 Promise,不返回的话就挂起,后面的就不执行了
    console.log("2.等待获取 accessToken......");
    //只有在更新令牌的时候才挂起
    let token = await getAccessToken();
    console.log("header", header);
    return request(url, params, header, token, msg)
    .catch(e => {
    if (e.errCode == -444) {
    toLogin();
    } else {
    return Promise.reject(e);
    }
    });
    }

    async function getAccessToken() {
    console.log("2.1 获取令牌...");
    //如果正在刷新令牌,挂起来
    if (isRefreshing) {
    console.log("2.2 令牌正在刷新,先挂起来...");
    //将被拦截的请求挂起 存到缓存池中
    const externalControl = {
    resolved: null,
    };
    // 这里返回了一个新的Promise变相的实现请求的挂起(只要没有resolved或rejected,请求就会一直处于pedding状态)
    // 并将Promise状态的改变放到了外部一个对象来控制 externalControl ,待定池缓存这个对象即可,待需要执行后续被拦截请求,只需要利用这个对象引用的 resolved 来改变Promise状态即可实现请求挂起的放行
    const pendingPromise = new Promise((resolved) => {
    externalControl.resolved = resolved;
    });
    pendingRequests.push(externalControl);

    return pendingPromise; //返回一个被挂起的 Promise,await 就一直等待
    }

    //没有正在刷新令牌,返回
    const accessToken = getApp().globalData.tokenInfo?.accessToken;
    return Promise.resolve(accessToken);
    };

    function request(url, params, header, token, msg) {
    console.log("3.请求数据......");
    console.log('request msg:', msg);
    console.log('request前的 header:', header);
    header['X-TOKEN'] = token;
    console.log('赋值token 后的 header:', header);
    return wxrequest(url, params, 'POST', header)
    .catch(async (e) => {
    if (e.errCode == 401) {
    updateToken();
    let token = await getAccessToken();
    return request(url, params, header, token, '重新请求:' + msg);
    }
    if (e.errCode == 403) {
    throw {
    errCode: -444,
    errMsg: '禁止访问,必须登录'
    };
    }
    return Promise.reject(e);
    })
    .then(data => {
    console.log("得到的数据:", data);
    return data
    });
    }

    async function updateToken() {
    if (isRefreshing) return;
    const refreshToken = getApp().globalData.tokenInfo?.refreshToken;
    if (!refreshToken) {
    // 注意在异步函数里 throw 异常,外部无法用 catch 捕获
    throw {
    errCode: -444,
    errMsg: '没有刷新令牌,必须登录获取'
    };
    }

    console.log("有 refreshToken,开始刷新令牌", refreshToken);
    try {
    await doUpdateToken(refreshToken);
    } catch (e) {
    if (parseInt(e.errCode) > 100000) {
    // const REFRESH_TOKEN_IS_TIME_OUT = 900211;
    // const REFRESH_TOKEN_IS_INVALID = 900212;
    //判断错误的类别,要不要重新登录
    // 注意在异步函数里 throw 异常,外部无法用 catch 捕获
    throw {
    errCode: -444,
    errMsg: '刷新令牌失败,必须登录获取'
    };
    } else {
    throw e;
    }
    }
    }

    async function doUpdateToken(refreshToken) {
    isRefreshing = true; //第一个进入的修改

    return refreshTokenRequest(refreshToken)
    .then(tokenInfo => {
    getApp().setTokenInfo(tokenInfo);
    isRefreshing = false;
    const newAccesssToken = tokenInfo.accessToken;
    // 用新的token重新发起待定池中的请求
    pendingRequests.forEach((item) => {
    item.resolved(newAccesssToken);
    });

    // 清空缓存池
    pendingRequests = [];

    return newAccesssToken;
    })
    .catch(e => {
    isRefreshing = false;
    pendingRequests = []; // 清空缓存池???
    return Promise.reject(e);
    })
    };

    async function refreshTokenRequest(refreshToken) {
    console.log("refreshToken:", refreshToken);
    let url = refreshTokenUrl;
    let method = 'POST';
    let header = headers;
    let params = {
    refreshToken: refreshToken,
    appId: apiBase.appId,
    time: new Date().getTime(), //milliseconds
    nonce: '',
    signature: ''
    }
    params.signature = sign(params);
    try {
    let data = await wxrequest(url, params, method, header);
    return Promise.resolve(data.token);
    } catch (e) {
    return Promise.reject(e);
    }
    }

    //前往登录页面
    function toLogin() {
    wx.showToast({
    title: '登录失败,请重新登录',
    icon: 'none',
    success: () => {
    setTimeout(() => {
    wx.reLaunch({
    url: '/pages/userLogin/userLogin',
    })
    }, 1200);
    }
    })
    }

    export {
    fetch,
    };

    相关文章

      网友评论

          本文标题:微信小程序 自动刷新 token 记录

          本文链接:https://www.haomeiwen.com/subject/wzwimrtx.html