Skip to content

Commit 4e6005e

Browse files
authored
feat: add AuthGuard component for enhanced route protection (#446)
* feat: add AuthGuard component for enhanced route protection * feat: add AuthGuard component for enhanced route protection * feat: add AuthGuard component for enhanced route protection * feat: add AuthGuard component for enhanced route protection * feat: update login redirect logic to navigate directly to login page * feat: update login redirect logic to navigate directly to login page
1 parent 2986101 commit 4e6005e

3 files changed

Lines changed: 134 additions & 15 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useState, useEffect, useCallback } from "react";
2+
import { message } from "antd";
3+
import { LoginDialog } from "@/pages/Layout/LoginDialog";
4+
import { SignupDialog } from "@/pages/Layout/SignupDialog";
5+
import { post, get } from "@/utils/request";
6+
import { useTranslation } from "react-i18next";
7+
8+
function loginUsingPost(data: { username: string; password: string }) {
9+
return post("/api/user/login", data);
10+
}
11+
12+
function signupUsingPost(data: { username: string; email: string; password: string }) {
13+
return post("/api/user/signup", data);
14+
}
15+
16+
export function AuthGuard() {
17+
const { t } = useTranslation();
18+
const [loginOpen, setLoginOpen] = useState(false);
19+
const [signupOpen, setSignupOpen] = useState(false);
20+
const [loading, setLoading] = useState(false);
21+
22+
const openLoginDialog = useCallback(() => {
23+
console.log('[AuthGuard] openLoginDialog called, setting loginOpen to true');
24+
setLoginOpen(true);
25+
}, []);
26+
27+
const openSignupDialog = useCallback(() => {
28+
console.log('[AuthGuard] openSignupDialog called');
29+
setSignupOpen(true);
30+
}, []);
31+
32+
useEffect(() => {
33+
console.log('[AuthGuard] Registering show-login event listener');
34+
window.addEventListener("show-login", openLoginDialog);
35+
36+
return () => {
37+
console.log('[AuthGuard] Removing show-login event listener');
38+
window.removeEventListener("show-login", openLoginDialog);
39+
};
40+
}, [openLoginDialog]);
41+
42+
useEffect(() => {
43+
const session = localStorage.getItem("session");
44+
if (!session) {
45+
get("/api/sys-param/sys.home.page.url").catch(() => {});
46+
}
47+
}, []);
48+
49+
const handleLogin = async (values: { username: string; password: string }) => {
50+
try {
51+
setLoading(true);
52+
const response = await loginUsingPost(values);
53+
localStorage.setItem("session", JSON.stringify(response.data));
54+
message.success(t("user.messages.loginSuccess"));
55+
setLoginOpen(false);
56+
window.location.reload();
57+
} catch (error) {
58+
console.error("Login error:", error);
59+
message.error(t("user.messages.loginFailed"));
60+
} finally {
61+
setLoading(false);
62+
}
63+
};
64+
65+
const handleSignup = async (values: {
66+
username: string;
67+
email: string;
68+
password: string;
69+
confirmPassword: string;
70+
}) => {
71+
if (values.password !== values.confirmPassword) {
72+
message.error(t("user.messages.passwordMismatch"));
73+
return;
74+
}
75+
76+
try {
77+
setLoading(true);
78+
const { username, email, password } = values;
79+
const response = await signupUsingPost({ username, email, password });
80+
message.success(t("user.messages.signupSuccess"));
81+
localStorage.setItem("session", JSON.stringify(response.data));
82+
setSignupOpen(false);
83+
window.location.reload();
84+
} catch (error) {
85+
console.error("Registration error:", error);
86+
message.error(t("user.messages.signupFailed"));
87+
} finally {
88+
setLoading(false);
89+
}
90+
};
91+
92+
return (
93+
<>
94+
<LoginDialog
95+
open={loginOpen}
96+
onOpenChange={setLoginOpen}
97+
onLogin={handleLogin}
98+
loading={loading}
99+
onSignupClick={openSignupDialog}
100+
/>
101+
<SignupDialog
102+
open={signupOpen}
103+
onOpenChange={setSignupOpen}
104+
onSignup={handleSignup}
105+
loading={loading}
106+
onLoginClick={openLoginDialog}
107+
/>
108+
</>
109+
);
110+
}
111+
112+
export default AuthGuard;

