记录 soybean-admin 中的某些模块

804 阅读5分钟

环境配置

// package.json

首先看 package.json 中的脚本

举例:
"dev": "cross-env VITE_SERVICE_ENV=dev vite",

当我们启动项目时,设置了一个环境变量 VITE_SERVICE_ENV=dev

然后全局搜索这个环境变量在哪里被使用

// .env-config.ts

/** 请求服务的环境配置 */
type ServiceEnv = Record<ServiceEnvType, ServiceEnvConfig>;

/** 不同请求服务的环境配置 */
const serviceEnv: ServiceEnv = {
  dev: {
    url: 'http://localhost:8080'
  },
  test: {
    url: 'http://localhost:8080'
  },
  prod: {
    url: 'http://localhost:8080'
  }
};

/**
 * 获取当前环境模式下的请求服务的配置
 * @param env 环境
 */
export function getServiceEnvConfig(env: ImportMetaEnv): ServiceEnvConfigWithProxyPattern {
  // 发现在这里被使用
  const { VITE_SERVICE_ENV = 'dev' } = env;

  // 根据不同的 VITE_SERVICE_ENV 获取不同的后端url
  const config = serviceEnv[VITE_SERVICE_ENV];

  // 返回出去配置信息
  return {
    ...config,
    proxyPattern: '/proxy-pattern'
  };
}

然后发现这个函数在两个地方使用到

// 第一处 在发送请求时
// request/index.ts

const { url, proxyPattern } = getServiceEnvConfig(import.meta.env);
// 在axios请求中 使用url
export const request = createRequest({ baseURL: isHttpProxy ? proxyPattern : url });

// 第二处
// 在 vite.config.ts中 配置代理

const envConfig = getServiceEnvConfig(viteEnv);

server: {
      host: '0.0.0.0',
      port: 3200,
      open: true,
      proxy: createViteProxy(isOpenProxy, envConfig)
    },

token相关

在我写nestjs之后,对token有更清晰的认知,就是当我们登录一个系统的时候,会给后端传递账密之类的数据,后端经过查询校验之后,会返回给前端用户一个token。就是这样,欸。 然后我们前端拿到token会做些什么?

其实不得不插一句题外话,入场晚,当我终于开始了解一些事情的时候,这些事情已经过时了,所以我的文章都没啥用,写给自己舒服罢咯😟😟

拿到token以后,看一下前端的代码逻辑

    /**
     * 登录
     * @param userName - 用户名
     * @param password - 密码
     */
    async login(userName: string, password: string) {
      this.loginLoading = true;
      // fetchLogin 这就是我们的登录函数
      const { data } = await fetchLogin(userName, password);
      if (data) {
        // 登录成功以后,跳到 handleActionAfterLogin 处理登录成功以后的逻辑
        await this.handleActionAfterLogin(data);
      }
      this.loginLoading = false;
    },
 /**
     * 处理登录后成功或失败的逻辑
     * @param backendToken - 返回的token
     */
    async handleActionAfterLogin(backendToken: ApiAuth.Token) {
      const route = useRouteStore();
      const { toLoginRedirect } = useRouterPush(false);

      // 在这里 我们发现 开始使用 token 至于干嘛了 看下个代码块的代码 标志 ★
      const loginSuccess = await this.loginByToken(backendToken);

      if (loginSuccess) {
        await route.initAuthRoute();

        // 跳转登录后的地址
        toLoginRedirect();

        // 登录成功弹出欢迎提示
        if (route.isInitAuthRoute) {
          window.$notification?.success({
            title: '登录成功!',
            content: `欢迎回来,${this.userInfo.userName}!`,
            duration: 3000
          });
        }

        return;
      }

      // 不成功则重置状态
      this.resetAuthStore();
    },
// 第一次使用token的 地方 ★

/**
     * 根据token进行登录
     * @param backendToken - 返回的token
     */
    async loginByToken(backendToken: ApiAuth.Token) {
      let successFlag = false;

      // 先把token存储到缓存中(后面接口的请求头需要token) 
      // 首先是解析
      const { token, refreshToken } = backendToken;
      // 其次是存储到本地
      localStg.set('token', token);
      // 话说 这个 refreshToken 的是用来干嘛的 我还不是很熟悉,后面补充一下吧
      localStg.set('refreshToken', refreshToken);

      // 获取用户信息,这里我们访问这个接口的时候是需要把token绑定在请求头上的 ★★
      const { data } = await fetchUserInfo();
      if (data) {
        // 成功后把用户信息存储到缓存中
        localStg.set('userInfo', data);

        // 更新状态
        this.userInfo = data;
        this.token = token;

        successFlag = true;
      }

      return successFlag;
    },
