系统静默跳转至登录页面的优化

main
kangwenjing 2026-06-12 14:38:57 +08:00
parent f72fb86c16
commit b25d77c63c
2 changed files with 59 additions and 8 deletions

View File

@ -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));
}
}

View File

@ -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("登录已失效,请重新登录");
}