task-26(会话/菜单/权限): 实现 401 登录跳转行为防循环守卫
401 统一跳 /login 并携带目标路径;新增纯函数守卫 isLoginLocation/ isAuthEndpointRequest/shouldRedirectUnauthorized:已处登录页或失败请求即 登录端点时不再二次跳转,避免登录页死循环与吞掉登录失败反馈。http 层 success/error 两支均带失败请求 URL 过守卫后再跳转。envelope/http 单测 8 用例 + 全量 179 单测 + vue-tsc/vite build 通过。
This commit is contained in:
@@ -60,3 +60,36 @@ export function requestErrorMessage(error: unknown): string {
|
||||
export function loginRedirectTarget(location: { pathname: string; search: string }): string {
|
||||
return encodeURIComponent(`${location.pathname}${location.search}`)
|
||||
}
|
||||
|
||||
/** 登录页统一入口;401 一律跳此路径。 */
|
||||
export const LOGIN_PATH = '/login'
|
||||
|
||||
/** 当前 pathname 是否处于登录页(去掉可能携带的查询串后比较)。 */
|
||||
export function isLoginLocation(pathname: string): boolean {
|
||||
if (typeof pathname !== 'string') return false
|
||||
const queryAt = pathname.indexOf('?')
|
||||
return (queryAt >= 0 ? pathname.slice(0, queryAt) : pathname) === LOGIN_PATH
|
||||
}
|
||||
|
||||
/** 把请求 URL(相对或绝对)规整为相对路径,便于与登录入口比较。 */
|
||||
export function requestRelativePath(url: string): string {
|
||||
if (typeof url !== 'string') return ''
|
||||
const noQuery = url.split('?')[0]
|
||||
const match = noQuery.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/[^/]+(.*)$/)
|
||||
return match ? match[1] : noQuery
|
||||
}
|
||||
|
||||
/** 401 失败请求本身是否指向登录/认证端点(登录提交失败不应再被踢回登录页)。 */
|
||||
export function isAuthEndpointRequest(url: string | undefined): boolean {
|
||||
return requestRelativePath(url || '') === LOGIN_PATH
|
||||
}
|
||||
|
||||
/**
|
||||
* 401 是否应执行“跳 /login”:已处于登录页或失败请求即登录端点时不应再跳,
|
||||
* 否则会在登录页循环跳转或吞掉登录失败反馈。
|
||||
*/
|
||||
export function shouldRedirectUnauthorized(currentPathname: string, requestUrl?: string): boolean {
|
||||
if (isLoginLocation(currentPathname)) return false
|
||||
if (isAuthEndpointRequest(requestUrl)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user