diff --git a/apps/web-antd/src/api/request.ts b/apps/web-antd/src/api/request.ts index 288dddd09..070129b7f 100644 --- a/apps/web-antd/src/api/request.ts +++ b/apps/web-antd/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { RequestClientOptions } from '@vben/request'; +import type { RequestClientConfig, RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -30,18 +30,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } diff --git a/apps/web-antd/src/store/auth.ts b/apps/web-antd/src/store/auth.ts index 4b93784c8..4cc2bb30e 100644 --- a/apps/web-antd/src/store/auth.ts +++ b/apps/web-antd/src/store/auth.ts @@ -24,6 +24,7 @@ export const useAuthStore = defineStore('auth', () => { * 异步处理登录操作 * Asynchronously handle the login process * @param params 登录表单数据 + * @param onSuccess 登录成功回调 */ async function authLogin( params: Recordable, @@ -77,14 +78,26 @@ export const useAuthStore = defineStore('auth', () => { }; } - async function logout(redirect: boolean = true) { + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. + + async function logout(redirect: boolean = true, navigate: boolean = true) { + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); } catch { // 不做任何处理 + } finally { + accessStore.setAccessToken(null); + isLoggingOut.value = false; // 重置 标识 + resetAllStores(); + accessStore.setLoginExpired(false); + } + + if (!navigate) { + return; } - resetAllStores(); - accessStore.setLoginExpired(false); // 回登录页带上当前路由地址 await router.replace({ diff --git a/apps/web-antdv-next/src/api/request.ts b/apps/web-antdv-next/src/api/request.ts index d97a2163b..38f4e97fc 100644 --- a/apps/web-antdv-next/src/api/request.ts +++ b/apps/web-antdv-next/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { RequestClientOptions } from '@vben/request'; +import type { RequestClientConfig, RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -30,18 +30,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } diff --git a/apps/web-antdv-next/src/store/auth.ts b/apps/web-antdv-next/src/store/auth.ts index 90af35c8d..18a3b5a5d 100644 --- a/apps/web-antdv-next/src/store/auth.ts +++ b/apps/web-antdv-next/src/store/auth.ts @@ -24,6 +24,7 @@ export const useAuthStore = defineStore('auth', () => { * 异步处理登录操作 * Asynchronously handle the login process * @param params 登录表单数据 + * @param onSuccess 登录成功回调 */ async function authLogin( params: Recordable, @@ -77,14 +78,26 @@ export const useAuthStore = defineStore('auth', () => { }; } - async function logout(redirect: boolean = true) { + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. + + async function logout(redirect: boolean = true, navigate: boolean = true) { + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); } catch { // 不做任何处理 + } finally { + accessStore.setAccessToken(null); + isLoggingOut.value = false; // 重置 标识 + resetAllStores(); + accessStore.setLoginExpired(false); + } + + if (!navigate) { + return; } - resetAllStores(); - accessStore.setLoginExpired(false); // 回登录页带上当前路由地址 await router.replace({ diff --git a/apps/web-ele/src/api/request.ts b/apps/web-ele/src/api/request.ts index 203b35bf7..ebee57f21 100644 --- a/apps/web-ele/src/api/request.ts +++ b/apps/web-ele/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { RequestClientOptions } from '@vben/request'; +import type { RequestClientConfig, RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -30,18 +30,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } diff --git a/apps/web-ele/src/store/auth.ts b/apps/web-ele/src/store/auth.ts index bb37051ad..f24e41f9a 100644 --- a/apps/web-ele/src/store/auth.ts +++ b/apps/web-ele/src/store/auth.ts @@ -24,6 +24,7 @@ export const useAuthStore = defineStore('auth', () => { * 异步处理登录操作 * Asynchronously handle the login process * @param params 登录表单数据 + * @param onSuccess 登录成功回调 */ async function authLogin( params: Recordable, @@ -78,14 +79,26 @@ export const useAuthStore = defineStore('auth', () => { }; } - async function logout(redirect: boolean = true) { + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. + + async function logout(redirect: boolean = true, navigate: boolean = true) { + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); } catch { // 不做任何处理 + } finally { + accessStore.setAccessToken(null); + isLoggingOut.value = false; // 重置 标识 + resetAllStores(); + accessStore.setLoginExpired(false); + } + + if (!navigate) { + return; } - resetAllStores(); - accessStore.setLoginExpired(false); // 回登录页带上当前路由地址 await router.replace({ diff --git a/apps/web-naive/src/api/request.ts b/apps/web-naive/src/api/request.ts index f8fbacc0b..e6ff8cfbf 100644 --- a/apps/web-naive/src/api/request.ts +++ b/apps/web-naive/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { RequestClientOptions } from '@vben/request'; +import type { RequestClientConfig, RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -29,18 +29,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } diff --git a/apps/web-naive/src/store/auth.ts b/apps/web-naive/src/store/auth.ts index 5e5cf655b..4de035989 100644 --- a/apps/web-naive/src/store/auth.ts +++ b/apps/web-naive/src/store/auth.ts @@ -24,6 +24,7 @@ export const useAuthStore = defineStore('auth', () => { * 异步处理登录操作 * Asynchronously handle the login process * @param params 登录表单数据 + * @param onSuccess 登录成功回调 */ async function authLogin( params: Recordable, @@ -78,14 +79,26 @@ export const useAuthStore = defineStore('auth', () => { }; } - async function logout(redirect: boolean = true) { + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. + + async function logout(redirect: boolean = true, navigate: boolean = true) { + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); } catch { // 不做任何处理 + } finally { + accessStore.setAccessToken(null); + isLoggingOut.value = false; // 重置 标识 + resetAllStores(); + accessStore.setLoginExpired(false); + } + + if (!navigate) { + return; } - resetAllStores(); - accessStore.setLoginExpired(false); // 回登录页带上当前路由地址 await router.replace({ diff --git a/apps/web-tdesign/src/api/request.ts b/apps/web-tdesign/src/api/request.ts index 86b181d33..569b72108 100644 --- a/apps/web-tdesign/src/api/request.ts +++ b/apps/web-tdesign/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { RequestClientOptions } from '@vben/request'; +import type { RequestClientConfig, RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -29,18 +29,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } @@ -71,13 +72,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { }); // 处理返回的响应数据格式 - client.addResponseInterceptor( - defaultResponseInterceptor({ - codeField: 'code', - dataField: 'data', - successCode: 0, - }), - ); + client.addResponseInterceptor(defaultResponseInterceptor({})); // token过期的处理 client.addResponseInterceptor( @@ -96,7 +91,8 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg // 当前mock接口返回的错误字段是 error 或者 message const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; + const errorMessage = + responseData?.error ?? responseData?.message ?? responseData?.msg ?? ''; // 如果没有错误信息,则会根据状态码进行提示 message.error(errorMessage || msg); }), diff --git a/apps/web-tdesign/src/store/auth.ts b/apps/web-tdesign/src/store/auth.ts index 7f8d8f9b2..d48ed5a8d 100644 --- a/apps/web-tdesign/src/store/auth.ts +++ b/apps/web-tdesign/src/store/auth.ts @@ -76,14 +76,26 @@ export const useAuthStore = defineStore('auth', () => { }; } - async function logout(redirect: boolean = true) { + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. + + async function logout(redirect: boolean = true, navigate: boolean = true) { + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); } catch { // 不做任何处理 + } finally { + accessStore.setAccessToken(null); + isLoggingOut.value = false; // 重置 标识 + resetAllStores(); + accessStore.setLoginExpired(false); + } + + if (!navigate) { + return; } - resetAllStores(); - accessStore.setLoginExpired(false); // 回登录页带上当前路由地址 await router.replace({ diff --git a/packages/effects/request/src/request-client/preset-interceptors.ts b/packages/effects/request/src/request-client/preset-interceptors.ts index c7bec3711..c15971ebe 100644 --- a/packages/effects/request/src/request-client/preset-interceptors.ts +++ b/packages/effects/request/src/request-client/preset-interceptors.ts @@ -1,5 +1,9 @@ import type { RequestClient } from './request-client'; -import type { MakeErrorMessageFn, ResponseInterceptorConfig } from './types'; +import type { + MakeErrorMessageFn, + RequestClientConfig, + ResponseInterceptorConfig, +} from './types'; import { $t } from '@vben/locales'; import { isFunction } from '@vben/utils'; @@ -52,7 +56,7 @@ export const authenticateResponseInterceptor = ({ formatToken, }: { client: RequestClient; - doReAuthenticate: () => Promise; + doReAuthenticate: (config?: RequestClientConfig) => Promise; doRefreshToken: () => Promise; enableRefreshToken: boolean; formatToken: (token: string) => null | string; @@ -67,7 +71,7 @@ export const authenticateResponseInterceptor = ({ // 判断是否启用了 refreshToken 功能 // 如果没有启用或者已经是重试请求了,直接跳转到重新登录 if (!enableRefreshToken || config.__isRetryRequest) { - await doReAuthenticate(); + await doReAuthenticate(config); throw error; } // 如果正在刷新 token,则将请求加入队列,等待刷新完成 @@ -99,7 +103,7 @@ export const authenticateResponseInterceptor = ({ client.refreshTokenQueue.forEach((callback) => callback('')); client.refreshTokenQueue = []; console.error('Refresh token failed, please login again.'); - await doReAuthenticate(); + await doReAuthenticate(config); throw refreshError; } finally { @@ -118,6 +122,11 @@ export const errorMessageResponseInterceptor = ( return Promise.reject(error); } + // 如果配置了错误消息提示模式为 none,则跳过错误提示 + if (error?.config?.errorMessageMode === 'none') { + return Promise.reject(error); + } + const err: string = error?.toString?.() ?? ''; let errMsg = ''; if (err?.includes('Network Error')) { diff --git a/packages/effects/request/src/request-client/types.ts b/packages/effects/request/src/request-client/types.ts index d40ee8a5e..d4e710594 100644 --- a/packages/effects/request/src/request-client/types.ts +++ b/packages/effects/request/src/request-client/types.ts @@ -26,6 +26,18 @@ type ExtendOptions = { * - data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。 */ responseReturn?: 'body' | 'data' | 'raw'; + /** + * 认证过期时是否跳过自动跳转登录页。 + * - true: 仅清除登录态,不跳转 + * - false / 不传: 清除登录态并跳转登录页 + */ + skipAuthNavigate?: boolean; + /** + * 错误消息提示模式。 + * - message: 使用 message 提示(默认) + * - none: 不显示错误提示 + */ + errorMessageMode?: 'message' | 'none'; }; type RequestClientConfig = AxiosRequestConfig & ExtendOptions; diff --git a/playground/src/api/request.ts b/playground/src/api/request.ts index 3aebcce67..3795e3a50 100644 --- a/playground/src/api/request.ts +++ b/playground/src/api/request.ts @@ -1,7 +1,11 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { AxiosResponseHeaders, RequestClientOptions } from '@vben/request'; +import type { + AxiosResponseHeaders, + RequestClientConfig, + RequestClientOptions, +} from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -44,18 +48,19 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { /** * 重新认证逻辑 */ - async function doReAuthenticate() { + async function doReAuthenticate(config?: RequestClientConfig) { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); - accessStore.setAccessToken(null); + const navigate = !config?.skipAuthNavigate; if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { + accessStore.setAccessToken(null); accessStore.setLoginExpired(true); } else { - await authStore.logout(); + await authStore.logout(true, navigate); } } diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts index 251bbcb82..cf4b8065d 100644 --- a/playground/src/store/auth.ts +++ b/playground/src/store/auth.ts @@ -79,7 +79,7 @@ export const useAuthStore = defineStore('auth', () => { const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. - async function logout(redirect: boolean = true) { + async function logout(redirect: boolean = true, navigate: boolean = true) { if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. isLoggingOut.value = true; // 设置 标识 @@ -88,12 +88,16 @@ export const useAuthStore = defineStore('auth', () => { } catch { // 不做任何处理 } finally { + accessStore.setAccessToken(null); isLoggingOut.value = false; // 重置 标识 - resetAllStores(); accessStore.setLoginExpired(false); } + if (!navigate) { + return; + } + // 回登录页带上当前路由地址 await router.replace({ path: LOGIN_PATH,