Spring Boot项目与React项目集成问题

问题描述 投票:0回答:1

我正在开发这个项目,使用 Spring Boot 作为后端,使用 React 作为前端,我连接了这个前端和后端。然后注册和登录工作正常,没有任何错误,但是当我要获取登录信息以显示前端时,会显示错误消息。我创建了前端的功能。当单击“登录/注册”按钮时,它将转到基于角色(用户/所有者/管理员)的用户配置文件。但无法显示相关的登录用户详细信息。特别的是,当我在邮递员中运行该 API 时,该函数运行正常,没有任何错误。

error message in console

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;
}

我们需要显示登录用户自己的个人资料的详细信息,我们需要知道如何正确集成。

java mysql reactjs spring-boot postman
1个回答
0
投票

由于 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 请求之前检查所有动态参数是否已正确初始化。

© www.soinside.com 2019 - 2024. All rights reserved.