首先打开公司的项目,由于公司前端使用的是tarojs+vue+babel,因此需要先安装tarojs-cli并运行编译
之后研究前端前辈们的代码,来学习一下前端怎么获取token并全局使用
首先思路搞清楚,token是通过调取后端的登录接口,并在后端成功返回后拿到并全局使用的,基于这个思路,我们先看看调用login函数和定义login函数的地方
调用login函数位于src\pages\common\login\index.vue的:
javascript
// 调用 useService('Auth')获取一个与认证相关的服务对象,然后调用其 login 方法进行登录操作,
// 传入包含 code、encryptedData 和 iv 的对象作为登录参数,并等待这个异步操作完成
if (await useService('Auth').login({
code: wxCode.value,
encryptedData: detail.encryptedData,
iv: detail.iv
})) {
// 如果登录成功且 eventId.value 有值,则触发名为 eventId.value(登录成功) 的事件
eventId.value && eventCenter.trigger(eventId.value);
// 如果登录成功且 eventId.value 没有值,则执行返回上一页的操作
!eventId.value && useNavigateBack();
} else {
// 如果登录失败,执行返回上一页的操作
useNavigateBack();
}
这里用到了useService('Auth'),而useService函数位于src\service\useService.ts的:
javascript
export default function useService(serviceName: string) {
//获取了被加载文件的默认导出(假设 serviceName 指向的文件中有一个默认导出的类)。
const ctor = require('./' + serviceName).default
//返回了一个新的实例化对象,这样调用方就可以使用这个对象上的方法,比如 .login 方法。
return new ctor()
}
/**
useService('Auth'):当传入 'Auth' 作为参数时,
useService函数会尝试加载 ./Auth,也就是相对路径下名为 Auth.ts 的文件。
由于有 src/service/Auth.ts 文件存在,
并且假设这个文件在当前上下文的相对路径符合 useService 的加载规则,
那么这个调用就会加载 src/service/Auth.ts 文件。
**/
由此可知useService('Auth').login是通过useService函数导出了Auth类对象,并调用其login函数。
我们找到位于src\service\Auth.ts的login函数:
javascript
async login(args: AuthPhoneNumberProps) {
// 移除名为 "USER_LOGOUT" 的存储项
this.useStorage.removeStorage("USER_LOGOUT");
// 创建一个加载提示,显示标题为"正在登录...",并在变量 hideLoading 中保存用于关闭提示的函数
const hideLoading = useLoading({ title: '正在登录...' });
// 调用 this.model.login 方法进行登录操作,将传入的参数 args 展开传递,并解构赋值结果为 [error, response]
const [error, { data }] = await this.model.login({...args });
// 如果有错误发生
if (error) {
// 触发名为 "refreshWxCode" 的事件
eventCenter.trigger('refreshWxCode');
// 关闭加载提示
hideLoading();
// 直接返回,不继续执行后续代码
return;
}
// 将登录成功后获取的数据提交到 store 的 login mutation 中
store.commit('login', data);
// 调用名为 "Classes" 的服务获取活跃班级信息
await useService('Classes').fetchActiveClassInfo();
// 关闭加载提示
hideLoading();
// 返回 true,表示登录成功并执行了后续操作
return true;
}
发现该函数调用store.commit('login', data),这里的data是从后端获取的登录响应数据。
当store.commit('login', data)被调用时,实际上会执行login mutation 函数。
而这个login mutation 函数则是在src\store\index.ts中mutations里定义的:
javascript
// 定义 Vuex 的 mutations 对象,用于更改状态
mutations: {
// init 方法,用于初始化状态
init(state) {
// 从本地存储中获取用户信息并设置到 Vuex 的 state 中的 user 属性
state.user = useStorage().getStorage(STORE_USER);
// 从本地存储中获取 token 并设置到 Vuex 的 state 中的 token 属性
state.token = useStorage().getStorage(STORE_TOKEN);
},
// login 方法,用于处理登录成功后的状态更新
login(state, obj) {
// 设置登录状态为 true
state.isLoginState = true;
// 将登录后的 accessToken 存储到本地存储中,并以 STORE_TOKEN 为键
useStorage().setStorage(STORE_TOKEN, obj.accessToken);
// 将登录后的用户信息存储到本地存储中,并以 STORE_USER 为键
useStorage().setStorage(STORE_USER, obj.user);
// 计算并设置登录过期时间到本地存储中,以 STORE_EXPIRATION 为键
useStorage().setStorage(STORE_EXPIRATION, new Date().getTime() + (LOGIN_TIME || 720) * 60 * 1000);
// 更新 Vuex 的 state 中的 user 属性为登录后的用户信息对象的扩展形式
state.user = {...obj.user };
// 更新 Vuex 的 state 中的 token 属性为登录后的 accessToken
state.token = obj.accessToken;
},
};
在这里可以看到,state.token在init mutation被赋值一次,在login mutation 函数中被obj.accessToken赋值一次。
之后来到src\request\http.ts,查看配置 HTTP 请求拦截器,因为在发送请求之前,拦截器会对请求进行一些预处理操作(比如说把token传入请求头)
javascript
http.interceptors.request = (request) => {
// 如果 store 的 getters 中有 token
if (store.getters.token) {
// 将环境变量 TOKEN_NAME 作为键名,把 store 中的 token 值设置到请求头中
request.header[process.env.TOKEN_NAME as string] = store.getters.token;
}
// 设置请求头的 Content-Type 为 application/json
request.header['content-type'] = 'application/json';
// 返回修改后的请求配置对象
return request;
};
这里用到了store.getters.token,但是赋值的时候明明是赋值给state.token,这两者之间有什么关系呢?
这里查到了他们的关系,state是原始状态数据的存储,而store.getters是基于state计算得到的派生状态,它们共同构成了 Vuex 状态管理中的重要组成部分,为应用的组件提供了可预测、可维护的数据访问方式。而如果没有明确为token定义一个 getter,那么store.getters.token不会自动等同于state.token。所以基于这个角度考虑,我们需要去查token是怎么定义 getter的
回到src\store\index.ts中,发现token的 getter定义代码
javascript
getters: {
token: (state) => {
// 从本地存储中获取存储的过期时间,并转换为浮点数
const expirationTime = parseFloat(useStorage().getStorage(STORE_EXPIRATION));
// 如果过期时间存在且当前时间小于过期时间
if (expirationTime && new Date().getTime() < expirationTime) {
// 返回 state 中的 token
return state.token;
}
// 如果不满足上述条件,返回空字符串
return "";
},
},
这样就可以在 HTTP 请求拦截器中使用store.getters.token,从而实现每次请求,都来实时判断token是否过期,获取token来全局控制用户登录情况了