token 和refresh token(无感知刷新token)

334 阅读3分钟

双token前端版本(token过期 拿 refreshtoken 获取新的token,未发送的请求在获取新的token后再发起) ,全部复制干净可以直接用,多余的逻辑可以自己删除,状态code 处理要看后端那边的定义的是什么. 消息提示的话 看用的什么框架也要改一下

request.js

import { showToast } from '@nutui/nutui';
import { isRefreshRequest } from '@/api/login';
import { getToken } from '@/utils/auth';
import { useUserStore } from '@/store/modules/user';
import router from '@/router';
import { showConfirmDialog } from 'vant';

// 创建 Axios 实例
const service: AxiosInstance = axios.create({
  withCredentials: false,
  baseURL: import.meta.env.VITE_APP_BASE_API,
  timeout: 10000, // 超时时间
});

// 请求拦截器
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const notRequireToken = (config.headers || {}).isToken === false;

    if (!notRequireToken && !isRefreshRequest(config) && getToken('accessToken')) {
      config.headers['Authorization'] = 'Bearer ' + getToken('accessToken');
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  },
);

// 响应拦截器
service.interceptors.response.use(
  async (res: AxiosResponse) => {
    const code = res.data.code;

    if (code && code !== 0 && code !== 1) {
      if (code === 450) {
        if (isRefreshRequest(res.config)) {
          //如果刷新token也过期了
          console.warn('refreshToken expired ');
          return Promise.resolve('无效的会话,或者会话已过期,请重新登录。');
        } else {
          const isSuccess = await useUserStore().refreshToken();
          if (isSuccess) {
            res.config.headers.Authorization = `Bearer ${getToken('accessToken')}`;
            return await service(res.config);
          } else {
            //刷新失败,到登录页
            showConfirmDialog({
              title: '系统提示',
              message: '登录状态已过期,您可以继续留在该页面,或者重新登录',
              cancelButtonText: '取消',
              confirmButtonText: '重新登录',
              theme: 'round-button',
            })
              .then(() => {
                // on confirm
                router.push(`/login`); // 否则全部重定向到登录页
              })
              .catch(() => {
                // on cancel
              });

            return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
          }
        }
      } else {
        showToast.text(`请求出错[${res.data.code}]:${res.data.message}`);

        res.data.api = res.config.url;
        return Promise.reject(res.data);
      }
    }

    if (code === 1) {
      //控制台打印警告信息
      console.warn('[Request warn]: ' + res.data.message);
      // ElMessage({ message: res.data.message, type: "warning" })
    }

    return res.data;
  },
  (error: AxiosError) => {
    let message = '系统错误';
    if (error.message === 'Network Error') {
      message = '服务器连接异常';
    } else if (error.message.includes('timeout')) {
      message = '请求服务器超时';
    } else if (error.message.includes('Request failed with status code')) {
      if (error.response?.status === 401) {
        message = '没有权限访问';
        router.push('/login');
      } else {
        message = `系统接口异常 ${error.message.substr(error.message.length - 3)}`;
      }
    }
    showToast.text(message);
    return Promise.reject(error);
  },
);

interface RequestConfig {
  url: string;
  data?: object;
  method?: string;
  params?: object;
  isUploadFile?: boolean;
  isDownloadFile?: boolean;
  downloadMeta?: boolean;
  baseURL?: string;
  timeout?: number;
  headers?: object;
}

// 封装请求函数
export function request({ url, data, method, params, baseURL, timeout, headers }: RequestConfig) {
  return service({
    url: url,
    method: method ?? 'get',
    data: data ?? {},
    params: params ?? {},
    baseURL: baseURL,
    timeout: timeout ?? import.meta.env.VITE_APP_API_TIMEOUT,
    headers: headers ?? {},
    // 可以根据需要添加自定义处理逻辑
  });
}

export default service;

函数import { isRefreshRequest } from '@/api/login';

  return !!config.headers?.__isRefreshToken;
}

import { getToken } from '@/utils/auth';


const { VITE_TOKEN_KEY } = import.meta.env;

export function getToken(tokenKey) {
  const token = localStorage.getItem(`${VITE_TOKEN_KEY}-${tokenKey}`);
  return isEmpty(token) ? '' : token;
}

export function setToken(tokenKey, token) {
  return localStorage.setItem(`${VITE_TOKEN_KEY}-${tokenKey}`, token);
}

export function removeToken(tokenKey) {
  return localStorage.removeItem(`${VITE_TOKEN_KEY}-${tokenKey}`);
}

import { isEmpty } from '@/utils/validate';

  // null or undefined
  if (val == null) return true;

  if (typeof val === 'boolean') return false;

  if (typeof val === 'number') return false;

  switch (Object.prototype.toString.call(val)) {
    // String or Array
    case '[object String]':
      return val === 'undefined' || val === 'null' || val.trim() === '';
    case '[object Array]':
      return !val.length;

    // Map or Set or File
    case '[object File]': {
      return val.size === 0 || val.type === '';
    }
    case '[object Map]': {
      return !val.size;
    }
    case '[object Set]': {
      return !val.size;
    }
    // Plain Object
    case '[object Object]': {
      return !Object.keys(val).length;
    }
  }
  return false;
}

公共状态库import { useUserStore } from '@/store/modules/user';

import { defineStore } from 'pinia';
import router from '@/router';

import { setToken, getToken, removeToken } from '@/utils/auth';
import defAva from '@/assets/images/default-avatar.png';//图片可删
import { loginWxWork, refreshToken, login, logout } from '@/api/login';//api
import { getUserInfo as _getUserInfo } from '@/api/user';//api
import useAppStore from '@/store/modules/app';

const { VITE_TOKEN_KEY } = import.meta.env;
// const token = useCookies().get(VITE_TOKEN_KEY as string);
let refreshTokenPromise;
let logOutPromise: Promise<void> | null = null;

interface StoreUser {
  info: Record<string, any>; // 或者你可以更精确地定义 info 的结构
  token: string | null;
  id: string;
  name: string;
  avatar: string;
  roles: string[];
  permissions: string[];
}
export const useUserStore = defineStore({
  id: 'app-user',
  state: (): StoreUser => ({
    info: {},
    token: getToken(VITE_TOKEN_KEY),
    id: '',
    name: '',
    avatar: '',
    roles: [],
    permissions: [],
  }),
  getters: {},
  actions: {
    setInfo(info: any) {
      this.info = info ? info : '';
    },
    // 登录
    login(userInfo) {
      const username = userInfo.username.trim();
      const password = userInfo.password;
      const code = userInfo.code;
      const uuid = userInfo.uuid;
      return new Promise((resolve, reject) => {
        login({ username, password, code, uuid })
          .then((res) => {
            setToken('accessToken', res.data.accessToken);
            setToken('refreshToken', res.data.refreshToken);
            resolve(res);
          })
          .catch((error) => {
            reject(error);
          });
      });
    },
    //企业微信登录
    loginWxWork(code) {
      return new Promise((resolve, reject) => {
        loginWxWork(code)
          .then((res) => {
            setToken('accessToken', res.data.accessToken);
            setToken('refreshToken', res.data.refreshToken);
            resolve(res);
          })
          .catch((err) => {
            reject(err);
          });
      });
    },
    // 退出系统

    logOut(): Promise<void> {
      console.log('come?');

      if (logOutPromise) {
        console.log('logOutPromise->', logOutPromise);
        return logOutPromise;
      }
      logOutPromise = new Promise<void>((resolve, reject) => {
        logout()
          .then(() => {
            resolve();
          })
          .catch((err: unknown) => {
            console.warn('logout err->', err);
            reject(err);
          })
          .finally(() => {
            this.token = '';
            this.roles = [];
            this.permissions = [];
            removeToken('accessToken');
            removeToken('refreshToken');
            useAppStore().toggleRefreshStatus(true);
            router.push({ path: '/login' });
          });
      }).finally(() => {
        logOutPromise = null;
      });

      return logOutPromise;
    },

    // 获取用户信息
    getUserInfo() {
      return new Promise((resolve, reject) => {
        _getUserInfo()
          .then((res) => {
            const user = res.data.user;
            const avatar = user.avatar == '' || user.avatar == null ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
            this.roles = res.data.roles;
            this.permissions = res.data.permissions;

            this.id = user.empLoginid;
            this.name = user.empName;
            this.avatar = avatar;

            resolve(res.data);
          })
          .catch((err) => {
            reject(err);
          });
      });
    },
    async refreshToken() {
      if (refreshTokenPromise) {//这一步很重要,可以代替 Queen的创建 实现 未发送完的请求再拿到新token 之后重新拉起
        return refreshTokenPromise;
      }
      refreshTokenPromise = new Promise(async (resolve, reject) => {
        let res;
        try {
          res = await refreshToken();

          if (res.code === 0) {
            setToken('accessToken', res.data.accessToken);
            setToken('refreshToken', res.data.refreshToken);
            console.log('token refreshed at ', new Date());
          }
          resolve(res.code === 0);
        } catch (err) {
          reject(err);
        }
      })
        .catch((_err) => {
          return false;
        })
        .finally(() => {
          refreshTokenPromise = null;
        });
      return refreshTokenPromise;
    },
  },
  // persist: {
  //   key: 'token',
  //   storage: localStorage,
  //   paths: ['token'],
  // },
});

公共状态库import useAppStore from '@/store/modules/app';

// import Cookies from 'js-cookie';

const useAppStore = defineStore('app', {
  state: () => ({
    // sidebar: {
    //   // opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
    //   withoutAnimation: false,
    //   hide: false,
    // },
    device: 'desktop',
    // size: Cookies.get('size') || 'default',
    isRefresh: true, //页面是否刷新
  }),
  actions: {
    // toggleSideBar(withoutAnimation) {
    //   if (this.sidebar.hide) {
    //     return false;
    //   }
    //   this.sidebar.opened = !this.sidebar.opened;
    //   this.sidebar.withoutAnimation = withoutAnimation;
    //   if (this.sidebar.opened) {
    //     Cookies.set('sidebarStatus', 1);
    //   } else {
    //     Cookies.set('sidebarStatus', 0);
    //   }
    // },
    // closeSideBar({ withoutAnimation }) {
    //   Cookies.set('sidebarStatus', 0);
    //   this.sidebar.opened = false;
    //   this.sidebar.withoutAnimation = withoutAnimation;
    // },
    // toggleDevice(device) {
    //   this.device = device;
    // },
    // setSize(size) {
    //   this.size = size;
    //   Cookies.set('size', size);
    // },
    // toggleSideBarHide(status) {
    //   this.sidebar.hide = status;
    // },
    //切换刷新状态
    toggleRefreshStatus(status) {
      this.isRefresh = status;
    },
  },
});

export default useAppStore;