frontend/src/main.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import router from "./routes/routes";
55
import { App as AntdApp, Spin, ConfigProvider } from "antd";
66
import "./index.css";
77
import TopLoadingBar from "./components/TopLoadingBar";
8+
import AuthGuard from "./components/AuthGuard";
89
import { store } from "./store";
910
import { Provider } from "react-redux";
1011
import theme from "./theme";
@@ -94,6 +95,7 @@ async function bootstrap() {
9495
<AntdApp>
9596
<Suspense fallback={<Spin />}>
9697
<TopLoadingBar />
98+
<AuthGuard />
9799
<RouterProvider router={router} />
98100
</Suspense>
99101
</AntdApp>

frontend/src/utils/request.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import {message} from "antd";
22
import Loading from "./loading";
33
import {errorConfigStore} from "@/utils/errorConfigStore.ts";
44
import i18n from "@/i18n";
5-
import i18n from "@/i18n";
6-
import i18n from "@/i18n";
7-
import i18n from "@/i18n";
85

96
/**
107
* 通用请求工具类
@@ -526,11 +523,9 @@ request.addRequestInterceptor((config) => {
526523
try {
527524
const sessionData = JSON.parse(session);
528525
if (sessionData.token) {
529-
// 后端使用 "User" 请求头而不是 "Authorization"
530-
// 可以直接发送 token 或 username
531526
config.headers = {
532527
...config.headers,
533-
'User': sessionData.token, // 使用 User 请求头
528+
'Authorization': `Bearer ${sessionData.token}`,
534529
};
535530
}
536531
} catch (e) {
@@ -550,24 +545,29 @@ request.addRequestInterceptor((config) => {
550545
// --- 常量配置 ---
551546
const DEFAULT_ERROR_MSG = '系统繁忙,请稍后重试';
552547
// 需要触发重新登录的 Code 集合 (包含 HTTP 401 和 业务 Token 过期码)
553-
const AUTH_ERR_CODES = [401, '401', 'common.401'];
548+
// 注意:后端返回的是 "common.0401"(有前导零)
549+
const AUTH_ERR_CODES = [401, '401', 'common.401', 'common.0401'];
554550

555551
// --- 辅助函数:防抖处理登录失效 ---
556552
let isRelogging = false;
553+
557554
const handleLoginRedirect = () => {
558-
if (isRelogging) return;
555+
console.log('[Auth] handleLoginRedirect called, isRelogging:', isRelogging);
556+
557+
if (isRelogging) {
558+
console.log('[Auth] Skipping - already relogging');
559+
return;
560+
}
559561
isRelogging = true;
560562

561-
// 1. 清除 Session / Token
562563
localStorage.removeItem('session');
563564

564-
// 2. 触发登录弹窗事件 (根据你的架构,这里可以是 dispatch event 或 router 跳转)
565-
const loginEvent = new CustomEvent('show-login');
566-
window.dispatchEvent(loginEvent);
565+
console.log('[Auth] Dispatching show-login event');
566+
window.dispatchEvent(new CustomEvent('show-login'));
567567

568-
// 3. 重置标志位 (3秒后才允许再次触发)
569568
setTimeout(() => {
570569
isRelogging = false;
570+
console.log('[Auth] Reset isRelogging flag');
571571
}, 3000);
572572
};
573573

@@ -578,6 +578,7 @@ request.addResponseInterceptor(async (response, config) => {
578578
}
579579

580580
const { status } = response;
581+
console.log('[API Interceptor] Response status:', status, 'URL:', config?.url);
581582

582583
// ------------------ 修改重点开始 ------------------
583584

@@ -588,17 +589,19 @@ request.addResponseInterceptor(async (response, config) => {
588589
// 关键点 2: 必须用 .clone(),因为流只能读一次。读了克隆的,原版 response 还能留给外面用
589590
// 关键点 3: 必须 await,因为读取流是异步的
590591
resData = await response.clone().json();
592+
console.log('[API Interceptor] Response data:', resData);
591593
} catch (e) {
592594
// 如果后端返回的不是 JSON (比如 404 HTML 页面,或者空字符串),json() 会报错
593595
// 这里捕获异常,保证 resData 至少是个空对象,不会导致后面取值 crash
594-
console.warn('响应体不是有效的JSON:', e);
596+
console.warn('[API Interceptor] 响应体不是有效的JSON:', e);
595597
resData = {};
596598
}
597599

598600
// 2. 获取统一的错误码 (转为字符串以匹配 JSON 配置的 Key)
599601
// 优先取后端 body 里的 business code,没有则取 HTTP status
600602
const code = resData.code ?? status;
601603
const codeStr = String(code);
604+
console.log('[API Interceptor] Extracted code:', code, 'codeStr:', codeStr);
602605

603606
// 3. 判断成功 (根据你的后端约定:200/0 为成功)
604607
// 如果是成功状态,直接返回 response,不拦截
@@ -623,7 +626,9 @@ request.addResponseInterceptor(async (response, config) => {
623626
}
624627

625628
// 7. 处理 Token 过期 / 未登录
626-
if (AUTH_ERR_CODES.includes(code) || AUTH_ERR_CODES.includes(codeStr)) {
629+
const isAuthError = AUTH_ERR_CODES.includes(code) || AUTH_ERR_CODES.includes(codeStr);
630+
console.log('[API Interceptor] Is auth error?', isAuthError, 'AUTH_ERR_CODES:', AUTH_ERR_CODES);
631+
if (isAuthError) {
627632
handleLoginRedirect();
628633
}
629634

0 commit comments

Comments
 (0)