How to Pass Data from Angular 10 form to Spring Boot Rest API that includes both Strings and Files/Images?

huangapple go评论116阅读模式
英文:

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[&quot;pProfilePic&quot;])]

This is part of my Angular Form

&lt;form class=&quot;&quot; action=&quot;&quot; method=&quot;POST&quot; enctype = &quot;multipart/form-data&quot; role=&quot;form&quot; [formGroup]=&quot;form&quot; (ngSubmit)=&quot;onSubmit()&quot;&gt;

                  &lt;input id=&quot;name&quot; class=&quot;form-control&quot; type=&quot;text&quot; placeholder=&quot;Your Name&quot; formControlName=&quot;pName&quot;
                    class=&quot;form-control&quot; [ngClass]=&quot;{ &#39;is-invalid&#39;: submitted &amp;&amp; f.pName.errors }&quot;&gt;
                  &lt;div *ngIf=&quot;submitted || f.pName.invalid &amp;&amp; (f.pName.dirty || f.pName.touched)&quot;
                    class=&quot;alert alert-danger&quot;&gt;
                    &lt;div *ngIf=&quot;f.pName.errors?.required&quot;&gt;Name is required&lt;/div&gt;
                  &lt;/div&gt;

&lt;div class=&quot;form-group row mb-4&quot;&gt;
            &lt;label for=&quot;uploadPhoto&quot; class=&quot;col-md-3 col-form-label&quot;&gt;Upload Photo &lt;/label&gt;
            &lt;div class=&quot;col-md-8&quot;&gt;
              &lt;input type=&quot;text&quot; class=&quot;form-control mb-2 transparent-disabled&quot; id=&quot;uploadPhoto&quot;
                placeholder=&quot;JPEG size 200kb max&quot; [value]=&quot;filePhotoInfo&quot; disabled&gt;
               &lt;div *ngIf=&quot;submitted || f.pProfilePhoto.invalid &amp;&amp; (f.pProfilePhoto.dirty || f.pProfilePhoto.touched)&quot;
                class=&quot;alert alert-danger&quot;&gt;
                &lt;div *ngIf=&quot;f.pProfilePhoto.errors?.required&quot;&gt; Upload JPEG file and it should not exceed 200kb&lt;/div&gt;
              &lt;/div&gt; 
              &lt;button type=&quot;button&quot; class=&quot;btn btn-outline-primary btn-upload&quot;&gt;&lt;i class=&quot;fas fa fa-cloud-upload&quot;&gt;&lt;/i&gt;
                Upload File

                &lt;input
                      formControlName=&quot;pProfilePhoto&quot; 
                      type=&quot;file&quot; 
                      (change)=&quot;onPhotoSelect($event)&quot;
                      class=&quot;form-control&quot; 
                      &gt;
              &lt;/button&gt;
            &lt;/div&gt;
          &lt;/div&gt;

This is part of my Registration ts file

import { Component, OnInit } from &#39;@angular/core&#39;;
import { NgbModalConfig, NgbModal } from &#39;@ng-bootstrap/ng-bootstrap&#39;;
import { FormBuilder, FormControl, Validators } from &#39;@angular/forms&#39;;
import { ActivatedRoute, Router } from &#39;@angular/router&#39;;
import { ValidationService } from &#39;src/app/Services/validation.service&#39;;
import { PRegistrationService } from &#39;src/app/Services/P-Services/p-registration.service&#39;;
@Component({
  selector: &#39;app-p-registration&#39;,
  templateUrl: &#39;./p-registration.component.html&#39;,
  styleUrls: [&#39;./p-registration.component.css&#39;, &#39;../../../app.component.css&#39;],
  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 = &#39;static&#39;;
    config.keyboard = false;
}

ngOnInit() {
  this.form = this.formBuilder.group({
    pName: [&#39;&#39;, Validators.required],
      pProfilePhoto: [&#39;&#39;, Validators.required],
      pProfilePic: new FormControl(&#39;&#39;),
  });
}
// 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(&#39;pProfilePic&#39;, this.form.get(&#39;pProfilePic&#39;).value);
      this.PRegistrationService.pRegistration(this.form.value)
        .subscribe(result =&gt; {
          this.router.navigate([&#39;PRegistration&#39;]);
        }, (err) =&gt; {
          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 =&#39;&#39;;

  onPhotoSelect(event) {
  
    if (event.target.files.length &gt; 0) {
      const pProfilePhoto = event.target.files[0];
      console.log(&#39;this is &#39;)
      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 &#39;@angular/core&#39;;
import { Observable } from &#39;rxjs&#39;;
import {HttpClient,HttpHeaders} from &quot;@angular/common/http&quot;;
import { environment } from &#39;src/environments/environment&#39;;
import { PRegistration } from &#39;src/app/Services/P-Services/p-registration&#39;;

@Injectable({
  providedIn: &#39;root&#39;
})
export class PRegistrationService {
  constructor(private http: HttpClient) { }
  httpOptions = {
    headers: new HttpHeaders({
      &#39;Content-Type&#39;:  &#39;application/json&#39;,
    })
  }
  pRegistration(p):Observable&lt;any&gt;{

    return this.http.post&lt;PRegistration&gt;(environment.api_url+&quot;save_user&quot;,p,this.httpOptions)
  
  }
}

This is part of my model

@Entity
@Table(name = &quot;prepository&quot;)
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 = &quot;//Users//Desktop//p//&quot;;
	Path path = Paths.get(folder);
    if (!Files.exists(path)) {
    	try {
    		Files.createDirectory(path);

    		}
    	catch (IOException e) {
    		e.printStackTrace();
    		}
    	System.out.println(&quot;Directory created&quot;);
        }
    else {
    	System.out.println(&quot;Directory already exists&quot;);
    	}
    folder = folder + p.getPId() + &quot;//&quot;;
	Path path1 = Paths.get(folder);
    if (!Files.exists(path1)) {
    	try {
    		Files.createDirectory(path1);
    	}
    	catch (IOException e) {
    		e.printStackTrace();
    		}
    	System.out.println(&quot;Directory created&quot;);
        }
    else {
    	System.out.println(&quot;Directory already exists&quot;);
    	}
      Files.copy(pProfilePic_is,Paths.get(folder+pProfilePic_name),StandardCopyOption.REPLACE_EXISTING);
  
  
} catch (IOException e) {
	e.printStackTrace();
}
	
}

This is part of my controller

@PostMapping(&quot;/save_user&quot;)
	 
	
	  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.

huangapple
  • 本文由 发表于 2020年10月7日 02:04:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/64231433.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定