环境配置
// 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的工作流程如下:
- 登录成功后保存token和refreshToken
- 在响应拦截器中对401状态码引入刷新token的api方法调用
- 替换保存本地新的token
- 把错误对象里的token替换
- 再次发送未完成的请求
- 如果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;
}