Просмотр исходного кода

修复next分支因同步vue-next-admin覆盖的request.ts导致代码生成器生成的菜单无法正常请求的问题

caomei sun 3 лет назад
Родитель
Сommit
731dcaf334
1 измененных файлов с 164 добавлено и 25 удалено
  1. 164 25
      Web/src/utils/request.ts

+ 164 - 25
Web/src/utils/request.ts

@@ -1,20 +1,74 @@
-import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { Session } from '/@/utils/storage';
+import axios, { AxiosInstance } from 'axios';
+import { ElMessage } from 'element-plus';
+import { Local, Session } from '/@/utils/storage';
 
 // 配置新建一个 axios 实例
-const service: AxiosInstance = axios.create({
-	baseURL: import.meta.env.VITE_API_URL,
+export const service = axios.create({
+	baseURL: import.meta.env.VITE_API_URL as any,
 	timeout: 50000,
 	headers: { 'Content-Type': 'application/json' },
 });
 
+// token 键定义
+export const accessTokenKey = 'access-token';
+export const refreshAccessTokenKey = `x-${accessTokenKey}`;
+
+// 获取 token
+export const getToken = () => {
+	return Local.get(accessTokenKey);
+};
+
+// 清除 token
+export const clearAccessTokens = () => {
+	Local.remove(accessTokenKey);
+	Local.remove(refreshAccessTokenKey);
+
+	// 清除其他
+	Session.clear();
+
+	// 刷新浏览器
+	window.location.reload();
+};
+
+// axios 默认实例
+export const axiosInstance: AxiosInstance = axios;
+
 // 添加请求拦截器
 service.interceptors.request.use(
-	(config: AxiosRequestConfig) => {
-		// 在发送请求之前做些什么 token
-		if (Session.get('token')) {
-			config.headers!['Authorization'] = `${Session.get('token')}`;
+	(config) => {
+		// // 在发送请求之前做些什么 token
+		// if (Session.get('token')) {
+		// 	(<any>config.headers).common['Authorization'] = `${Session.get('token')}`;
+		// }
+
+		// 获取本地的 token
+		const accessToken = Local.get(accessTokenKey);
+		if (accessToken) {
+			// 将 token 添加到请求报文头中
+			config.headers!['Authorization'] = `Bearer ${accessToken}`;
+
+			// 判断 accessToken 是否过期
+			const jwt: any = decryptJWT(accessToken);
+			const exp = getJWTDate(jwt.exp as number);
+
+			// token 已经过期
+			if (new Date() >= exp) {
+				// 获取刷新 token
+				const refreshAccessToken = Local.get(refreshAccessTokenKey);
+
+				// 携带刷新 token
+				if (refreshAccessToken) {
+					config.headers!['X-Authorization'] = `Bearer ${refreshAccessToken}`;
+				}
+			}
+			// debugger
+			// get请求映射params参数
+			if (config.method?.toLowerCase() === 'get' && config.data) {
+				let url = config.url + '?' + tansParams(config.data);
+				url = url.slice(0, -1);
+				config.data = {};
+				config.url = url;
+			}
 		}
 		return config;
 	},
@@ -26,24 +80,61 @@ service.interceptors.request.use(
 
 // 添加响应拦截器
 service.interceptors.response.use(
-	(response) => {
-		// 对响应数据做点什么
-		const res = response.data;
-		if (res.code && res.code !== 0) {
-			// `token` 过期或者账号已在别处登录
-			if (res.code === 401 || res.code === 4001) {
-				Session.clear(); // 清除浏览器全部临时缓存
-				window.location.href = '/'; // 去登录页
-				ElMessageBox.alert('你已被登出,请重新登录', '提示', {})
-					.then(() => {})
-					.catch(() => {});
-			}
-			return Promise.reject(service.interceptors.response);
-		} else {
-			return response.data;
+	(res) => {
+		// 获取状态码和返回数据
+		var status = res.status;
+		var serve = res.data;
+
+		// 处理 401
+		if (status === 401) {
+			clearAccessTokens();
+		}
+
+		// 处理未进行规范化处理的
+		if (status >= 400) {
+			throw new Error(res.statusText || 'Request Error.');
+		}
+
+		// 处理规范化结果错误
+		if (serve && serve.hasOwnProperty('errors') && serve.errors) {
+			throw new Error(JSON.stringify(serve.errors || 'Request Error.'));
+		}
+
+		// 读取响应报文头 token 信息
+		var accessToken = res.headers[accessTokenKey];
+		var refreshAccessToken = res.headers[refreshAccessTokenKey];
+
+		// 判断是否是无效 token
+		if (accessToken === 'invalid_token') {
+			clearAccessTokens();
+		}
+		// 判断是否存在刷新 token,如果存在则存储在本地
+		else if (refreshAccessToken && accessToken && accessToken !== 'invalid_token') {
+			Local.set(accessTokenKey, accessToken);
+			Local.set(refreshAccessTokenKey, refreshAccessToken);
 		}
+
+		// 响应拦截及自定义处理
+		if (serve.code === 401) {
+			clearAccessTokens();
+		} else if (serve.code === undefined) {
+			return Promise.resolve(res);
+		} else if (serve.code !== 200) {
+			var message = JSON.stringify(serve.message);
+			ElMessage.error(message);
+			throw new Error(message);
+		}
+
+		return res;
 	},
 	(error) => {
+		// 处理响应错误
+		if (error.response) {
+			if (error.response.status === 401) {
+				clearAccessTokens();
+			}
+		}
+
 		// 对响应错误做点什么
 		if (error.message.indexOf('timeout') != -1) {
 			ElMessage.error('网络超时');
@@ -53,9 +144,57 @@ service.interceptors.response.use(
 			if (error.response.data) ElMessage.error(error.response.statusText);
 			else ElMessage.error('接口路径找不到');
 		}
+
 		return Promise.reject(error);
 	}
 );
 
+/**
+ *  参数处理
+ * @param {*} params  参数
+ */
+export function tansParams(params: any) {
+	let result = '';
+	for (const propName of Object.keys(params)) {
+		const value = params[propName];
+		var part = encodeURIComponent(propName) + '=';
+		if (value !== null && value !== '' && typeof value !== 'undefined') {
+			if (typeof value === 'object') {
+				for (const key of Object.keys(value)) {
+					if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
+						let params = propName + '[' + key + ']';
+						var subPart = encodeURIComponent(params) + '=';
+						result += subPart + encodeURIComponent(value[key]) + '&';
+					}
+				}
+			} else {
+				result += part + encodeURIComponent(value) + '&';
+			}
+		}
+	}
+	return result;
+}
+
+/**
+ * 解密 JWT token 的信息
+ * @param token jwt token 字符串
+ * @returns <any>object
+ */
+export function decryptJWT(token: string): any {
+	token = token.replace(/_/g, '/').replace(/-/g, '+');
+	var json = decodeURIComponent(escape(window.atob(token.split('.')[1])));
+	return JSON.parse(json);
+}
+
+/**
+ * 将 JWT 时间戳转换成 Date
+ * @description 主要针对 `exp`,`iat`,`nbf`
+ * @param timestamp 时间戳
+ * @returns Date 对象
+ */
+export function getJWTDate(timestamp: number): Date {
+	return new Date(timestamp * 1000);
+}
+
 // 导出 axios 实例
-export default service;
+export default service;