添加管理员登陆,添加nginx反向代理配置,
This commit is contained in:
+178
-157
@@ -1,11 +1,11 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSearchParams, Form } from "@remix-run/react";
|
||||
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
|
||||
import { OAuthClient } from "~/api/login/oauth-client";
|
||||
import { OAUTH_CONFIG } from "~/config/api-config";
|
||||
import { getUserSession, getSession, sessionStorage, getUserBySub, addDefaultRole } from "~/api/login/auth.server";
|
||||
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
|
||||
import { getUserSession, getSession, simpleRootLogin } from "~/api/login/auth.server";
|
||||
import styles from "~/styles/pages/login.css?url";
|
||||
import { toastService } from "~/components/ui";
|
||||
|
||||
export const links = () => [
|
||||
{ rel: "stylesheet", href: styles }
|
||||
@@ -44,112 +44,26 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
const intent = formData.get("intent");
|
||||
const username = formData.get("username")?.toString().trim();
|
||||
const password = formData.get("password")?.toString().trim();
|
||||
|
||||
if (intent === "test_user_login") {
|
||||
if (intent === "password_login") {
|
||||
// 获取重定向目标
|
||||
const session = await getSession(request);
|
||||
const redirectTo = session.get("redirectTo") || "/";
|
||||
|
||||
// 使用测试用户登录
|
||||
const testUserSub = "001"; // 测试用户的sub
|
||||
const userResult = await getUserBySub(testUserSub);
|
||||
// 调用 simpleRootLogin 方法进行登录
|
||||
const response = await simpleRootLogin(username || "", password || "", redirectTo);
|
||||
|
||||
if (userResult.success && userResult.data) {
|
||||
const user = userResult.data;
|
||||
|
||||
// 确保用户有默认角色
|
||||
if (user.id) {
|
||||
await addDefaultRole(user.id, 2); // 添加common角色
|
||||
}
|
||||
|
||||
// 设置模拟的OAuth token信息
|
||||
const mockTokenExpiresIn = 60 * 60 * 2; // 2小时,与真实OAuth token保持一致
|
||||
const userRole = 'common';
|
||||
|
||||
// 生成前端专用JWT
|
||||
const jwtUserInfo: UserInfoForJWT = {
|
||||
sub: user.sub,
|
||||
user_id: user.id!,
|
||||
username: user.username,
|
||||
nick_name: user.nick_name,
|
||||
email: user.email,
|
||||
phone_number: user.phone_number,
|
||||
ou_id: user.ou_id,
|
||||
ou_name: user.ou_name,
|
||||
is_leader: user.is_leader,
|
||||
user_role: userRole
|
||||
};
|
||||
|
||||
const frontendJWT = JWTUtils.generateJWT(jwtUserInfo, mockTokenExpiresIn);
|
||||
|
||||
// 打印JWT生成信息
|
||||
console.log("=== 测试用户登录 - JWT生成信息 ===");
|
||||
console.log("用户信息:", jwtUserInfo);
|
||||
console.log("生成的JWT:", frontendJWT);
|
||||
console.log("JWT过期时间:", JWTUtils.getJWTExpiration(frontendJWT));
|
||||
console.log("JWT解析结果:", JWTUtils.decodeJWT(frontendJWT));
|
||||
console.log("JWT验证结果:", JWTUtils.verifyJWT(frontendJWT));
|
||||
|
||||
// 创建session,保持与OAuth登录相同的数据结构
|
||||
session.set("isAuthenticated", true);
|
||||
session.set("accessToken", "mock_access_token_for_test"); // 模拟的访问令牌
|
||||
session.set("refreshToken", "mock_refresh_token_for_test"); // 模拟的刷新令牌
|
||||
session.set("tokenIssuedAt", Date.now());
|
||||
session.set("tokenExpiresIn", mockTokenExpiresIn);
|
||||
session.set("userRole", userRole);
|
||||
session.set("frontendJWT", frontendJWT);
|
||||
|
||||
// 构建与OAuth登录相同结构的userInfo
|
||||
const enhancedUserInfo = {
|
||||
// 保持与callback.tsx中相同的数据结构
|
||||
sub: user.sub,
|
||||
username: user.username,
|
||||
nick_name: user.nick_name,
|
||||
phone_number: user.phone_number,
|
||||
email: user.email,
|
||||
ou_id: user.ou_id,
|
||||
ou_name: user.ou_name,
|
||||
status: user.status,
|
||||
is_leader: user.is_leader,
|
||||
// 增强字段,与OAuth登录保持一致
|
||||
user_id: user.id,
|
||||
user_role: userRole,
|
||||
frontend_jwt: frontendJWT
|
||||
};
|
||||
|
||||
session.set("userInfo", enhancedUserInfo);
|
||||
|
||||
// 打印session信息
|
||||
console.log("=== 测试用户登录 - Session信息 ===");
|
||||
console.log("保存到session的userInfo:", enhancedUserInfo);
|
||||
// console.log("session数据结构:", {
|
||||
// isAuthenticated: true,
|
||||
// userRole: userRole,
|
||||
// accessToken: "mock_access_token_for_test",
|
||||
// refreshToken: "mock_refresh_token_for_test",
|
||||
// tokenIssuedAt: Date.now(),
|
||||
// tokenExpiresIn: mockTokenExpiresIn,
|
||||
// frontendJWT: frontendJWT,
|
||||
// userInfo: enhancedUserInfo
|
||||
// });
|
||||
|
||||
const cookie = await sessionStorage.commitSession(session);
|
||||
|
||||
console.log("=== 测试用户登录完成 ===");
|
||||
console.log("用户:", user.username);
|
||||
console.log("角色:", userRole);
|
||||
console.log("重定向到:", redirectTo);
|
||||
|
||||
return new Response(null, {
|
||||
status: 302,
|
||||
headers: {
|
||||
Location: redirectTo,
|
||||
"Set-Cookie": cookie,
|
||||
},
|
||||
});
|
||||
// 检查响应状态
|
||||
if (response.status === 302) {
|
||||
// 登录成功,直接返回重定向响应
|
||||
return response;
|
||||
} else {
|
||||
// 如果用户不存在,重定向到登录页面并显示错误
|
||||
return redirect(`/login?error=${encodeURIComponent("测试用户不存在")}`);
|
||||
// 登录失败,解析错误信息并重定向到登录页面
|
||||
const errorData = await response.json();
|
||||
const errorMsg = errorData.error || "登录失败";
|
||||
return redirect(`/login?error=${encodeURIComponent(errorMsg)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +73,9 @@ export async function action({ request }: ActionFunctionArgs) {
|
||||
export default function Login() {
|
||||
const [searchParams] = useSearchParams();
|
||||
const error = searchParams.get("error");
|
||||
const [isFlipped, setIsFlipped] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
// 获取错误消息的友好描述
|
||||
const getErrorMessage = (error: string | null) => {
|
||||
@@ -175,6 +92,15 @@ export default function Login() {
|
||||
return "获取用户信息失败,请重新登录";
|
||||
case "callback_error":
|
||||
return "登录回调处理失败,请重新登录";
|
||||
case "用户名和密码不能为空":
|
||||
case "用户名和密码不能为空,请重新输入":
|
||||
return "用户名和密码不能为空,请重新输入";
|
||||
case "登录失败,请检查用户名和密码":
|
||||
case "用户名或密码错误,请重新输入":
|
||||
return "用户名或密码错误,请重新输入";
|
||||
case "登录请求失败,请稍后重试":
|
||||
case "网络连接失败,请稍后重试":
|
||||
return "网络连接失败,请稍后重试";
|
||||
default:
|
||||
return decodeURIComponent(error);
|
||||
}
|
||||
@@ -203,80 +129,175 @@ export default function Login() {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理管理员登录
|
||||
const handleAdminLogin = () => {
|
||||
setIsFlipped(true);
|
||||
};
|
||||
|
||||
// 处理返回OAuth登录
|
||||
const handleBackToOAuth = () => {
|
||||
setIsFlipped(false);
|
||||
setUsername("");
|
||||
setPassword("");
|
||||
};
|
||||
|
||||
// 处理账号密码登录表单提交
|
||||
const handlePasswordLoginSubmit = (e: React.FormEvent) => {
|
||||
// 客户端验证
|
||||
if (!username.trim()) {
|
||||
e.preventDefault();
|
||||
toastService.error("请输入用户名");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.trim()) {
|
||||
e.preventDefault();
|
||||
toastService.error("请输入密码");
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证通过,让表单正常提交
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 检查OAuth配置是否完整
|
||||
if (!OAUTH_CONFIG.serverUrl || !OAUTH_CONFIG.clientId || !OAUTH_CONFIG.clientSecret) {
|
||||
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
|
||||
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="login-page">
|
||||
<div className="login-container">
|
||||
<div className="login-header">
|
||||
<h1 className="login-title">中国烟草AI合同及卷宗审核系统</h1>
|
||||
</div>
|
||||
|
||||
<div className="login-form-container">
|
||||
<h2 className="login-subtitle">统一身份认证登录</h2>
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||
<div className="error-text">{getErrorMessage(error)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="oauth-login-section">
|
||||
<div className="login-description">
|
||||
<p>请点击下方按钮进行统一身份认证登录</p>
|
||||
<div className={`login-container ${isFlipped ? 'flipped' : ''}`}>
|
||||
<div className="login-card">
|
||||
{/* 正面 - OAuth登录 */}
|
||||
<div className="login-card-front">
|
||||
<div className="login-header">
|
||||
<h1 className="login-title">中国烟草AI合同及卷宗审核系统</h1>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleOAuthLogin}
|
||||
className="oauth-login-button"
|
||||
type="button"
|
||||
>
|
||||
<i className="ri-shield-user-line"></i>
|
||||
统一身份认证登录
|
||||
</button>
|
||||
<div className="login-form-container">
|
||||
<h2 className="login-subtitle">统一身份认证登录</h2>
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||
<div className="error-text">{getErrorMessage(error)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="oauth-login-section">
|
||||
<div className="login-description">
|
||||
<p>请点击下方按钮进行统一身份认证登录</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleOAuthLogin}
|
||||
className="oauth-login-button"
|
||||
type="button"
|
||||
>
|
||||
<i className="ri-shield-user-line"></i>
|
||||
统一身份认证登录
|
||||
</button>
|
||||
|
||||
<div className="login-tips">
|
||||
<p>
|
||||
<i className="ri-information-line"></i>
|
||||
系统将跳转到统一身份认证平台进行登录
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 管理员登录链接 */}
|
||||
<div className="admin-login-link">
|
||||
<button
|
||||
onClick={handleAdminLogin}
|
||||
className="admin-login-text"
|
||||
type="button"
|
||||
>
|
||||
管理员登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="login-tips">
|
||||
<p>
|
||||
<i className="ri-information-line"></i>
|
||||
系统将跳转到统一身份认证平台进行登录
|
||||
</p>
|
||||
<div className="login-footer">
|
||||
<p>© 2025 中国烟草 版权所有</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 测试用户登录区域 */}
|
||||
<div className="temp-login-section">
|
||||
<div className="section-divider">
|
||||
<span>或</span>
|
||||
{/* 背面 - 管理员登录 */}
|
||||
<div className="login-card-back">
|
||||
<div className="login-header">
|
||||
<h1 className="login-title">中国烟草AI合同及卷宗审核系统</h1>
|
||||
</div>
|
||||
|
||||
<Form method="post" className="temp-login-form">
|
||||
<input type="hidden" name="intent" value="test_user_login" />
|
||||
<button
|
||||
type="submit"
|
||||
className="temp-admin-login-button"
|
||||
>
|
||||
<i className="ri-user-line"></i>
|
||||
测试用户登录
|
||||
</button>
|
||||
<div className="temp-login-tips">
|
||||
<p>
|
||||
<i className="ri-information-line"></i>
|
||||
使用测试用户(testuser1)登录,默认普通权限
|
||||
</p>
|
||||
<div className="login-form-container">
|
||||
<h2 className="login-subtitle">管理员登录</h2>
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<div className="error-icon"><i className="ri-error-warning-line"></i></div>
|
||||
<div className="error-text">{getErrorMessage(error)}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Form method="post" className="admin-login-form" onSubmit={handlePasswordLoginSubmit}>
|
||||
<input type="hidden" name="intent" value="password_login" />
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="username" className="form-label">用户名</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
name="username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
className="form-input"
|
||||
placeholder="请输入用户名"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="password" className="form-label">密码</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="form-input"
|
||||
placeholder="请输入密码"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="admin-login-button"
|
||||
>
|
||||
<i className="ri-login-box-line"></i>
|
||||
登录
|
||||
</button>
|
||||
</Form>
|
||||
|
||||
<div className="back-to-oauth">
|
||||
<button
|
||||
onClick={handleBackToOAuth}
|
||||
className="back-button"
|
||||
type="button"
|
||||
>
|
||||
<i className="ri-arrow-left-line"></i>
|
||||
返回统一身份认证登录
|
||||
</button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<div className="login-footer">
|
||||
<p>© 2025 中国烟草 版权所有</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="login-footer">
|
||||
<p>© 2025 中国烟草 版权所有</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user