|
|
@@ -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');
|
|
|
};
|