★★
 // 一般是在axios请求中
 // 设置token
 handleConfig.headers.Authorization = localStg.get('token') || '';
 
 然后后端会规定有些接口的访问必须经过token校验,比如说获取用户信息等

还有个地方使用了token,就是判断用户是否登录

const isLogin = Boolean(localStg.get('token'));

通过 判断 是否登录 决定访问页面时 相应的行为是什么
const actions: Common.StrategyAction[] = [
    // 已登录状态跳转登录页,跳转至首页
    [
      isLogin && to.name === routeName('login'),
      () => {
        next({ name: routeName('root') });
      }
    ],
    // 不需要登录权限的页面直接通行
    [
      !needLogin,
      () => {
        next();
      }
    ],
    // 未登录状态进入需要登录权限的页面
    [
      !isLogin && needLogin,
      () => {
        const redirect = to.fullPath;
        next({ name: routeName('login'), query: { redirect } });
      }
    ],
    // 登录状态进入需要登录权限的页面,有权限直接通行
    [
      isLogin && needLogin && hasPermission,
      () => {
        next();
      }
    ],
    [
      // 登录状态进入需要登录权限的页面,无权限,重定向到无权限页面
      isLogin && needLogin && !hasPermission,
      () => {
        next({ name: routeName('403') });
      }
    ]
  ];
退出的时候直接 清除token 就可以啦

/** 去除用户相关缓存 */
export function clearAuthStorage() {
  localStg.remove('token');
  localStg.remove('refreshToken');
  localStg.remove('userInfo');
}

小结一下 token主要的作用就是校验

  • 通过传给后台,校验当前用户的身份
  • 储存在前台,校验当前登录的状态

。。。。。未完待续,我应该去看看 refreshToken是干嘛的,突然想起很火的文章,无痛刷新?

更新

refreshToken

refreshToken的工作流程如下:

  1. 登录成功后保存token和refreshToken
  2. 在响应拦截器中对401状态码引入刷新token的api方法调用
  3. 替换保存本地新的token
  4. 把错误对象里的token替换
  5. 再次发送未完成的请求
  6. 如果refreshToken过期了,判断是否过期,过期了就清楚所有的token重新登录
假设我们把请求用户信息时,携带token的代码注释掉

那么后端会因为我们未携带token而给我们返回错误信息

假设如下

  // 这里的mock插件得到的字段是authorization, 前端传递的是Authorization字段
  const { authorization = '' } = options.headers;

  const REFRESH_TOKEN_CODE = 66666;

  if (!authorization) {
    return {
      code: REFRESH_TOKEN_CODE,
      message: '用户已失效或不存在!',
      data: null
    };
  }

前端 拿到错误信息后 的 逻辑如下:

在响应拦截器中校验

 // token失效, 刷新token
  if (REFRESH_TOKEN_CODE.includes(backend[codeKey])) {
    // 重新刷新token
    const config = await handleRefreshToken(response.config);
    // 拿到新的config信息,里面包含了参数啊,地址啊,新的token
    if (config) {
      // 重新发送刚刚未完成的请求
      return this.instance.request(config);
    }
  }
// handleRefreshToken 的逻辑如下

import type { AxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/store';
import { localStg } from '@/utils';
import { fetchUpdateToken } from '../api';

/**
 * 刷新token
 * @param axiosConfig - token失效时的请求配置
 */
export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
  const { resetAuthStore } = useAuthStore();
  // 前面我们会把 token refreshToken 一起保存在本地
  const refreshToken = localStg.get('refreshToken') || '';
  
  // 请求刷新token
  const { data } = await fetchUpdateToken(refreshToken);
  
  // 重新 更新 token 和 refreshToken
  if (data) {
    localStg.set('token', data.token);
    localStg.set('refreshToken', data.refreshToken);
    
    // 复制一份token
    const config = { ...axiosConfig };
    // 更新请求头中的token
    if (config.headers) {
      config.headers.Authorization = data.token;
    }
    // 返回新的 config 信息
    return config;
  }

  resetAuthStore();
  return null;
}