• React-umi-request动态刷新Token功能实现及node.js 代码逻辑


    在Antd-pro里面,使用的是umi-request,为了实现动态刷新token,我使用了拦截器。

    拦截器更新token有两种:

    方法一:在请求发起前拦截每个请求,判断token的有效时间是否已经过期,若已过期,则将请求挂起,先刷新token后再继续请求。

    • 优点: 在请求前拦截,能节省请求,省流量。
    • 缺点: 需要后端额外提供一个token过期时间的字段 refreshTime ;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。

    方法二:拦截返回后的数据。先发起请求,接口返回过期后,先刷新token,再进行一次重试。

    • 优点:不需额外的token过期字段,不需判断时间。
    • 缺点: 会消耗多一次请求,耗流量。

    综上考虑,方法一和二优缺点是互补的,方法一有校验失败的风险(本地时间被篡改时,当然一般没有用户闲的蛋疼去改本地时间的啦),方法二更简单粗暴,等知道服务器已经过期了再重试一次,只是会耗多一个请求。

    我看有的博主用了方法二,我个人更喜欢方法一,(原因就是有的请求要求并发,for循环,一次出去10个请求,所以嘛。。。)

    下面是实现过程

    /**
     * request 网络请求工具
     * 更详细的 api 文档: https://github.com/umijs/umi-request
     */
    import { extend } from 'umi-request';
    import { notification } from 'antd';
    import { routerRedux } from 'dva/router';
    import { getUserToken, saveUserToken, clearAuthority } from './authority';
    import jwt_decode from 'jwt-decode'
    const codeMessage = {
      200: '服务器成功返回请求的数据。',
      201: '新建或修改数据成功。',
      202: '一个请求已经进入后台排队(异步任务)。',
      204: '删除数据成功。',
      400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
      401: '用户没有权限(令牌、用户名、密码错误)。',
      403: '用户得到授权,但是访问是被禁止的。',
      404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
      406: '请求的格式不可得。',
      410: '请求的资源被永久删除,且不会再得到的。',
      422: '当创建一个对象时,发生一个验证错误。',
      500: '服务器发生错误,请检查服务器。',
      502: '网关错误。',
      503: '服务不可用,服务器暂时过载或维护。',
      504: '网关超时。',
    };
    /**
     * 异常处理程序
     */
    
    const errorHandler = error => {
      const { response } = error;
    
      if (response && response.status) {
        const errorText = codeMessage[response.status] || response.statusText;
        const { status, url } = response;
        notification.error({
          message: `请求错误 ${status}: ${url}`,
          description: errorText,
        });
      } else if (!response) {
        notification.error({
          description: '您的网络发生异常,无法连接服务器',
          message: '网络异常',
        });
      }
    
      return response;
    };
    /**
     * 配置request请求时的默认参数
     */
    
    const request = extend({
      errorHandler,
      // 默认错误处理
      credentials: 'include', // 默认请求是否带上cookie
    });
    
    let _cacheRequest = [];
    let guoqi = true;
    let ispending = false;
    request.interceptors.request.use(async (url, options) => {
    
        const token = getUserToken();
        if( token ){
            //如果有token 就走token逻辑
            const headers = {
                Authorization: `Bearer ${token}`,
            };
            //如果是刷新token接口,就直接过,不要拦截它!!!
            if( url === '/weiqinketop/api/account/login/getnewtoken'){
                return ({
                    url: url,
                    options: { ...options, headers: headers },
                });
            }
            let decodeToken = jwt_decode(token);
            const { iat, exp, refreshTime } = decodeToken;
            const maxTime = exp*1000+refreshTime;
            const nowTime = new Date().getTime();
            console.log( parseInt((maxTime - nowTime)/1000) +'秒后重新登录')
            if( nowTime >= maxTime){
                //token过期,而且延期token也过去了,那么,清空你的数据,直接返回登录,不允许操作了
                console.log('超时了')
                clearAuthority()
                location.href = '/user/login'
                return;
            }
            console.log( parseInt((exp*1000 - nowTime)/1000) + '秒后第一个过期' )
            if( nowTime >= exp*1000 ){
                //只是过期了,那就去拿新的token
                if( ispending ){
                    //如果正在发送中,此请求就等一会吧,生成一个Promise 等新token返回的时候,我再resolve
                }else{
                    //如果没发送,立刻改为发送状态
                    ispending = true;
                    //如果过期了还没请求新token,那就请求新token 记得带上旧token
                    request('/weiqinketop/api/account/login/getnewtoken',{
                        method: 'POST',
                        data:{}    
                    }).then((res)=>{
                        if( res.status === 200 ){
                            ispending = false;
                            let token = res.token;
                            saveUserToken( token )
                            _cacheRequest.map(cb => cb())
                        }
                    })
    
                }
                return new Promise((resolve, reject) => {
                    _cacheRequest.push(() => {
                        resolve()
                    })
                });
            }
            return ({
                url: url,
                options: { ...options, headers: headers },
            });
        }
        return ({
            url: url,
            options: options,
        });
    })
    //第二个拦截器,为什么会存在第二个拦截器呢?就是因为第一个拦截器有可能返回Promise,那么Promise由第二个拦截器处理吧。之前因为这个问题跟umi提了issues。原来是我没搞明白。。。
    request.interceptors.request.use(async (url, options) => {
        const token = getUserToken();
        if( token ){
            //如果有token 就走token逻辑
            const headers = {
                Authorization: `Bearer ${token}`,
            };
            return ({
                url: url,
                options: { ...options, headers: headers },
            });
        }
        return ({
            url: url,
            options: options,
        });
    
    
    
    })
    export default request;

    说下中途遇到的问题吧,原本只有第一个拦截器,返回Promise,总是会造成请求二次重发,我后来请教官方,得知 resolve( request( url, options) ) 导致的重发请求,现在已经进行了修复,可以正常使用了,后台使用的node.js KOA,下面贴下代码吧

    /**** Token功能类 ****/
    const jwt = require('jsonwebtoken');
    /**** secret编码 ****/
    const secret = '002c6';
    /*****
     * 过期时间延长24小时
     */
    // const refreshTime = 86400000;
    /*****
     * 过期时间延长10s
     */
    const refreshTime = 10000;
    
    class Token{
         constructor() {
            this.jwt = jwt;
            this.secret = secret;
        }
        /*******生成的格式就是{alg: "HS256",typ: "JWT"}.{name: "你的名字字段",refreshTime: 10000,iat: 1573704935,exp: 1573704947.[signature] */
        createToken( payload , dateStr ){
            payload.refreshTime = refreshTime;
            return this.jwt.sign(payload, this.secret, { expiresIn: dateStr });
        }
    }
    
    // module.exports = {
    //   proToken : new Token()
    // }
    
    exports.proToken = new Token()
  • 相关阅读:
    lufylegendRPG游戏引擎 Yorhom's Game Box
    讨论交流 Yorhom's Game Box
    货币之间的大小写转换
    Unreal3的D3D渲染器部分
    Linxu宿主目录
    用于主题检测的临时日志(b25e234297d442ccba394dd2241308d2 3bfe001a32de4114a6b44005b770f6d7)
    Linux命令 文件命名规则 基础
    C#_GDI_文章粗略整合
    由IDisposable接口导致的using使用 以及using的其他用法
    ADO.NET基础备忘1_SqlConnection SqlCommand SqlDataReader
  • 原文地址:https://www.cnblogs.com/qkstart/p/11856168.html
Copyright © 2020-2023  润新知