Spring Boot controller - Upload Multipart and JSON to DTO

前端 未结 8 1097
南旧
南旧 2020-11-28 05:16

I want to upload a file inside a form to a Spring Boot API endpoint.

The UI is written in React:

export function createExpense(formData) {
  return         


        
相关标签:
8条回答
  • 2020-11-28 05:22

    I had a similar use case where I had some JSON data and image upload (Think of it as a user trying to register with a personal details and profile image).

    Referring to @Stephan and @GSSwain answer I came up with a solution with Spring Boot and AngularJs.

    Below is a snapshot of my code. Hope it helps someone.

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

    And SpringBoot controller:

    @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;
    }
    
    0 讨论(0)
  • 2020-11-28 05:23

    I had created a similar thing using pure JS and Spring Boot. Here is the Repo. I'm sending an User object as JSON and a File as part of the multipart/form-data request.

    The relevant snippets are below

    The Controller 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 + "]";
            }
    
        }
    }
    

    The HTML and JS code

    <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>
    
    0 讨论(0)
  • 2020-11-28 05:26

    Yes, you can simply do it via wrapper class.

    1) Create a Class to hold form data:

    public class FormWrapper {
        private MultipartFile image;
        private String title;
        private String description;
    }
    

    2) Create an HTML form for submitting data:

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

    3) Create a method to receive form's text data and multipart file:

    @PostMapping("/api/upload/multi/model")
    public ResponseEntity<?> multiUploadFileModel(@ModelAttribute FormWrapper model) {
        try {
            // Save as you want as per requiremens
            saveUploadedFile(model.getImage());
            formRepo.save(mode.getTitle(), model.getDescription());
        } catch (IOException e) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
    
        return new ResponseEntity("Successfully uploaded!", HttpStatus.OK);
    }
    

    4) Method to save file:

    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);
        }
    }
    
    0 讨论(0)
  • 2020-11-28 05:26

    Remove this from the react front end:

     'Content-Type': 'application/json'
    

    Modify the Java side controller:

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

    This can be written better but tried keeping it as close to your original code as much as I could. I hope it helps.

    0 讨论(0)
  • 2020-11-28 05:40

    I built my most recent file upload application in AngularJS and SpringBoot which are similar enough in syntax to help you here.

    My client side request handler:

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

    One thing to note is Angular automatically sets the multipart mime type and boundary on the 'Content-Type' header value for me. Yours may not, in which case you need to set it yourself.

    My application expects a JSON response from the server, thus the 'Accept' header.

    You are passing in the FormData object yourself, so you need to make sure that your form is setting the File to whatever attribute you map to on your Controller. In my case it is mapped to the 'file' parameter on the FormData object.

    My controller endpoints look like this:

    @POST
    @RequestMapping("/upload")
    public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file) 
    {
        if (file.isEmpty()) {
            return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
        } else {
            //...
        }
    }
    

    You can add as many other @RequestParam as you'd like, including your DTO that represents the rest of the form, just make sure its structured that way as a child of the FormData object.

    The key take-away here is that each @RequestParam is an attribute on the FormData object body payload on the multipart request.

    If I were to modify my code to accommodate your data, it would look something like this:

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

    Then your controller endpoint would look like this:

    @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 {
            //...
        }
    }
    
    0 讨论(0)
  • 2020-11-28 05:41
    @RequestMapping(value = { "/test" }, method = { RequestMethod.POST })
    @ResponseBody
    public String create(@RequestParam("file") MultipartFile file, @RequestParam String description, @RequestParam ArrayList<Long> sharers) throws Exception {
        ExpensePostDto expensePostDto = new ExpensePostDto(file, description, sharers);
        // do your thing
        return "test";
    }
    

    This seems to be the easiest way out here, other ways could be to add your own messageConverter.

    0 讨论(0)
提交回复
热议问题