Javascript Spring引导控制器-将多部分和JSON上载到DTO

Javascript Spring引导控制器-将多部分和JSON上载到DTO,javascript,java,spring-boot,Javascript,Java,Spring Boot,我想将表单中的文件上载到Spring Boot API端点 用户界面是用React编写的: export function createExpense(formData) { return dispatch => { axios.post(ENDPOINT, formData, headers: { 'Authorization': //..., 'Content-Type': 'application/json'

我想将表单中的文件上载到Spring Boot API端点

用户界面是用React编写的:

export function createExpense(formData) {
  return dispatch => {
    axios.post(ENDPOINT,
      formData, 
      headers: {
        'Authorization': //...,
        'Content-Type': 'application/json'
      }
      ).then(({data}) => {
        //...
      })
      .catch(({response}) => {
        //...
      });
    };
}

  _onSubmit = values => {
    let formData = new FormData();
    formData.append('title', values.title);
    formData.append('description', values.description);
    formData.append('amount', values.amount);
    formData.append('image', values.image[0]);
    this.props.createExpense(formData);
  }
这是java端代码:

@RequestMapping(path = "/{groupId}", method = RequestMethod.POST)
public ExpenseSnippetGetDto create(@RequestBody ExpensePostDto expenseDto, @PathVariable long groupId, Principal principal, BindingResult result) throws IOException {
   //..
}
但我在Java方面遇到了一个例外:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryapHVvBsdZYc6j4Af;charset=UTF-8' not supported
我应该如何解决这个问题?类似的API端点和JavaScript端代码已经开始工作

注 我看到了一个解决方案,它建议请求主体应该有两个属性:一个是JSON部分下的属性,另一个是图像属性。我想看看是否有可能将其自动转换为DTO


更新1 客户端发送的上载负载应转换为以下DTO:

public class ExpensePostDto extends ExpenseBaseDto {

    private MultipartFile image;

    private String description;

    private List<Long> sharers;

}

在前端,去掉
内容类型
,因为它应该由浏览器本身决定,并使用
FormData
(标准JavaScript)。这应该可以解决问题。

我用AngularJS和SpringBoot构建了我最新的文件上传应用程序,它们在语法上非常相似,可以在这里帮助您

我的客户端请求处理程序:

uploadFile=function(fileData){
    var formData=new FormData();
    formData.append('file',fileData);
    return $http({
        method: 'POST',
        url: '/api/uploadFile',
        data: formData,
        headers:{
            'Content-Type':undefined,
            'Accept':'application/json'
        }
    });
};
需要注意的是,Angular会自动为我设置“Content type”头值上的多部分mime类型和边界。你的可能不会,在这种情况下,你需要自己设置它

我的应用程序需要来自服务器的JSON响应,因此需要“Accept”头

您自己正在传递FormData对象,因此需要确保表单将文件设置为您在控制器上映射到的任何属性。在我的例子中,它被映射到FormData对象上的“file”参数

我的控制器端点如下所示:

@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file) 
{
    if (file.isEmpty()) {
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else {
        //...
    }
}
uploadFile=function(fileData, otherData){
    var formData=new FormData();
    formData.append('file',fileData);
    formData.append('expenseDto',otherData);
    return $http({
        method: 'POST',
        url: '/api/uploadFile',
        data: formData,
        headers:{
            'Content-Type':undefined,
            'Accept':'application/json'
        }
    });
};
@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file, @RequestParam("expenseDto") ExpensePostDto expenseDto)
{
    if (file.isEmpty()) {
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else {
        //...
    }
}
然后,控制器端点将如下所示:

@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file) 
{
    if (file.isEmpty()) {
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else {
        //...
    }
}
uploadFile=function(fileData, otherData){
    var formData=new FormData();
    formData.append('file',fileData);
    formData.append('expenseDto',otherData);
    return $http({
        method: 'POST',
        url: '/api/uploadFile',
        data: formData,
        headers:{
            'Content-Type':undefined,
            'Accept':'application/json'
        }
    });
};
@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file, @RequestParam("expenseDto") ExpensePostDto expenseDto)
{
    if (file.isEmpty()) {
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else {
        //...
    }
}
@POST
@请求映射(“/upload”)
公共响应上传(@RequestParam(“文件”)多部分文件,@RequestParam(“expenseDto”)ExpensePostDto expenseDto)
{
if(file.isEmpty()){
返回新的响应属性(HttpStatus.BAD_请求);
}否则{
//...
}
}

将使用者类型添加到您的请求映射中。它应该可以正常工作

@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file,consumes = "multipart/form-data") 
{
    if (file.isEmpty()) {
        return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
    } else {
        //...
    }
}
@POST
@请求映射(“/upload”)
公共响应性上载(@RequestParam(“文件”)MultipartFile file,consumes=“multipart/form data”)
{
if(file.isEmpty()){
返回新的响应属性(HttpStatus.BAD_请求);
}否则{
//...
}
}

是的,您可以通过包装器类简单地完成

1) 创建一个
以保存表单数据:

public class FormWrapper {
    private MultipartFile image;
    private String title;
    private String description;
}
2) 创建用于提交数据的HTML
表单

<form method="POST" enctype="multipart/form-data" id="fileUploadForm" action="link">
    <input type="text" name="title"/><br/>
    <input type="text" name="description"/><br/><br/>
    <input type="file" name="image"/><br/><br/>
    <input type="submit" value="Submit" id="btnSubmit"/>
</form>
4) 保存
文件的方法

