Angular+Spring Boot上传图片至PostgreSQL报非multipart请求错误求助
文件上传接口报错
Current request is not a multipart request排查修复 问题根因
一共3个核心问题导致报错:
- 后端接口注解冲突:
@RequestBody用于接收application/json格式的请求体,@RequestParam MultipartFile需要请求是multipart/form-data格式,二者不能在同一个接口方法上同时使用,Spring无法同时解析两种格式的请求。 - 前端未正确构造上传请求:你在TS里直接把普通字段序列化成JSON对象发POST请求,完全没有处理选中的文件,也没有用multipart要求的
FormData格式传参。模板上写的enctype="multipart/form-data"只对原生无JS的表单提交生效,用HttpClient发AJAX请求时这个属性不会生效。 - 前端文件输入框没有绑定逻辑:文件选择框既没有加表单控件,也没有绑定change事件,用户选的文件根本没有被组件获取到,不会随请求发给后端。
修复步骤
1. 调整后端Controller代码
给接口指定消费multipart格式数据,把@RequestBody换成@ModelAttribute,让Spring自动把multipart请求里的普通字段绑定到DTO对象上:
// 注意加consumes限定接收的请求格式 @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity<Invoice> addInvoice(@ModelAttribute InvoiceDTO invoice , @RequestParam("image") MultipartFile multipartFile) throws IOException { String fileName = StringUtils.cleanPath(multipartFile.getOriginalFilename()); invoice.setPhoto(fileName); Invoice savedInvoice = invoiceService.saveInvoice(invoice); String uploadDir = "user-photos/" + savedInvoice.getId(); FileUpload.saveFile(uploadDir, fileName, multipartFile); LOGGER.info(" invoice saved " + invoice.getSerialNumber() + "and id : " + invoice.getId()); return new ResponseEntity<>(savedInvoice, HttpStatus.CREATED); }
你写的FileUpload工具类逻辑没有问题,不需要修改。
2. 修改Angular前端模板
给文件选择框绑定change事件,获取用户选中的文件:
<div class="addreq"> <form class="form" [formGroup]="addstudentform"> <h1 class="header">Add new invoice</h1> <input formControlName="serialNumber" type="number" placeholder="serialNumber"><br> <input formControlName="status" type="text" placeholder="status"><br> <input formControlName="customerSerialNumber" type="text" placeholder="customer serial number"><br> <input formControlName="employeeSerialNumber" type="text" placeholder="employee serial number"><br> <!-- 绑定文件选择事件 --> <input type="file" name="image" accept="image/png, image/jpeg" (change)="onFileSelected($event)" /> <button (click)="onSave()" type="submit" class="main-btn">Save</button> </form> </div>
3. 修改Angular TS业务逻辑
新增存储选中文件的属性,提交时构造FormData对象传参,不要手动设置Content-Type请求头,HttpClient会自动给FormData生成带正确boundary的multipart请求头,手动设置反而会丢失boundary导致解析失败:
export class AddInvoiceComponent implements OnInit { // 存储用户选中的上传文件 selectedFile: File | null = null; addstudentform: FormGroup = this._formbuilder.group({ serialNumber: ['', Validators.required], status: ['', Validators.required], customerSerialNumber: ['', Validators.required], employeeSerialNumber: ['', Validators.required], }); constructor(private _formbuilder: FormBuilder, private _http: HttpClient) { } ngOnInit(): void { } // 处理文件选择 onFileSelected(event: Event): void { const inputEl = event.target as HTMLInputElement; if (inputEl.files?.length) { this.selectedFile = inputEl.files[0]; } } onSave(): void { if (!this.selectedFile) { alert('请先选择要上传的发票图片'); return; } // 构造multipart请求需要的FormData const submitData = new FormData(); submitData.append('serialNumber', this.addstudentform.get('serialNumber')?.value); submitData.append('status', this.addstudentform.get('status')?.value); submitData.append('customerSerialNumber', this.addstudentform.get('customerSerialNumber')?.value); submitData.append('employeeSerialNumber', this.addstudentform.get('employeeSerialNumber')?.value); // 文件字段名必须和后端@RequestParam指定的"image"一致 submitData.append('image', this.selectedFile, this.selectedFile.name); this._http.post("http://localhost:8085/invoice/save", submitData).subscribe({ next: (res) => console.log('发票保存成功', res), error: (err) => console.error('保存失败', err) }); } }
改完以上内容重启后端服务、刷新前端页面,就能正常上传文件、保存发票数据,文件名会正常写入数据库的photo字段,文件也会存到你指定的user-photos/{发票id}目录下。
内容的提问来源于stack exchange,提问作者Talia Dianal
相关产品推荐
相关产品推荐

