四时宝库

程序员的知识宝库

【JS fetch】使用fetch实现无感刷新

最近公司的项目出现一些调整,登录状态的token,从原来存放在sessionStorage里,时效周期性和浏览器tab关联,改变为有效期变为8小时,而且在token失效时不跳转登录页面,而是自动刷新。因此为了实现无感刷新token,梳理了下大致流程

  • 后端调整token接口逻辑,重新返回token数据格式
Bash
//token
{
  access_token:'xxxxxx',
  refresh_token:'xxxxx'
}
  • access_token是业务代码token的验证标识,一般放在headers的Authorization里,需要拼接 'Bearer',如 'Bearer access_token'
  • access_token失效之后,状态码返回401,前端根据401,用refresh_token请求刷新token的接口,获取token信息之后替换之后的Authorization
  • 重新获取token之后,之前的api请求,继续执行
  • 因为页面可能存在多个请求,需要解决因多个请求token失效而造成的重复刷新token接口
  • refresh_token失效之后跳转login页面


以上几点就是无感刷新的大致逻辑,代码如下

封装请求接口request

Bash
import qs from 'query-string';

const NOT_TOKEN_API = [
  '/oauth2/token',
]

/**
 * request 封装fetch函数
 * @params url 接口api
 * @params options fetch请求参数
 * * method GET,POST,PUT,DELETE
 * * headers 请求头参数配置
 * * body 请求体参数
 * * __isRefresh 是否是刷新token的标识
 * 
*/
async function request(url, options) {
  const {
    method = 'GET', 
    headers = {
      'Content-Type': 'application/json',
      'Authorization':'Basic anR4LWx1bmFyOjEyMzQ1Ng==',
    }, 
    body = null,
    __isRefresh=false
  } = options||{}
  const {access_token} = getToken()
  // 非token接口,headers需要添加Authorization
  //或者其他业务逻辑
  if(NOT_TOKEN_API.findIndex(s=>url.includes(s))===-1) {
    if(access_token) {
      headers['Authorization'] = 'Bearer ' + access_token
    }else {
      message.error('token 丢失请重新登陆')
      message.error('正在跳转登录页')
      window.location.href = '/login'
    }
  }

  // 构建请求配置
  const requestOptions = {
    method,
    headers,
    __isRefresh,
    body: body ? JSON.stringify(body) : null,
  };

  // 发起请求并返回 Promise 对象
  return fetch(url, requestOptions)
    .then(async(response)=>{
    	const {status} = response
    	const res = await response.json()
      if(status ===200) {
        // 返回业务数据
        return res;
      }
      //status为401时,token失效
      //__isRefresh为false,重新刷新token,也就是请求resfreshtoken()
      else if(status ===401 && !requestOptions.__isRefresh) {
        const refresh = await refreshToken()
        await sleep(100)
        if(refresh) {
          const resp = await request(url,options)
          return resp
        }
      }else {
        res && message.error(res.message||res.error_description||res.error)
        return res||{}
      }
    })
    .catch(async(error) => {
      // 错误处理逻辑
      message.error('请求失败')
      return {error:'error'}
      
    })
}

refreshToken刷新token

Bash
let promise = null
async function refreshToken () {
  const {access_token,refresh_token} = getToken()

  if(!access_token) {
    return null
  }

  //通过请求实例唯一解决多个请求token失效时重复请求
  if(promise) {
    return promise
  }
  // 非token接口,headers需要添加Authorization
  const params = {
    grant_type: 'refresh_token',
    refresh_token: refresh_token
  }
  // 序列化请求参数
  const query = qs.stringify(params)
  promise = new Promise(async(resp)=>{
    await request(
      `/api/auth/oauth2/token?${query}`,
      {
      method:'POST',
      __isRefresh:true
    }).then(async(res)=>{
      // 设置
      if(res?.access_token) {
        localStorage.setItem('tokenInfo',JSON.stringify(res))
        resp(res)
      }else {
        //refresh_token失效之后跳转login页面
        message.error('正在跳转 login page')
        goLogin()
        resp(null)
      }
    })
  })

  // promise结束之后重置实例
  promise.finally(()=>{
    promise=null
  })
  return promise

}
const getToken = () => {
  const tokenInfo = JSON.parse(localStorage.getItem('tokenInfo')||'{}')
  return tokenInfo
}
const goLogin = () => {
  localStorage.setItem('tokenInfo',JSON.stringify({}))
  window.location.href="/login"
}


无感刷新token实现之后,如下图


发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接