private void saveUploadedFile(MultipartFile file) throws IOException {
    if (!file.isEmpty()) {
        byte[] bytes = file.getBytes();
        Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
        Files.write(path, bytes);
    }
}

我使用纯JS和Spring Boot创建了一个类似的东西。 这是你的电话号码 我将
用户
对象作为
JSON
发送,并将
文件
作为
多部分/表单数据
请求的一部分发送

下面是相关的代码片段

控制器
代码

@RestController
public class FileUploadController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = { "multipart/form-data" })
    public void upload(@RequestPart("user") @Valid User user,
            @RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {
            System.out.println(user);
            System.out.println("Uploaded File: ");
            System.out.println("Name : " + file.getName());
            System.out.println("Type : " + file.getContentType());
            System.out.println("Name : " + file.getOriginalFilename());
            System.out.println("Size : " + file.getSize());
    }

    static class User {
        @NotNull
        String firstName;
        @NotNull
        String lastName;

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

        @Override
        public String toString() {
            return "User [firstName=" + firstName + ", lastName=" + lastName + "]";
        }

    }
}
<html>    
<head>
    <script>
        function onSubmit() {

            var formData = new FormData();

            formData.append("file", document.forms["userForm"].file.files[0]);
            formData.append('user', new Blob([JSON.stringify({
                "firstName": document.getElementById("firstName").value,
                "lastName": document.getElementById("lastName").value
            })], {
                    type: "application/json"
                }));
            var boundary = Math.random().toString().substr(2);
            fetch('/upload', {
                method: 'post',
                body: formData
            }).then(function (response) {
                if (response.status !== 200) {
                    alert("There was an error!");
                } else {
                    alert("Request successful");
                }
            }).catch(function (err) {
                alert("There was an error!");
            });;
        }
    </script>
</head>

<body>
    <form name="userForm">
        <label> File : </label>
        <br/>
        <input name="file" type="file">
        <br/>
        <label> First Name : </label>
        <br/>
        <input id="firstName" name="firstName" />
        <br/>
        <label> Last Name : </label>
        <br/>
        <input id="lastName" name="lastName" />
        <br/>
        <input type="button" value="Submit" id="submit" onclick="onSubmit(); return false;" />
    </form>
</body>    
</html>
HTML
JS
code

@RestController
public class FileUploadController {

    @RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = { "multipart/form-data" })
    public void upload(@RequestPart("user") @Valid User user,
            @RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {
            System.out.println(user);
            System.out.println("Uploaded File: ");
            System.out.println("Name : " + file.getName());
            System.out.println("Type : " + file.getContentType());
            System.out.println("Name : " + file.getOriginalFilename());
            System.out.println("Size : " + file.getSize());
    }

    static class User {
        @NotNull
        String firstName;
        @NotNull
        String lastName;

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

        @Override
        public String toString() {
            return "User [firstName=" + firstName + ", lastName=" + lastName + "]";
        }

    }
}
<html>    
<head>
    <script>
        function onSubmit() {

            var formData = new FormData();

            formData.append("file", document.forms["userForm"].file.files[0]);
            formData.append('user', new Blob([JSON.stringify({
                "firstName": document.getElementById("firstName").value,
                "lastName": document.getElementById("lastName").value
            })], {
                    type: "application/json"
                }));
            var boundary = Math.random().toString().substr(2);
            fetch('/upload', {
                method: 'post',
                body: formData
            }).then(function (response) {
                if (response.status !== 200) {
                    alert("There was an error!");
                } else {
                    alert("Request successful");
                }
            }).catch(function (err) {
                alert("There was an error!");
            });;
        }
    </script>
</head>

<body>
    <form name="userForm">
        <label> File : </label>
        <br/>
        <input name="file" type="file">
        <br/>
        <label> First Name : </label>
        <br/>
        <input id="firstName" name="firstName" />
        <br/>
        <label> Last Name : </label>
        <br/>
        <input id="lastName" name="lastName" />
        <br/>
        <input type="button" value="Submit" id="submit" onclick="onSubmit(); return false;" />
    </form>
</body>    
</html>

函数onSubmit(){
var formData=new formData();
formData.append(“文件”,document.forms[“userForm”].file.files[0]);
append('user',新Blob([JSON.stringify({
“firstName”:document.getElementById(“firstName”).value,
“lastName”:document.getElementById(“lastName”).value
})], {
类型:“application/json”
}));
var boundary=Math.random().toString().substr(2);
获取(“/upload”{
方法:“post”,
正文:formData
}).然后(功能(响应){
如果(response.status!==200){
警报(“出现错误!”);
}否则{
警报(“请求成功”);
}
}).catch(函数(err){
警报(“出现错误!”);
});;
}
文件:


名字:

姓氏:

@RequestMapping(值={/test},方法={RequestMethod.POST})
@应答器
公共字符串创建(@RequestParam(“文件”)多部分文件、@RequestParam字符串描述、@RequestParam ArrayList sharers)引发异常{
ExpensePostDto ExpensePostDto=新的ExpensePostDto(文件、说明、共享者);
//做你的事
返回“测试”;
}

这似乎是最简单的解决方法,其他方法可以是添加您自己的messageConverter。

您必须通过将
consumes=“multipart/form data”
添加到
RequestMapping
注释来告诉spring您正在使用
multipart/form data
。同时从
expenseDto
参数中删除
RequestBody
注释

@RequestMapping(path = "/{groupId}", consumes = "multipart/form-data", method = RequestMethod.POST)
public ExpenseSnippetGetDto create(ExpensePostDto expenseDto, 
   @PathVariable long groupId, Principal principal, BindingResult result) 
   throws IOException {
   //..
}
ExpensePostDto
过账后,请求中的
标题将被忽略

编辑

您还需要将内容类型更改为
multipart/formdata
。根据其他一些答案,这似乎是
post
的默认设置。为了安全起见,我将具体说明:

'Content-Type': 'multipart/form-data'

将其从反应器前端拆下:

 'Content-Type': 'application/json'
修改Java端控制器:

   @PostMapping("/{groupId}")
   public Expense create(@RequestParam("image") MultipartFile image,  @RequestParam("amount") double amount, @RequestParam("description") String description, @RequestParam("title") String title) throws IOException {
         //storageService.store(file); ....
          //String imagePath = path.to.stored.image;
         return new Expense(amount, title, description, imagePath);
 }
@RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = {   "multipart/form-data" })
@ResponseBody
public boolean uploadImage(@RequestPart("obj") YourDTO dto, @RequestPart("file") MultipartFile file) {
    // your logic
    return true;
}

这可以写得更好,但我尽可能地保持它与原始代码尽可能接近。我希望它能有所帮助。

我有一个类似的用例,我上传了一些JSON数据和图像(把它想象成一个试图注册个人详细信息和配置文件图像的用户)

参考@Stephan和@GSSwain的回答,我想出了一个使用Spring Boot和AngularJs的解决方案

下面是我的代码的快照。希望它能帮助别人

    var url = "https://abcd.com/upload";
    var config = {
        headers : {
            'Content-Type': undefined
        }

    }
    var data = {
        name: $scope.name,
        email: $scope.email
    }
    $scope.fd.append("obj", new Blob([JSON.stringify(data)], {
                type: "application/json"
            }));

    $http.post(
        url, $scope.fd,config
    )
        .then(function (response) {
            console.log("success", response)
            // This function handles success

        }, function (response) {
            console.log("error", response)
            // this function handles error

        });
和SpringBoot控制器:

   @PostMapping("/{groupId}")
   public Expense create(@RequestParam("image") MultipartFile image,  @RequestParam("amount") double amount, @RequestParam("description") String description, @RequestParam("title") String title) throws IOException {
         //storageService.store(file); ....
          //String imagePath = path.to.stored.image;
         return new Expense(amount, title, description, imagePath);
 }
@RequestMapping(value = "/upload", method = RequestMethod.POST, consumes = {   "multipart/form-data" })
@ResponseBody
public boolean uploadImage(@RequestPart("obj") YourDTO dto, @RequestPart("file") MultipartFile file) {
    // your logic
    return true;
}

您的内容类型不正确,FormData不生成
application/json
我将其更改为
multipart/form data
,但我仍然收到相同的错误。这正是您需要的:我仍然收到相同的错误。你的endpoi