|
|
@@ -19,6 +19,30 @@ import { isArray } from '@/utils/validate'
|
|
|
|
|
|
let loadingInstance
|
|
|
|
|
|
+// ===== 防止登录失效时并发请求导致的重复弹窗/重复跳转/刷新死循环 =====
|
|
|
+let __authRedirecting = false
|
|
|
+let __authRedirectingAt = 0
|
|
|
+const AUTH_REDIRECT_COOLDOWN_MS = 3000
|
|
|
+
|
|
|
+const beginAuthRedirectOnce = () => {
|
|
|
+ const now = Date.now()
|
|
|
+ if (
|
|
|
+ __authRedirecting &&
|
|
|
+ now - __authRedirectingAt < AUTH_REDIRECT_COOLDOWN_MS
|
|
|
+ )
|
|
|
+ return false
|
|
|
+ __authRedirecting = true
|
|
|
+ __authRedirectingAt = now
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const endAuthRedirectLater = () => {
|
|
|
+ // 给路由跳转/整页跳转留一点时间,避免立即被后续请求打断
|
|
|
+ setTimeout(() => {
|
|
|
+ __authRedirecting = false
|
|
|
+ }, AUTH_REDIRECT_COOLDOWN_MS)
|
|
|
+}
|
|
|
+
|
|
|
// ensure custom style for scrollable error message box exists
|
|
|
const ensureScrollableBoxStyle = () => {
|
|
|
if (typeof document === 'undefined') return
|
|
|
@@ -63,7 +87,10 @@ const ensureScrollableBoxStyle = () => {
|
|
|
*/
|
|
|
const handleCode = (code, msg) => {
|
|
|
switch (code) {
|
|
|
- case invalidCode:
|
|
|
+ case invalidCode: {
|
|
|
+ // token/会话失效:只允许触发一次清理与跳转,避免并发请求造成循环跳转/刷新
|
|
|
+ const canRedirect = beginAuthRedirectOnce()
|
|
|
+
|
|
|
ensureScrollableBoxStyle()
|
|
|
MessageBox.alert(
|
|
|
`<div class="scrollable-error-content">${
|
|
|
@@ -78,37 +105,52 @@ const handleCode = (code, msg) => {
|
|
|
center: false,
|
|
|
}
|
|
|
)
|
|
|
- store.dispatch('user/resetAccessToken')
|
|
|
- if (loginInterception) {
|
|
|
- try {
|
|
|
- const fullPath = router.currentRoute.fullPath
|
|
|
- if (recordRoute && fullPath) {
|
|
|
- router.push(`/login?redirect=${fullPath}`)
|
|
|
- } else {
|
|
|
- router.push('/login')
|
|
|
+
|
|
|
+ if (canRedirect) {
|
|
|
+ store.dispatch('user/resetAccessToken')
|
|
|
+ if (loginInterception) {
|
|
|
+ try {
|
|
|
+ const fullPath = router.currentRoute.fullPath
|
|
|
+ if (recordRoute && fullPath) {
|
|
|
+ router.replace(`/login?redirect=${fullPath}`)
|
|
|
+ } else {
|
|
|
+ router.replace('/login')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ // 兜底:直接跳转登录页
|
|
|
+ window.location.href = '/login'
|
|
|
+ } finally {
|
|
|
+ endAuthRedirectLater()
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- // 兜底:直接跳转登录页
|
|
|
- window.location.href = '/login'
|
|
|
+ } else {
|
|
|
+ endAuthRedirectLater()
|
|
|
}
|
|
|
}
|
|
|
break
|
|
|
- case noPermissionCode:
|
|
|
+ }
|
|
|
+ case noPermissionCode: {
|
|
|
+ const canRedirect = beginAuthRedirectOnce()
|
|
|
+ if (!canRedirect) break
|
|
|
+
|
|
|
store
|
|
|
.dispatch('user/logout')
|
|
|
.then(() => {
|
|
|
if (recordRoute) {
|
|
|
const fullPath = router.currentRoute.fullPath
|
|
|
- router.push(`/login?redirect=${fullPath}`)
|
|
|
+ router.replace(`/login?redirect=${fullPath}`)
|
|
|
} else {
|
|
|
- router.push('/login')
|
|
|
+ router.replace('/login')
|
|
|
}
|
|
|
})
|
|
|
.catch(() => {
|
|
|
// logout时可能因为token过期报401错误,此时用href跳转到login页面
|
|
|
window.location.href = '/login'
|
|
|
})
|
|
|
+ .finally(() => {
|
|
|
+ endAuthRedirectLater()
|
|
|
+ })
|
|
|
break
|
|
|
+ }
|
|
|
default:
|
|
|
if (Number(code) === 500) {
|
|
|
// 500 保持原来的轻提示样式
|
|
|
@@ -236,8 +278,15 @@ instance.interceptors.response.use(
|
|
|
const { response, message } = error
|
|
|
if (error.response && error.response.data) {
|
|
|
const { status, data } = response
|
|
|
- //data.msg
|
|
|
- handleCode(status, data.message || message)
|
|
|
+
|
|
|
+ // 避免并发401/会话失效时重复弹窗、重复跳转
|
|
|
+ // 注意:后端可能直接返回 HTTP 401/403,而不是业务 code
|
|
|
+ if (status === 401 || status === invalidCode) {
|
|
|
+ handleCode(invalidCode, data.message || message)
|
|
|
+ } else {
|
|
|
+ handleCode(status, data.message || message)
|
|
|
+ }
|
|
|
+
|
|
|
return Promise.reject(error)
|
|
|
} else {
|
|
|
let { message } = error
|