我正在开发这个项目,使用 Spring Boot 作为后端,使用 React 作为前端,我连接了这个前端和后端。然后注册和登录工作正常,没有任何错误,但是当我要获取登录信息以显示前端时,会显示错误消息。我创建了前端的功能。当单击“登录/注册”按钮时,它将转到基于角色(用户/所有者/管理员)的用户配置文件。但无法显示相关的登录用户详细信息。特别的是,当我在邮递员中运行该 API 时,该函数运行正常,没有任何错误。
import axios from 'axios';
import React, { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom'; // Import useNavigate hook
import ForgotPasswordPopup from '../components/ForgotPasswordPopup';
import EnterCodePopup from '../components/EnterCodePopup';
import ChangePasswordPopup from '../components/ChangePasswordPopup';
import './login.css';
import MailOutlineIcon from '@mui/icons-material/MailOutline';
import LockIcon from '@mui/icons-material/Lock';
import CloseIcon from '@mui/icons-material/Close';
import { GoogleLogin } from '@react-oauth/google'; // Import from @react-oauth/google
export default function Login() {
const [step, setStep] = useState(null);
const navigate = useNavigate(); // Initialize navigate function
const [email, setEmail] = useState(''); // State for email input
const [password, setPassword] = useState('');
const location = useLocation();
const [error, setError] = useState(''); // State to show login errors
// Get the page the user was trying to access before being redirected to login
const redirectTo = location.state?.from?.pathname || '/'; // Defaults to home page if no previous page
const openForgotPassword = () => setStep('forgot');
const openEnterCode = () => setStep('code');
const openChangePassword = () => setStep('change');
const closePopup = () => setStep(null);
// Navigate to the signup page when the signup button is clicked
const handleSignup = () => {
navigate('/signup'); // Navigate to your signup page
};
// Function to handle Google Login success
const handleGoogleSuccess = (response) => {
console.log('Google login successful:', response);
localStorage.setItem('authToken', 'google-auth-token');
navigate(redirectTo); // Navigate to the intended page after successful login
};
// Function to handle Google Login failure
const handleGoogleFailure = (error) => {
console.log('Google login failed:', error);
// Handle failed login here
};
// Function to go back to the previous page
const handleClose = () => {
navigate(-1); // Navigates back to the previous page
};
// Handle email/password form submission
const handleSignIn = async (e) => {
e.preventDefault();
setError(''); // Reset error message
try {
// Send a POST request to the backend API
const response = await axios.post(${process.env.REACT_APP_API_URL}/SignInUser/SignIn, {
email,
password,
});
const { role, token } = response.data; // Assuming your backend returns a user object with a role
console.log('Email login successful');
// Store authentication token in localStorage
localStorage.setItem('authToken',token); // Assuming the token is returned
// Redirect based on user role
switch (role) {
case 'User':
navigate('/userprofile');
break;
case 'Owner':
navigate('/ownerprofile');
break;
case 'Admin':
navigate('/adminprofile');
break;
default:
navigate('/'); // Fallback in case role is unrecognized
}
} catch (error) {
if (error.response) {
// Handle specific errors based on the response from the backend
if (error.response.status === 401) {
setError('Incorrect email or password');
} else {
setError('Login failed. Please try again.');
}
} else {
setError('Login failed. Please try again.');
}
}
};
return (
<div className="login-container">
<div className="login">
<div className="login-left">
<img src="/images/1.png" alt="logo" className="logo"/>
<p className="signup-text">Don't have an account?</p>
<button className="signup-button" onClick={handleSignup}>SIGN UP</button>
<img src="/images/1.1.png" alt="login vector" className='vectorimage1'/>
</div>
<div className="login-right">
{/* Update CloseIcon to use handleClose on click */}
<CloseIcon className="close-icon" onClick={handleClose} />
<div className="login-right-signup">
<p className="signup-text">Don't have an account?</p>
<button className="signup-button" onClick={handleSignup}>SIGN UP</button>
</div>
<h2 className="signin-header">Sign in Here</h2>
<form onSubmit={handleSignIn}>
<div className="input">
<input type="email" placeholder="Email" className="input-field" value={email}
onChange={(e) => setEmail(e.target.value)} // Update email state
/>
<MailOutlineIcon className="icon"/>
<input type="password" placeholder="Password" className="input-field"
value={password}
onChange={(e) => setPassword(e.target.value)} // Update password state
/>
<LockIcon className="icon"/>
</div>
<div className="input-field-container">
<button type="button" className="forgot-password" onClick={openForgotPassword}>Forget Your Password?</button>
</div>
<button type="submit" className="signin-button">SIGN IN</button>
</form>
{error && <p style={{ color: 'red' }}>{error}</p>}
<div className="or-divider"><span>OR</span></div>
{/* Use GoogleLogin from @react-oauth/google */}
<GoogleLogin
onSuccess={handleGoogleSuccess}
onError={handleGoogleFailure}
render={({ onClick, disabled }) => (
<button onClick={onClick} disabled={disabled} className="google-signin-button">
<img src="/images/1.2.png" alt="Google logo" className="googlelogo"/>
Google
</button>
)}
/>
</div>
</div>
{step === 'forgot' && <ForgotPasswordPopup onClose={closePopup} onNext={openEnterCode} />}
{step === 'code' && <EnterCodePopup onClose={closePopup} onNext={openChangePassword} />}
{step === 'change' && <ChangePasswordPopup onClose={closePopup} />}
</div>
);
}
import React,{useState} from 'react';
import NavigationBar from '../../components/NavigationBar';
import Footer from '../../components/Footer';
import UserContent from '../../components/UserProfile/UserContent';
import {Avatar,Typography} from '@mui/material';
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
import StarOutlineIcon from '@mui/icons-material/StarOutline';
import PersonOutlineIcon from '@mui/icons-material/PersonOutline';
import MenuIcon from '@mui/icons-material/Menu';
import './userProfile.css';
const UserProfile = () => {
const [activeMenuItem, setActiveMenuItem] = useState('Profile');
const [isSidebarVisible, setSidebarVisible] = useState(false);
const toggleSidebar = () => {
setSidebarVisible(!isSidebarVisible);
};
return (
<div className="userprofile">
{/* Navigation bar at the top */}
<div className="nav">
<NavigationBar />
</div>
{/* Menu icon for small screens */}
<div className="menu-icon" onClick={toggleSidebar}>
<MenuIcon />
</div>
{/* Main container with sidebar and content */}
<div className={userprofile-container ${isSidebarVisible ? 'show-sidebar' : ''}}>
{/* Sidebar Section */}
<div className={userprofile-sidebar ${isSidebarVisible ? 'show-sidebar' : ''}}>
<div className="sidebar-header">
<Avatar className="avatar" />
<Typography variant="h6" className="sidebar-title"sx={{margin:'2px',fontFamily:'"Josefin Sans", sans-serif'}} >User</Typography>
</div>
<ul className="sidebar-menu">
<li
className={sidebar-menu-item ${activeMenuItem === 'Profile' ? 'active' : ''}}
onClick={() => setActiveMenuItem('Profile')}
>
<PersonOutlineIcon className="menu-icon" />
<span className="menu-text">Profile</span>
</li>
<li
className={sidebar-menu-item ${activeMenuItem === 'Chats' ? 'active' : ''}}
onClick={() => setActiveMenuItem('Chats')}
>
<ChatBubbleOutlineIcon className="menu-icon" />
<span className="menu-text">Chats</span>
</li>
<li
className={sidebar-menu-item ${activeMenuItem === 'Ratings' ? 'active' : ''}}
onClick={() => setActiveMenuItem('Ratings')}
>
<StarOutlineIcon className="menu-icon" />
<span className="menu-text">Ratings</span>
</li>
</ul>
</div>
{/* Main Content Section */}
<div className="userprofile-content">
{/* Content can go here */}
<UserContent
activeMenuItem={activeMenuItem}
setActiveMenuItem={setActiveMenuItem}
/>
</div>
</div>
<div className='footer'>
<Footer/>
</div>
</div>
);
};
export default UserProfile;
import axios from 'axios';
// Create an Axios instance with a base URL
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
});
// Request interceptor for adding token
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
},
(error) => Promise.reject(error)
);
// Function to fetch all boarding places
export const fetchBoardingPlaces = async () => {
try {
const response = await api.get('/boarding-places');
return response.data;
} catch (error) {
console.error('Error fetching boarding places:', error);
throw new Error('Unable to fetch boarding places. Please try again later.');
}
};
// Function to fetch a boarding place by ID
export const fetchBoardingPlaceById = async (id) => {
try {
const response = await api.get(/boarding-places/${id});
return response.data;
} catch (error) {
console.error(Error fetching boarding place ${id}:, error);
throw new Error(Unable to fetch boarding place ${id}.);
}
};
// Change user password
export const changePassword = async (newPassword) => {
const token = localStorage.getItem('token'); // Retrieve token
const response = await api.put('/change-password', { password: newPassword }, {
headers: {
Authorization: Bearer ${token},
},
});
return response.data; // Return success message or user data if needed
};
// Fetch user data
export const fetchUserData = async (token, userId) => {
try {
const response = await api.get(/loginuser/${userId}, {
headers: {
Authorization: Bearer ${token},
},
});
return response.data; // Return user data
} catch (error) {
console.error('Error fetching user data:', error);
throw error; // Propagate the error for further handling
}
};
import React from 'react';
import UserAccount from './UserAccount';
import UserChats from './UserChats';
import UserRatings from './UserRatings';
const UserContent = ({ activeMenuItem }) => {
return (
<div>
{activeMenuItem === 'Profile' && <UserAccount/>}
{activeMenuItem === 'Chats' && <UserChats />}
{activeMenuItem === 'Ratings' && <UserRatings />}
</div>
);
};
export default UserContent;
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Container, Grid } from '@mui/material';
import { fetchUserData, changePassword } from '../../apiService'; // Import the API functions
const UserAccount = () => {
const [userData, setUserData] = useState({
userId: '',
email: '',
name: '',
});
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState('');
const [successMessage, setSuccessMessage] = useState('');
useEffect(() => {
const fetchUserDetails = async () => {
try {
const data = await fetchUserData(); // Call API to get user data
setUserData(data);
} catch (error) {
console.error('Error fetching user data:', error);
}
};
fetchUserDetails();
}, []);
const handleChangePassword = async (e) => {
e.preventDefault();
setError('');
setSuccessMessage('');
// Basic validation for password change
if (!newPassword || !confirmPassword) {
setError('Both password fields are required.');
return;
}
if (newPassword !== confirmPassword) {
setError('Passwords do not match.');
return;
}
try {
await changePassword(newPassword); // Call API to update the password
setSuccessMessage('Password changed successfully!');
setNewPassword('');
setConfirmPassword('');
} catch (error) {
setError('Error changing password. Please try again.');
}
};
return (
<Container maxWidth="sm" style={{ padding: '20px', backgroundColor: '#f5f5f5', borderRadius: '8px' }}>
<Typography variant="h5" gutterBottom style={{ color: 'gray', fontSize: '36px' }}>
User Account
</Typography>
{/* Display user details */}
<Grid container spacing={1}>
<Grid item xs={12}>
<Typography variant="h6">User ID: {userData.userId}</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Email: {userData.email}</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="h6">Name: {userData.name}</Typography>
</Grid>
</Grid>
<Typography variant="h6" style={{ marginTop: '20px', color: '#72d6c9' }}>Change Password</Typography>
<form onSubmit={handleChangePassword}>
<TextField
label="New Password"
type="password"
fullWidth
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
<TextField
label="Confirm Password"
type="password"
fullWidth
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
/>
{error && <Typography color="error">{error}</Typography>}
{successMessage && <Typography color="primary">{successMessage}</Typography>}
<Button type="submit" variant="contained" color="primary" fullWidth style={{ marginTop: '20px', backgroundColor: '#72d6c9' }}>
Change Password
</Button>
</form>
</Container>
);
};
export default UserAccount;
package com.example.testing.controller;
import com.example.testing.dto.LoginUserDto;
import com.example.testing.dto.ReturnLoginUserDto;
import com.example.testing.service.LoginUserService;
import com.example.testing.utill.JWTAuthenticator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequestMapping("/loginuser")
public class LoginUserController {
@Autowired
LoginUserService service;
@Autowired
JWTAuthenticator jwtAuthenticator;
@PostMapping("/saveLoginUser")
public ResponseEntity<Object> saveLoginUser(@RequestBody LoginUserDto loginUserDto){
ReturnLoginUserDto returnLoginUserDto = service.saveLoginUser(loginUserDto);
if (returnLoginUserDto != null) {
return new ResponseEntity<>("Register Success", HttpStatus.OK);
} else {
return new ResponseEntity<>("Already regitered with this Email", HttpStatus.CREATED);
}
}
@GetMapping("/{id}")
public ResponseEntity<Object> getLoginUserById(@PathVariable Integer id) {
LoginUserDto loginUserDto = service.getLoginUserById(id);
if (loginUserDto != null) {
return new ResponseEntity<>(loginUserDto, HttpStatus.OK);
}
return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND);
}
}
package com.example.testing.service;
import com.example.testing.dto.LoginUserDto;
import com.example.testing.dto.ReturnLoginUserDto;
import com.example.testing.entity.LoginUser;
import com.example.testing.repo.LoginUserRepo;
import com.example.testing.utill.SignInMail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class LoginUserService {
@Autowired
LoginUserRepo loginUserRepo;
@Autowired
SignInMail signInMail;
@Autowired
PasswordEncoder passwordEncoder;
public ReturnLoginUserDto saveLoginUser(LoginUserDto loginUserDto) {
String encodedPassword = passwordEncoder.encode(loginUserDto.getPassword());
if (loginUserRepo.existsLoginUserByEmail(loginUserDto.getEmail())) {
return null;
}
LoginUser save = loginUserRepo.save(
new LoginUser(loginUserDto.getContactNo(),encodedPassword,loginUserDto.getEmail(),loginUserDto.getRole()));
signInMail.sendEmail(loginUserDto);
return new ReturnLoginUserDto(save.getEmail(), save.getId());
}
public List<LoginUserDto> getAllLoginUser(){
List<LoginUser> all = loginUserRepo.findAll();
List<LoginUserDto> loginUserDtos = new ArrayList<>();
for(LoginUser loginUser : all){
loginUserDtos.add(new LoginUserDto(loginUser.getId(), loginUser.getContactNo(), loginUser.getPassword(), loginUser.getEmail(), loginUser.getRole()));
}
return loginUserDtos;
}
}
package com.example.testing.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class LoginUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer contactNo;
private String password;
private String email;
private String role;
@OneToOne(mappedBy = "loginUser", cascade = CascadeType.ALL)
private BoardingOwner boardingOwner;
public LoginUser(Integer contactNo, String password, String email, String role) {
this.contactNo = contactNo;
this.password = password;
this.email = email;
this.role = role;
}
public LoginUser(Integer id, Integer contactNo, String password, String email) {
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class LoginUserDto {
private Integer id;
private Integer contactNo;
private String password;
private String email;
public String role;
}
我们需要显示登录用户自己的个人资料的详细信息,我们需要知道如何正确集成。
由于 API 在 Postman 中正常工作,但您的前端应用程序遇到 CORS 错误(如控制台所示),这表明您需要配置
CORS (Cross-Origin Resource Sharing)
以允许来自前端的请求。
这是一个示例 CORS 配置,您可以将其添加到您的
Spring Boot
应用程序中。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.addAllowedOrigin("http://localhost:3000"); // Allow requests from the specified origin
corsConfig.addAllowedHeader("*"); // Allow all headers
corsConfig.addAllowedMethod("*"); // Allow all HTTP methods
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return new CorsFilter(source);
}
}
此外,请注意,由于控制台日志在 URL 中显示一些未定义的值,您可能需要验证前端是否正确构建了所有 URL 路径。在发出 API 请求之前检查所有动态参数是否已正确初始化。