Преглед на файлове

feat: 修复刷新token失败时导致排队的请求全部挂起的bug(刷新失败时应该全部reject)

BaiLuoYan преди 4 дни
родител
ревизия
051c9c5fb9
променени са 1 файла, в които са добавени 50 реда и са изтрити 47 реда
  1. 50 47
      src/requestConfig/authHeaderInterceptor.ts

+ 50 - 47
src/requestConfig/authHeaderInterceptor.ts

@@ -4,51 +4,54 @@ import { stringMd5, stringSha1 } from '@/utils/crypto';
 import { currentUnixTimestamp } from '@/utils/timeUtils';
 import { RequestConfig, RequestInterceptorAxios } from '@umijs/max';
 
-class TokenRefresh {
-  private static isRefreshing: boolean = false;
-  private static requests: { (token: string): void }[] = [];
+type ResolveCallback = (token: string) => void;
+type RejectCallback = (err: Error) => void;
 
-  private static retryOriginalRequest(config: RequestConfig) {
-    return new Promise((resolve) => {
-      TokenRefresh.requests.push((accessToken: string) => {
-        config.headers = config.headers ?? {};
-        config.headers['Authorization'] = formatToken(accessToken);
-        resolve(config);
+const pendingRequests: { resolve: ResolveCallback; reject: RejectCallback }[] = [];
+let isRefreshing = false;
+
+const flushPending = (accessToken: string) => {
+  pendingRequests.forEach(({ resolve }) => resolve(accessToken));
+  pendingRequests.length = 0;
+};
+
+const rejectPending = (err: Error) => {
+  pendingRequests.forEach(({ reject }) => reject(err));
+  pendingRequests.length = 0;
+};
+
+const enqueueRequest = (config: RequestConfig): Promise<RequestConfig> =>
+  new Promise<string>((resolve, reject) => {
+    pendingRequests.push({ resolve, reject });
+  }).then((accessToken) => {
+    config.headers = config.headers ?? {};
+    config.headers['Authorization'] = formatToken(accessToken);
+    return config;
+  });
+
+const refreshAndRetry = (config: RequestConfig, refreshToken: string): Promise<RequestConfig> => {
+  const queued = enqueueRequest(config);
+
+  if (!isRefreshing) {
+    isRefreshing = true;
+    fetchRefreshToken({ headers: { Authorization: formatToken(refreshToken) } })
+      .then(
+        (res: API.RefreshTokenResult) => {
+          const { accessToken, expires, refreshToken: newRefreshToken } = res.data!;
+          setToken({ accessToken, expires, refreshToken: newRefreshToken });
+          flushPending(accessToken);
+        },
+        (err: Error) => {
+          rejectPending(err);
+        },
+      )
+      .finally(() => {
+        isRefreshing = false;
       });
-    });
   }
 
-  public static beforeRequestRefreshTokenFirst(config: RequestConfig, refreshToken: string) {
-    return new Promise((resolve, reject) => {
-      if (!TokenRefresh.isRefreshing) {
-        TokenRefresh.isRefreshing = true;
-        fetchRefreshToken({ headers: { Authorization: formatToken(refreshToken) } })
-          .then(
-            (res: API.RefreshTokenResult) => {
-              const info = res.data as API.UserInfo;
-              const accessToken = info.accessToken!;
-              setToken({
-                accessToken: accessToken,
-                expires: info.expires,
-                refreshToken: info.refreshToken,
-              });
-              config.headers = config.headers ?? {};
-              config.headers['Authorization'] = formatToken(accessToken);
-              TokenRefresh.requests.forEach((cb) => cb(accessToken));
-              TokenRefresh.requests = [];
-            },
-            (err: Error) => {
-              reject(err);
-            },
-          )
-          .finally(() => {
-            TokenRefresh.isRefreshing = false;
-          });
-      }
-      resolve(TokenRefresh.retryOriginalRequest(config));
-    });
-  }
-}
+  return queued;
+};
 
 export const authHeaderInterceptor: RequestInterceptorAxios = (config: RequestConfig) => {
   config.headers = config.headers ?? {};
@@ -61,15 +64,15 @@ export const authHeaderInterceptor: RequestInterceptorAxios = (config: RequestCo
     config.headers['X-Request-Timestamp'] = ts;
   }
   config.headers['X-Request-Sign'] = stringSha1(String(ts) + stringMd5(navigator.userAgent ?? ''));
-  if (config.requireToken === false) return config; // 默认所有接口都需要 token,如果接口不需要 token,需要显示设置 requireToken 为 false
-  // if (config.requireToken !== true) return config; // 默认所有接口都不需要 token,如果接口需要 token,需要显示设置 requireToken 为 true
+
+  if (config.requireToken === false) return config;
 
   const data = getToken();
-  const expires = (data?.expires ?? 0) - currentUnixTimestamp() > 0;
-  if (data?.accessToken && expires) {
+  const isValid = (data?.expires ?? 0) - currentUnixTimestamp() > 0;
+  if (data?.accessToken && isValid) {
     config.headers['Authorization'] = formatToken(data.accessToken);
     return config;
-  } else {
-    return TokenRefresh.beforeRequestRefreshTokenFirst(config, data?.refreshToken || 'none'); // 如果 refreshToken 为空,接口会返回 401 错误,跳转到登录页
   }
+
+  return refreshAndRetry(config, data?.refreshToken || 'none');
 };