登陆注册与忘记密码前后端与jwt配置
Change-Id: Ide4ca3ea34609fdb33ea027e28169852fa41784a
diff --git a/rhj/frontend/src/pages/TestDashboard/TestDashboard.js b/rhj/frontend/src/pages/TestDashboard/TestDashboard.js
new file mode 100644
index 0000000..0ecaaf4
--- /dev/null
+++ b/rhj/frontend/src/pages/TestDashboard/TestDashboard.js
@@ -0,0 +1,295 @@
+import React, { useState, useEffect } from 'react';
+import { Card, Button, Descriptions, Avatar, Tag, Space, message } from 'antd';
+import { UserOutlined, LogoutOutlined, ReloadOutlined } from '@ant-design/icons';
+import { getUserInfo, getAuthToken, isLoggedIn, saveAuthInfo, createAuthenticatedRequest } from '../../utils/auth';
+import LogoutButton from '../../components/LogoutButton/LogoutButton';
+import './TestDashboard.css';
+
+const TestDashboard = () => {
+ const [userInfo, setUserInfo] = useState(null);
+ const [token, setToken] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [jwtTestLoading, setJwtTestLoading] = useState(false);
+
+ useEffect(() => {
+ // 检查用户是否已登录
+ if (!isLoggedIn()) {
+ window.location.href = '/';
+ return;
+ }
+
+ // 获取用户信息和token
+ const authToken = getAuthToken();
+ const authUserInfo = getUserInfo();
+
+ setToken(authToken);
+ setUserInfo(authUserInfo);
+ }, []);
+
+ const handleRefreshProfile = async () => {
+ if (!token) {
+ message.error('未找到认证token');
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const response = await fetch('http://10.126.59.25:8082/profile', createAuthenticatedRequest());
+
+ const result = await response.json();
+
+ if (result.success) {
+ setUserInfo(result.user);
+ // 更新存储的用户信息,保持原有的存储方式(localStorage或sessionStorage)
+ const isRemembered = localStorage.getItem('authToken');
+ saveAuthInfo(token, result.user, !!isRemembered);
+ message.success('用户信息刷新成功');
+ } else {
+ message.error(`获取用户信息失败: ${result.message}`);
+ }
+ } catch (error) {
+ console.error('刷新用户信息失败:', error);
+ message.error('网络连接失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleLogout = async () => {
+ if (!token) {
+ // 清除存储并跳转
+ localStorage.removeItem('authToken');
+ localStorage.removeItem('userInfo');
+ sessionStorage.removeItem('authToken');
+ sessionStorage.removeItem('userInfo');
+ window.location.href = '/';
+ return;
+ }
+
+ try {
+ const response = await fetch('http://10.126.59.25:8082/logout', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ }
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ message.success('退出登录成功');
+ } else {
+ message.warning(`退出登录: ${result.message}`);
+ }
+ } catch (error) {
+ console.error('退出登录请求失败:', error);
+ message.warning('网络请求失败,但将清除本地数据');
+ } finally {
+ // 无论请求成功与否,都清除本地存储并跳转
+ localStorage.removeItem('authToken');
+ localStorage.removeItem('userInfo');
+ sessionStorage.removeItem('authToken');
+ sessionStorage.removeItem('userInfo');
+ window.location.href = '/';
+ }
+ };
+
+ const handleTestJWT = async () => {
+ if (!token) {
+ message.error('未找到认证token');
+ return;
+ }
+
+ setJwtTestLoading(true);
+ try {
+ const response = await fetch('http://10.126.59.25:8082/test-jwt', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ token: token, // 可选:在请求体中也发送token进行额外验证
+ test_purpose: 'frontend_jwt_test'
+ })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ message.success(`JWT令牌验证成功!用户: ${result.user.username}`);
+ console.log('JWT验证详细结果:', result);
+
+ // 如果有额外的token验证结果,也显示出来
+ if (result.additional_token_verification) {
+ console.log('额外token验证:', result.additional_token_verification);
+ }
+ } else {
+ message.error(`JWT令牌验证失败: ${result.message}`);
+ }
+ } catch (error) {
+ console.error('JWT令牌验证失败:', error);
+ message.error('网络连接失败');
+ } finally {
+ setJwtTestLoading(false);
+ }
+ };
+
+ const getRoleColor = (role) => {
+ switch (role) {
+ case 'superadmin':
+ return 'red';
+ case 'admin':
+ return 'orange';
+ case 'user':
+ default:
+ return 'blue';
+ }
+ };
+
+ const getStatusColor = (status) => {
+ switch (status) {
+ case 'active':
+ return 'green';
+ case 'banned':
+ return 'red';
+ case 'muted':
+ return 'orange';
+ default:
+ return 'default';
+ }
+ };
+
+ if (!userInfo) {
+ return (
+ <div className="test-dashboard">
+ <div className="loading-container">
+ <div className="spinner"></div>
+ <p>加载用户信息中...</p>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div className="test-dashboard">
+ <div className="dashboard-header">
+ <h1>测试仪表板</h1>
+ <p>登录成功!以下是从后端返回的用户信息:</p>
+ </div>
+
+ <div className="dashboard-content">
+ <Card
+ title={
+ <Space>
+ <Avatar size={40} icon={<UserOutlined />} src={userInfo.avatar} />
+ <span>用户信息</span>
+ </Space>
+ }
+ extra={
+ <Space>
+ <Button
+ type="primary"
+ icon={<ReloadOutlined />}
+ loading={loading}
+ onClick={handleRefreshProfile}
+ >
+ 刷新信息
+ </Button>
+ <LogoutButton onLogout={() => window.location.href = '/'} />
+ </Space>
+ }
+ className="user-info-card"
+ >
+ <Descriptions column={2} bordered>
+ <Descriptions.Item label="用户ID">{userInfo.id}</Descriptions.Item>
+ <Descriptions.Item label="用户名">{userInfo.username}</Descriptions.Item>
+ <Descriptions.Item label="邮箱">{userInfo.email}</Descriptions.Item>
+ <Descriptions.Item label="角色">
+ <Tag color={getRoleColor(userInfo.role)}>
+ {userInfo.role}
+ </Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="账号状态">
+ <Tag color={getStatusColor(userInfo.status)}>
+ {userInfo.status}
+ </Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="个人简介" span={2}>
+ {userInfo.bio || '暂无个人简介'}
+ </Descriptions.Item>
+ <Descriptions.Item label="创建时间">
+ {userInfo.created_at ? new Date(userInfo.created_at).toLocaleString() : '未知'}
+ </Descriptions.Item>
+ <Descriptions.Item label="更新时间">
+ {userInfo.updated_at ? new Date(userInfo.updated_at).toLocaleString() : '未知'}
+ </Descriptions.Item>
+ </Descriptions>
+ </Card>
+
+ <Card title="登录状态信息" className="login-status-card">
+ <div className="login-status-display">
+ <Descriptions column={1} bordered>
+ <Descriptions.Item label="登录方式">
+ <Tag color={localStorage.getItem('authToken') ? 'green' : 'blue'}>
+ {localStorage.getItem('authToken') ? '记住我登录 (持久化)' : '普通登录 (会话)'}
+ </Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="Token存储位置">
+ {localStorage.getItem('authToken') ? 'localStorage (浏览器关闭后仍保持登录)' : 'sessionStorage (浏览器关闭后需重新登录)'}
+ </Descriptions.Item>
+ <Descriptions.Item label="记住的登录信息">
+ {localStorage.getItem('rememberMe') === 'true' ?
+ `已保存邮箱: ${localStorage.getItem('rememberedEmail') || '无'}` :
+ '未保存登录信息'
+ }
+ </Descriptions.Item>
+ </Descriptions>
+ </div>
+ </Card>
+
+ <Card title="Token信息" className="token-info-card">
+ <div className="token-display">
+ <p><strong>认证Token:</strong></p>
+ <div className="token-text">
+ {token ? `${token.substring(0, 50)}...` : '未找到token'}
+ </div>
+ <p className="token-note">
+ * Token已被安全截断显示,完整token存储在浏览器存储中
+ </p>
+ </div>
+ </Card>
+
+ <Card title="API测试" className="api-test-card">
+ <Space direction="vertical" style={{ width: '100%' }}>
+ <p>您可以使用以下按钮测试不同的API接口:</p>
+ <Space wrap>
+ <Button onClick={handleRefreshProfile} loading={loading}>
+ 测试 GET /profile
+ </Button>
+ <Button onClick={handleLogout}>
+ 测试 POST /logout
+ </Button>
+ <Button
+ onClick={handleTestJWT}
+ loading={jwtTestLoading}
+ type="primary"
+ >
+ 测试 POST /test-jwt
+ </Button>
+ <Button
+ type="dashed"
+ onClick={() => window.open('http://10.126.59.25:8082/health', '_blank')}
+ >
+ 测试 GET /health
+ </Button>
+ </Space>
+ </Space>
+ </Card>
+ </div>
+ </div>
+ );
+};
+
+export default TestDashboard;