feat: 1. 修改完善全局路由检测。 2. 完善统一的token认证管理,token失效自动跳转到登录页。

This commit is contained in:
2025-11-18 20:32:43 +08:00
parent e7b1c2e294
commit adfb84a31d
17 changed files with 270 additions and 294 deletions
+40 -5
View File
@@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
import { useLoaderData, useNavigate, useFetcher } from "@remix-run/react";
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
import { useLoaderData, useFetcher } from "@remix-run/react";
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node";
import { OAuthClient } from "~/api/login/oauth-client";
import { CLIENT_OAUTH_CONFIG } from "~/config/api-config";
import { getUserSession, getSession, createUserSession } from "~/api/login/auth.server";
import { getSession, createUserSession } from "~/api/login/auth.server";
import { loginWithPassword } from "~/api/login/login-client";
import styles from "~/styles/pages/login.css?url";
import { toastService } from "~/components/ui";
@@ -88,7 +88,7 @@ export async function action({ request }: ActionFunctionArgs) {
}, { status: 401 });
}
const { access_token, user_info } = response.data;
const { access_token, expires_in, issued_time, user_info } = response.data;
// 验证返回数据
if (!access_token) {
@@ -107,8 +107,22 @@ export async function action({ request }: ActionFunctionArgs) {
}, { status: 500 });
}
// 🔑 将后端返回的 issued_time 转换为时间戳(毫秒)
let tokenIssuedAt = Date.now(); // 默认使用当前时间
if (issued_time) {
try {
// 后端返回格式:"2025-11-18 17:41:06"
// 转换为时间戳(毫秒)
tokenIssuedAt = new Date(issued_time.replace(' ', 'T')).getTime();
console.log("📅 [Login Action] 使用后端返回的签发时间:", issued_time, "→", tokenIssuedAt);
} catch (error) {
console.warn("⚠️ [Login Action] 无法解析 issued_time,使用当前时间:", error);
}
}
console.log("✅ [Login Action] 登录成功,准备创建 session");
console.log("👤 [Login Action] 用户角色:", user_info.user_role); // 应该是 "admin"
console.log("⏰ [Login Action] Token 有效期:", expires_in, "秒 (", expires_in / 3600, "小时)");
// 获取当前 URL 用于构建 callback URL
const url = new URL(request.url);
@@ -137,6 +151,8 @@ export async function action({ request }: ActionFunctionArgs) {
userRole: user_info.user_role,
redirectTo: callbackUrl.toString(), // 先跳转到 callback 页面保存 token
frontendJWT: access_token, // 保存到 Cookie Session
tokenExpiresIn: expires_in,
tokenIssuedAt: tokenIssuedAt, // 🔑 传递后端返回的签发时间
userInfo: {
user_id: user_info.user_id,
username: user_info.username,
@@ -161,7 +177,7 @@ export async function action({ request }: ActionFunctionArgs) {
}
export default function Login() {
const navigate = useNavigate();
// const navigate = useNavigate();
const loaderData = useLoaderData<typeof loader>();
const fetcher = useFetcher<{ success: boolean; error?: string }>();
const [isFlipped, setIsFlipped] = useState(false);
@@ -268,6 +284,25 @@ export default function Login() {
}, [fetcher.data]);
useEffect(() => {
// 🔑 只在 token 过期时清理客户端存储
// 检查 URL 参数中是否有 expired=true 标识
if (typeof window !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
const isExpired = urlParams.get('expired') === 'true';
if (isExpired) {
// 只有在因为过期被重定向时才清除 localStorage
localStorage.removeItem('access_token');
localStorage.removeItem('user_info');
console.log("🧹 [Login] Token 已过期,已清除客户端 token 数据");
// 清除 URL 中的 expired 参数,避免刷新页面时重复清除
urlParams.delete('expired');
const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
window.history.replaceState({}, '', newUrl);
}
}
// 检查OAuth配置是否完整(客户端不需要检查 clientSecret
if (!CLIENT_OAUTH_CONFIG.serverUrl || !CLIENT_OAUTH_CONFIG.clientId) {
console.error("OAuth2.0配置不完整:", CLIENT_OAUTH_CONFIG);