SpringBoot+React前后端POST请求报400 Bad Request求助
问题描述
我正在学习SpringBoot Java和React,尝试实现前后端交互以向数据库提交POST请求(当前Controller方法为测试用)。我的GET请求一切正常,且为了自行编写SQL查询,刻意不使用Axios等工具库。
目前遇到的问题是:运行应用时前后端通信失败,收到400 Bad Request错误,具体报错信息如下:
POST http://localhost:3000/addFormDataToDatabase 400 (Bad Request)
addFormDataToDatabase @ FormComponent.jsx:120
我明白问题大概率出在前端请求格式,但尝试多种方式后仍未解决。以下是当前最接近可行的代码,仍返回该错误,希望得到问题排查的帮助与建议。
前端代码(FormComponent.jsx,已剔除无关验证方法)
import React, { Component } from 'react' import AdminComponent from './AdminComponent.jsx' class FormComponent extends Component { constructor(props){ super(props) this.state = { rentalCars: [] }; this.addFormDataToDatabase = this.addFormDataToDatabase.bind(this); } async componentDidMount() { await this.getAllRentalCars(); } async getAllRentalCars() { const response = await fetch('/rent'); const data = await response.json(); console.log("GetRentalCars received from database: " + JSON.stringify(data)); this.setState({ rentalCars: data }); } render() { const {rentalCars} = this.state; const optionItems = rentalCars.map(({id, carName, rentalPricePerDay}, key) => <option key={id} value={carName}> {carName}: {rentalPricePerDay} SEK/day</option>) return ( <div className="App"> <div className="card col-md-6 offset-md-3 offset-md-3"> <div className="card-body"> <header className="rental-cars"> <h2 className="text-center">Rent one of our cars!</h2> </header> <form className="registerForm" onSubmit={this.handleSubmit}> <div className="form-group"> <label>First name: </label> <input className="form-control" required type="text" name="firstName" value={this.state.firstName} /> <label>Last name: </label> <input className="form-control" required type="text" name="lastName" value={this.state.lastName} /> <label>Date of Birth: </label> <input className="form-control" required type="date" name="dateOfBirth" value={this.state.dateOfBirth} /> <label>I want to rent a car from </label> <input className="form-control" required type="date" name="startDate" value={this.state.startDate} /> <label> to </label> <input className="form-control" required type="date" name="endDate" value={this.state.endDate} /> <label>Car I wish to rent: </label> <select className="form-control" name="selectCar" ref={node => (this.inputNode = node)}> {optionItems} </select> <input type="submit" className="btn btn-primary" value="Submit"/> </div> </form> </div> </div> </div> ); } handleSubmit = (event) => { event.preventDefault(); let target = event.target; let formData = []; for (let i = 0; i < target.length; i++) { formData[target.elements[i].getAttribute("name")] = target.elements[i].value; } let validationErrors = this.validateValues(formData); for (let j = 0; j < validationErrors.length; j++){ console.log("Error:" + validationErrors[j]); } console.log(validationErrors); console.log('formData', formData); if(!!validationErrors){ this.addFormDataToDatabase(formData); } } async addFormDataToDatabase(formData){ console.log('formData inside method addFormDataToDatabase', formData); const requestOptions = { method: 'POST', headers: {'Content-type': 'application/json'}, body: formData }; const response = await fetch('/addFormDataToDatabase', requestOptions); const message = await response.json(); console.log("addFormDataToDatabase received from backend: " + JSON.stringify(message)); } }
后端Controller代码
@RestController @RequestMapping("/") public class RentalCarApplicationController { @Autowired private RentalCarRepository rentalCarRepository; private RenterRepository renterRepository; private CarRentalDataRepository carRentalDataRepository; @GetMapping("/rent") public List<RentalCar> getRentalCars(){ return rentalCarRepository.getRentalCars(); } @PostMapping("/addFormDataToDatabase") public List<String> addFormDataToDatabase(@NotNull @RequestBody List<String> bookingData){ return (bookingData); } }
问题排查与解决建议
核心问题分析
400 Bad Request的直接原因是前后端数据格式完全不匹配:
- 前端用数组字面量
[]创建formData,但实际存储的是键值对,本质是对象;同时声明了Content-type: application/json,却没有把对象序列化为JSON字符串,后端无法解析。 - 后端Controller接收的是
List<String>类型,但前端传递的是键值对结构的表单数据,Spring无法将其解析为字符串列表。
具体修改步骤
1. 前端请求格式修正
- 将
formData改为对象(存储键值对更合理),过滤掉无name属性的元素(比如提交按钮)。 - 提交时用
JSON.stringify()将对象转为JSON字符串。 - 修正验证错误的判断逻辑(空数组转为布尔值是
true,应该判断长度)。
修改后的相关方法:
handleSubmit = (event) => { event.preventDefault(); let target = event.target; let formData = {}; // 改为对象存储键值对 for (let i = 0; i < target.length; i++) { const name = target.elements[i].getAttribute("name"); if (name) { // 过滤无name的元素 formData[name] = target.elements[i].value; } } let validationErrors = this.validateValues(formData); for (let j = 0; j < validationErrors.length; j++){ console.log("Error:" + validationErrors[j]); } console.log(validationErrors); console.log('formData', formData); // 只有无验证错误时才提交 if(validationErrors.length === 0){ this.addFormDataToDatabase(formData); } } async addFormDataToDatabase(formData){ console.log('formData inside method addFormDataToDatabase', formData); const requestOptions = { method: 'POST', headers: {'Content-type': 'application/json'}, body: JSON.stringify(formData) // 序列化为JSON字符串 }; try { const response = await fetch('/addFormDataToDatabase', requestOptions); if (!response.ok) { throw new Error(`请求失败,状态码: ${response.status}`); } const message = await response.json(); console.log("后端返回数据: " + JSON.stringify(message)); } catch (error) { console.error('请求出错:', error); } }
2. 后端接收格式修正
前端传递的是键值对JSON,后端需要用实体类接收,而非List<String>。创建一个DTO类:
import java.time.LocalDate; public class BookingRequest { private String firstName; private String lastName; private LocalDate dateOfBirth; private LocalDate startDate; private LocalDate endDate; private String selectCar; // 生成无参构造方法、Getter和Setter public BookingRequest() {} public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public LocalDate getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; } public LocalDate getStartDate() { return startDate; } public void setStartDate(LocalDate startDate) { this.startDate = startDate; } public LocalDate getEndDate() { return endDate; } public void setEndDate(LocalDate endDate) { this.endDate = endDate; } public String getSelectCar() { return selectCar; } public void setSelectCar(String selectCar) { this.selectCar = selectCar; } }
修改Controller方法:
@PostMapping("/addFormDataToDatabase") public BookingRequest addFormDataToDatabase(@NotNull @RequestBody BookingRequest bookingData){ // 后续可调用repository将数据存入数据库 return bookingData; // 测试用:返回接收的参数 }
额外注意事项
- 前端表单绑定问题:当前
input的value绑定了state字段,但state未初始化这些值,会导致输入无法更新状态。需在constructor中添加初始值,并给每个input加onChange事件:
constructor(props){ super(props) this.state = { rentalCars: [], firstName: '', lastName: '', dateOfBirth: '', startDate: '', endDate: '' }; this.addFormDataToDatabase = this.addFormDataToDatabase.bind(this); } // 示例:firstName的input添加onChange <input className="form-control" required type="text" name="firstName" value={this.state.firstName} onChange={(e) => this.setState({ firstName: e.target.value })} />
- 跨域问题:如果前端在3000端口、后端在8080端口,需给Controller添加跨域注解:
@RestController @RequestMapping("/") @CrossOrigin(origins = "http://localhost:3000") // 允许前端跨域请求 public class RentalCarApplicationController { // ... }
内容的提问来源于stack exchange,提问作者Zarkaylia
相关产品推荐
相关产品推荐

