fix(auth): 修复令牌过期处理和会话失效通知机制
- 移除 VITE_SERVICE_LOGOUT_CODES 中的 1002023000 状态码 - 将 VITE_SERVICE_EXPIRED_TOKEN_CODES 从 1002023001 改为 1002023000 - 修改 fetchRefreshToken 函数使用 params 传递 refreshToken 并设置 skipAuth - 添加 skipAuth 配置选项避免给公开接口带上过期 access 头 - 实现 notifySessionExpired 函数确保并发请求只弹一次会话失效提示 - 在登录成功后复位会话失效标志以支持下次正常提示 - 更新 handleExpiredRequest 使用 refreshTokenPromise 替代 refreshTokenFn
This commit is contained in:
@@ -114,7 +114,9 @@ export async function fetchRefreshToken(refreshToken: string): Promise<ServiceRe
|
||||
method: 'post',
|
||||
params: { refreshToken },
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
skipAuth: true
|
||||
skipAuth: true,
|
||||
suppressErrorMessage: true,
|
||||
skipTokenRefresh: true
|
||||
});
|
||||
|
||||
if (result.error || !result.data) {
|
||||
|
||||
@@ -10,6 +10,10 @@ declare module 'axios' {
|
||||
* 避免给它们带上过期 access 头被网关拦截。
|
||||
*/
|
||||
skipAuth?: boolean;
|
||||
/** 请求失败时不走通用错误 toast,由调用方自行收敛提示。 */
|
||||
suppressErrorMessage?: boolean;
|
||||
/** 请求失败命中过期 access code 时,不再触发 refresh-token 流程。 */
|
||||
skipTokenRefresh?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
32
src/service/request/error-message.ts
Normal file
32
src/service/request/error-message.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export const SESSION_EXPIRED_MESSAGE = '登录已失效,请重新登录';
|
||||
|
||||
export interface ErrorMessageSuppressOptions {
|
||||
backendErrorCode: string;
|
||||
suppressErrorMessage?: boolean;
|
||||
logoutCodes: string[];
|
||||
modalLogoutCodes: string[];
|
||||
expiredTokenCodes: string[];
|
||||
}
|
||||
|
||||
export interface BackendFailDeferOptions {
|
||||
suppressErrorMessage?: boolean;
|
||||
skipTokenRefresh?: boolean;
|
||||
}
|
||||
|
||||
export function parseServiceCodes(codes?: string) {
|
||||
return codes?.split(',').filter(Boolean) || [];
|
||||
}
|
||||
|
||||
export function shouldDeferBackendFailToCaller(options: BackendFailDeferOptions) {
|
||||
return Boolean(options.suppressErrorMessage && options.skipTokenRefresh);
|
||||
}
|
||||
|
||||
export function shouldSuppressErrorMessage(options: ErrorMessageSuppressOptions) {
|
||||
if (options.suppressErrorMessage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const handledCodes = [...options.logoutCodes, ...options.modalLogoutCodes, ...options.expiredTokenCodes];
|
||||
|
||||
return handledCodes.includes(options.backendErrorCode);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { localStg } from '@/utils/storage';
|
||||
import { getServiceBaseURL } from '@/utils/service';
|
||||
import { $t } from '@/locales';
|
||||
import { applyApiEncrypt } from './api-encrypt';
|
||||
import { parseServiceCodes, shouldDeferBackendFailToCaller, shouldSuppressErrorMessage } from './error-message';
|
||||
import { getAuthorization, handleExpiredRequest, notifySessionExpired, showErrorMsg } from './shared';
|
||||
import { withDedupe } from './dedupe';
|
||||
import type { RequestInstanceState } from './type';
|
||||
@@ -48,6 +49,15 @@ export const request = withDedupe(
|
||||
const authStore = useAuthStore();
|
||||
const responseCode = String(response.data.code);
|
||||
|
||||
if (
|
||||
shouldDeferBackendFailToCaller({
|
||||
suppressErrorMessage: response.config.suppressErrorMessage,
|
||||
skipTokenRefresh: response.config.skipTokenRefresh
|
||||
})
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
authStore.resetStore();
|
||||
}
|
||||
@@ -61,14 +71,14 @@ export const request = withDedupe(
|
||||
|
||||
// 当后端返回码命中 `logoutCodes` 时,表示登录态已失效,需要提示后退出登录
|
||||
// 走 notifySessionExpired 而不是裸 resetStore:保证并发请求只弹一次 toast、只清一次状态
|
||||
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
|
||||
const logoutCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_LOGOUT_CODES);
|
||||
if (logoutCodes.includes(responseCode)) {
|
||||
notifySessionExpired();
|
||||
return null;
|
||||
}
|
||||
|
||||
// 当后端返回码命中 `modalLogoutCodes` 时,表示通过弹窗提示后再退出登录
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
const modalLogoutCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES);
|
||||
if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
|
||||
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
|
||||
|
||||
@@ -92,8 +102,13 @@ export const request = withDedupe(
|
||||
|
||||
// 当后端返回码命中 `expiredTokenCodes` 时,表示 token 已过期,需要刷新 token
|
||||
// `refreshToken` 接口不能再返回 `expiredTokenCodes` 中的错误码,否则会形成死循环,应返回 `logoutCodes` 或 `modalLogoutCodes`
|
||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
||||
const expiredTokenCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES);
|
||||
if (expiredTokenCodes.includes(responseCode)) {
|
||||
if (response.config.skipTokenRefresh) {
|
||||
notifySessionExpired();
|
||||
return null;
|
||||
}
|
||||
|
||||
const success = await handleExpiredRequest(request.state);
|
||||
if (success) {
|
||||
const Authorization = getAuthorization();
|
||||
@@ -117,15 +132,19 @@ export const request = withDedupe(
|
||||
backendErrorCode = String(error.response?.data?.code || '');
|
||||
}
|
||||
|
||||
// 这类错误信息已经通过弹窗展示,不再重复提示
|
||||
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
|
||||
if (modalLogoutCodes.includes(backendErrorCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// token 过期时会自动刷新并重试请求,这里无需额外提示
|
||||
const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
|
||||
if (expiredTokenCodes.includes(backendErrorCode)) {
|
||||
const suppressErrorMessage = Boolean(error.config?.suppressErrorMessage);
|
||||
const logoutCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_LOGOUT_CODES);
|
||||
const modalLogoutCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES);
|
||||
const expiredTokenCodes = parseServiceCodes(import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES);
|
||||
if (
|
||||
shouldSuppressErrorMessage({
|
||||
backendErrorCode,
|
||||
suppressErrorMessage,
|
||||
logoutCodes,
|
||||
modalLogoutCodes,
|
||||
expiredTokenCodes
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAuthStore } from '@/store/modules/auth';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { fetchRefreshToken } from '../api';
|
||||
import { SESSION_EXPIRED_MESSAGE } from './error-message';
|
||||
import type { RequestInstanceState } from './type';
|
||||
|
||||
export function getAuthorization() {
|
||||
@@ -51,7 +52,7 @@ export function notifySessionExpired() {
|
||||
if (sessionExpiredNotified) return;
|
||||
sessionExpiredNotified = true;
|
||||
|
||||
window.$message?.error('登录已过期,请重新登录');
|
||||
window.$message?.error(SESSION_EXPIRED_MESSAGE);
|
||||
|
||||
const { resetStore } = useAuthStore();
|
||||
resetStore();
|
||||
|
||||
Reference in New Issue
Block a user