首先打开公司的项目,由于公司前端使用的是tarojs+vue+babel,因此需要先安装tarojs-cli并运行编译
之后研究前端前辈们的代码,来学习一下前端怎么获取token并全局使用
首先思路搞清楚,token是通过调取后端的登录接口,并在后端成功返回后拿到并全局使用的,基于这个思路,我们先看看调用login函数和定义login函数的地方
调用login函数位于src\pages\common\login\index.vue的:
// 调用 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的:
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函数:
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里定义的:
// 定义 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) {// 设置登录状态为 truestate.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 属性为登录后的 accessTokenstate.token = obj.accessToken;},
};
在这里可以看到,state.token在init mutation被赋值一次,在login mutation 函数中被obj.accessToken赋值一次。
之后来到src\request\http.ts,查看配置 HTTP 请求拦截器,因为在发送请求之前,拦截器会对请求进行一些预处理操作(比如说把token传入请求头)
http.interceptors.request = (request) => {// 如果 store 的 getters 中有 tokenif (store.getters.token) {// 将环境变量 TOKEN_NAME 作为键名,把 store 中的 token 值设置到请求头中request.header[process.env.TOKEN_NAME as string] = store.getters.token;}// 设置请求头的 Content-Type 为 application/jsonrequest.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定义代码
getters: {token: (state) => {// 从本地存储中获取存储的过期时间,并转换为浮点数const expirationTime = parseFloat(useStorage().getStorage(STORE_EXPIRATION));// 如果过期时间存在且当前时间小于过期时间if (expirationTime && new Date().getTime() < expirationTime) {// 返回 state 中的 tokenreturn state.token;}// 如果不满足上述条件,返回空字符串return "";},
},
这样就可以在 HTTP 请求拦截器中使用store.getters.token,从而实现每次请求,都来实时判断token是否过期,获取token来全局控制用户登录情况了