英文:
How to Pass Data from Angular 10 form to Spring Boot Rest API that includes both Strings and Files/Images?
问题
在尝试在Angular 10和Spring-Boot REST API之间进行通信时,我在Eclipse控制台中遇到以下错误:
2020-10-06 23:44:12.261 WARN 33762 --- [nio-8090-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token at [Source: (PushbackInputStream); line: 1, column: 535] (through reference chain: com.P.models.PModels["pProfilePic"])]
这是我的Angular表单的一部分:
<form class="" action="" method="POST" enctype="multipart/form-data" role="form" [formGroup]="form" (ngSubmit)="onSubmit()">
<input id="name" class="form-control" type="text" placeholder="Your Name" formControlName="pName"
class="form-control" [ngClass]="{ 'is-invalid': submitted && f.pName.errors }">
<div *ngIf="submitted || f.pName.invalid && (f.pName.dirty || f.pName.touched)"
class="alert alert-danger">
<div *ngIf="f.pName.errors?.required">Name is required</div>
</div>
<!-- 其他表单元素 -->
</form>
这是我的Registration TypeScript文件的一部分:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { PRegistrationService } from 'src/app/Services/P-Services/p-registration.service';
// 其他导入省略
export class PRegistrationComponent implements OnInit {
// 类定义省略
ngOnInit() {
this.form = this.formBuilder.group({
pName: ['', Validators.required],
pProfilePhoto: ['', Validators.required],
pProfilePic: new FormControl(''),
});
}
// 其他方法省略
}
这是我的Service TypeScript文件的一部分:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { environment } from 'src/environments/environment';
import { PRegistration } from 'src/app/Services/P-Services/p-registration';
@Injectable({
providedIn: 'root'
})
export class PRegistrationService {
constructor(private http: HttpClient) { }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
}
pRegistration(p): Observable<any> {
return this.http.post<PRegistration>(environment.api_url + "save_user", p, this.httpOptions)
}
}
这是我的模型(Java)的一部分:
@Entity
@Table(name = "prepository")
public class PModels {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int pId;
@Column(nullable = false)
private String pName;
private String pProfilePic;
// 构造函数、getter和setter、toString
}
这是我的Service(Java)的一部分:
public void saveUser(String pName, MultipartFile pProfilePic) {
try {
// 处理上传文件并保存到服务器
} catch (IOException e) {
e.printStackTrace();
}
}
这是我的Controller(Java)的一部分:
@PostMapping("/save_user")
public void saveUser(@RequestBody PModels pModels) {
this.pInfo.saveUser(pModels);
System.out.println(pModels.getpName()); // 仅用于检查
}
以上是您提供的代码片段的翻译。如果您需要更多帮助或有其他问题,请随时提问。
英文:
I am getting the following error in Eclipse Console when trying to communicate between Angular 10 and Spring-Boot rest Api.
2020-10-06 23:44:12.261 WARN 33762 --- [nio-8090-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 535] (through reference chain: com.P.models.PModels["pProfilePic"])]
This is part of my Angular Form
<form class="" action="" method="POST" enctype = "multipart/form-data" role="form" [formGroup]="form" (ngSubmit)="onSubmit()">
<input id="name" class="form-control" type="text" placeholder="Your Name" formControlName="pName"
class="form-control" [ngClass]="{ 'is-invalid': submitted && f.pName.errors }">
<div *ngIf="submitted || f.pName.invalid && (f.pName.dirty || f.pName.touched)"
class="alert alert-danger">
<div *ngIf="f.pName.errors?.required">Name is required</div>
</div>
<div class="form-group row mb-4">
<label for="uploadPhoto" class="col-md-3 col-form-label">Upload Photo </label>
<div class="col-md-8">
<input type="text" class="form-control mb-2 transparent-disabled" id="uploadPhoto"
placeholder="JPEG size 200kb max" [value]="filePhotoInfo" disabled>
<div *ngIf="submitted || f.pProfilePhoto.invalid && (f.pProfilePhoto.dirty || f.pProfilePhoto.touched)"
class="alert alert-danger">
<div *ngIf="f.pProfilePhoto.errors?.required"> Upload JPEG file and it should not exceed 200kb</div>
</div>
<button type="button" class="btn btn-outline-primary btn-upload"><i class="fas fa fa-cloud-upload"></i>
Upload File
<input
formControlName="pProfilePhoto"
type="file"
(change)="onPhotoSelect($event)"
class="form-control"
>
</button>
</div>
</div>
This is part of my Registration ts file
import { Component, OnInit } from '@angular/core';
import { NgbModalConfig, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { FormBuilder, FormControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ValidationService } from 'src/app/Services/validation.service';
import { PRegistrationService } from 'src/app/Services/P-Services/p-registration.service';
@Component({
selector: 'app-p-registration',
templateUrl: './p-registration.component.html',
styleUrls: ['./p-registration.component.css', '../../../app.component.css'],
providers: [NgbModalConfig, NgbModal]
})
export class PRegistrationComponent implements OnInit {
checkbox1 = false;
form: any;
loading = false;
submitted = false;
public fileData = {};
constructor(
private PRegistrationService : PRegistrationService,
config: NgbModalConfig,
private modalService: NgbModal,
private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private customValidator: ValidationService,
) {
// customize default values of modals used by this component tree
config.backdrop = 'static';
config.keyboard = false;
}
ngOnInit() {
this.form = this.formBuilder.group({
pName: ['', Validators.required],
pProfilePhoto: ['', Validators.required],
pProfilePic: new FormControl(''),
});
}
// convenience getter for easy access to form fields
get f() { return this.form.controls; }
// tslint:disable-next-line: typedef
onSubmit() {
this.submitted = true;
if (this.form.valid) {
const formData = new FormData();
formData.append('pProfilePic', this.form.get('pProfilePic').value);
this.PRegistrationService.pRegistration(this.form.value)
.subscribe(result => {
this.router.navigate(['PRegistration']);
}, (err) => {
console.log(err);
});
}
}
// tslint:disable-next-line: typedef
reset(){
this.form.reset();
}
// tslint:disable-next-line: typedef
open(content) {
this.modalService.open(content, { centered: true });
}
filePhotoInfo: string ='';
onPhotoSelect(event) {
if (event.target.files.length > 0) {
const pProfilePhoto = event.target.files[0];
console.log('this is ')
console.log(pProfilePhoto);
this.filePhotoInfo = `${pProfilePhoto.name}`;
this.form.patchValue({
pProfilePic: pProfilePhoto
});
}
}
}
This is part of my service ts file
export class PRegistration {
public pId: number;
public pName: string;
public pProfilePic: File;
constructor(
pId: number,
pName: string,
pProfilePic: File)
{
this.pId = pId;
this.pName = pName;
this.pProfilePic = pProfilePic;
}
}
This is part of my Service file
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import {HttpClient,HttpHeaders} from "@angular/common/http";
import { environment } from 'src/environments/environment';
import { PRegistration } from 'src/app/Services/P-Services/p-registration';
@Injectable({
providedIn: 'root'
})
export class PRegistrationService {
constructor(private http: HttpClient) { }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
})
}
pRegistration(p):Observable<any>{
return this.http.post<PRegistration>(environment.api_url+"save_user",p,this.httpOptions)
}
}
This is part of my model
@Entity
@Table(name = "prepository")
public class PModels {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int pId;
@Column(nullable = false)
private String pName;
private String pProfilePic;
// Constructor, getter and setter, toString
This is part of my service
public void saveUser(String pName,MultipartFile pProfilePic) {
try {
String pProfilePic_file = pProfilePic.getOriginalFilename();
InputStream pProfilePic_is = pProfilePic.getInputStream();
String pProfilePic_ts = String.valueOf(System.currentTimeMillis());
try { Thread.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}
String rand = UUID.randomUUID().toString();
String pProfilePic_name = pProfilePic_ts+rand+pProfilePic_file;
PModels p = new PModels();
p.setpName(pName);
p.setpProfilePic(pProfilePic_name);
pDao.save(p);
String folder = "//Users//Desktop//p//";
Path path = Paths.get(folder);
if (!Files.exists(path)) {
try {
Files.createDirectory(path);
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println("Directory created");
}
else {
System.out.println("Directory already exists");
}
folder = folder + p.getPId() + "//";
Path path1 = Paths.get(folder);
if (!Files.exists(path1)) {
try {
Files.createDirectory(path1);
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println("Directory created");
}
else {
System.out.println("Directory already exists");
}
Files.copy(pProfilePic_is,Paths.get(folder+pProfilePic_name),StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
This is part of my controller
@PostMapping("/save_user")
public void saveUser(@RequestBody PModels pModels) {
this.pInfo.saveUser(pModels);
System.out.println(pModels.getpName()); // Just for checking
答案1
得分: 1
在您的情况下,Spring Framework 假设传入的数据已编码为 application/json,但实际上编码为 multiparty/form-data。首先,您需要设置 @PostMapping 注释的 'consumes' 属性。然后您需要使用 @RequestPart 注释,而不是 @RequestBody 注释。
我认为这篇博文展示了如何操作。
如果我可以再提一个提示:通常,将同一个 POJO(类)既用于表示数据库实体,又用于表示 Web 服务的 DTO,并不是一个好主意。
英文:
In you case, the Spring Framework assumes that the incoming data is encoded as application/json but it is indeed encoded as multiparty/form-data. First of all, you need to set the ‚consumes‘ attribute of the @PostMapping annotation. Then you need to use the @RequestPart annotation instead of the @RequestBody one.
I think this blog post shows how to do it.
If I may add another hint: Usually, it’s not a good idea to have the same POJO (class) representing both, the DB entity and the DTO for the web service.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论