系统静默跳转至登录页面的优化
parent
f72fb86c16
commit
b25d77c63c
|
|
@ -1,6 +1,7 @@
|
|||
package com.unis.crm.controller;
|
||||
|
||||
import com.unis.crm.common.ApiResponse;
|
||||
import com.unis.crm.common.CurrentUserUtils;
|
||||
import com.unis.crm.dto.dashboard.DashboardAnalyticsCardDTO;
|
||||
import com.unis.crm.dto.dashboard.DashboardHomeDTO;
|
||||
import com.unis.crm.service.DashboardService;
|
||||
|
|
@ -29,7 +30,7 @@ public class DashboardController {
|
|||
@GetMapping("/home")
|
||||
public ApiResponse<DashboardHomeDTO> getHome(
|
||||
@RequestHeader("X-User-Id") @Min(1) Long userId) {
|
||||
return ApiResponse.success(dashboardService.getHome(userId));
|
||||
return ApiResponse.success(dashboardService.getHome(CurrentUserUtils.requireCurrentUserId(userId)));
|
||||
}
|
||||
|
||||
@PostMapping("/todos/{todoId}/complete")
|
||||
|
|
@ -37,7 +38,7 @@ public class DashboardController {
|
|||
public ApiResponse<Void> completeTodo(
|
||||
@RequestHeader("X-User-Id") @Min(1) Long userId,
|
||||
@PathVariable("todoId") @Min(1) Long todoId) {
|
||||
dashboardService.completeTodo(userId, todoId);
|
||||
dashboardService.completeTodo(CurrentUserUtils.requireCurrentUserId(userId), todoId);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +47,6 @@ public class DashboardController {
|
|||
@RequestHeader("X-User-Id") @Min(1) Long userId,
|
||||
@PathVariable("cardKey") String cardKey,
|
||||
@RequestParam(value = "dimension", required = false) String dimension) {
|
||||
return ApiResponse.success(dashboardService.getAnalyticsCardDetail(userId, cardKey, dimension));
|
||||
return ApiResponse.success(dashboardService.getAnalyticsCardDetail(CurrentUserUtils.requireCurrentUserId(userId), cardKey, dimension));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -763,6 +763,16 @@ const PROFILE_OVERVIEW_CACHE_KEY = "auth-cache:profile-overview";
|
|||
const memoryRequestCache = new Map<string, { expiresAt: number; value: unknown }>();
|
||||
const inFlightRequestCache = new Map<string, Promise<unknown>>();
|
||||
let authExpiryTimer: number | null = null;
|
||||
const AUTH_REQUIRED_MESSAGE_PATTERNS = [
|
||||
"登录已失效",
|
||||
"重新登录",
|
||||
"未登录",
|
||||
"未获取到当前登录用户",
|
||||
"禁止查询他人数据",
|
||||
"禁止操作他人数据",
|
||||
"unauthorized",
|
||||
"authorization",
|
||||
] as const;
|
||||
|
||||
type AccessTokenPayload = {
|
||||
exp?: number;
|
||||
|
|
@ -935,6 +945,40 @@ function buildApiErrorMessage(rawText: string, status: number, body?: ApiErrorBo
|
|||
return normalizedText.length > 300 ? normalizedText.slice(0, 300) : normalizedText;
|
||||
}
|
||||
|
||||
function normalizeAuthErrorSource(value?: string | null) {
|
||||
return value?.trim().toLowerCase() || "";
|
||||
}
|
||||
|
||||
function isUnauthorizedMessage(value?: string | null) {
|
||||
const normalized = normalizeAuthErrorSource(value);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return AUTH_REQUIRED_MESSAGE_PATTERNS.some((pattern) => normalized.includes(pattern.toLowerCase()));
|
||||
}
|
||||
|
||||
function isLoginRedirectResponse(response: Response) {
|
||||
if (!response.redirected || !response.url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const responseUrl = new URL(response.url, window.location.origin);
|
||||
return responseUrl.pathname === LOGIN_PATH;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function shouldTreatAsUnauthorized(response: Response, message?: string | null, body?: ApiErrorBody | null) {
|
||||
if (response.status === 401 || isLoginRedirectResponse(response)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isUnauthorizedMessage(message) || isUnauthorizedMessage(body?.msg) || isUnauthorizedMessage(body?.message);
|
||||
}
|
||||
|
||||
async function request<T>(input: string, init?: RequestInit, withAuth = false): Promise<T> {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has("Content-Type") && init?.body && !(init.body instanceof FormData)) {
|
||||
|
|
@ -950,24 +994,30 @@ async function request<T>(input: string, init?: RequestInit, withAuth = false):
|
|||
headers,
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
if (withAuth && shouldTreatAsUnauthorized(response)) {
|
||||
handleUnauthorizedResponse();
|
||||
throw new Error("登录已失效,请重新登录");
|
||||
}
|
||||
|
||||
const rawText = await response.text();
|
||||
const body = tryParseApiBody<T>(rawText, response.headers.get("content-type"));
|
||||
const errorMessage = buildApiErrorMessage(rawText, response.status, body);
|
||||
|
||||
if (withAuth && shouldTreatAsUnauthorized(response, errorMessage, body)) {
|
||||
handleUnauthorizedResponse();
|
||||
throw new Error("登录已失效,请重新登录");
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(buildApiErrorMessage(rawText, response.status, body));
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
if (!body) {
|
||||
throw new Error(rawText.trim() ? buildApiErrorMessage(rawText, response.status, null) : "接口返回为空");
|
||||
throw new Error(rawText.trim() ? errorMessage : "接口返回为空");
|
||||
}
|
||||
|
||||
if (!isSuccessCode(body.code)) {
|
||||
throw new Error(buildApiErrorMessage(rawText, response.status, body));
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return body.data;
|
||||
|
|
@ -979,7 +1029,7 @@ export async function fetchWithAuth(input: string, init?: RequestInit) {
|
|||
headers: applyAuthHeaders(new Headers(init?.headers)),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
if (shouldTreatAsUnauthorized(response)) {
|
||||
handleUnauthorizedResponse();
|
||||
throw new Error("登录已失效,请重新登录");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue