使用 JSON 的 Spring MVC 多部件请求

我想使用 Spring MVC 发布一个包含 JSON 数据的文件

@RequestMapping(value = "/servicegenerator/wsdl", method = RequestMethod.POST,consumes = { "multipart/mixed", "multipart/form-data" })
@ResponseBody
public String generateWSDLService(@RequestPart("meta-data") WSDLInfo wsdlInfo,@RequestPart("file") MultipartFile file) throws WSDLException, IOException,
JAXBException, ParserConfigurationException, SAXException, TransformerException {
return handleWSDL(wsdlInfo,file);
}

当我发送来自 rest 客户端的请求时 content-Type = multipart/form-data or multipart/mixed,我得到下一个例外: org.springframework.web.multipart.support.MissingServletRequestPartException

有人能帮我解决这个问题吗?

我可以使用 @RequestPart将 Multipart 和 JSON 发送到服务器吗?

157836 次浏览

As documentation says:

Raised when the part of a "multipart/form-data" request identified by its name cannot be found.

This may be because the request is not a multipart/form-data either because the part is not present in the request, or because the web application is not configured correctly for processing multipart requests -- e.g. no MultipartResolver.

This is how I implemented Spring MVC Multipart Request with JSON Data.

Multipart Request with JSON Data (also called Mixed Multipart):

Based on RESTful service in Spring 4.0.2 Release, HTTP request with the first part as XML or JSON formatted data and the second part as a file can be achieved with @RequestPart. Below is the sample implementation.

Java Snippet:

Rest service in Controller will have mixed @RequestPart and MultipartFile to serve such Multipart + JSON request.

@RequestMapping(value = "/executesampleservice", method = RequestMethod.POST,
consumes = {"multipart/form-data"})
@ResponseBody
public boolean executeSampleService(
@RequestPart("properties") @Valid ConnectionProperties properties,
@RequestPart("file") @Valid @NotNull @NotBlank MultipartFile file) {
return projectService.executeSampleService(properties, file);
}

Front End (JavaScript) Snippet:

  1. Create a FormData object.

  2. Append the file to the FormData object using one of the below steps.

    1. If the file has been uploaded using an input element of type "file", then append it to the FormData object. formData.append("file", document.forms[formName].file.files[0]);
    2. Directly append the file to the FormData object. formData.append("file", myFile, "myfile.txt"); OR formData.append("file", myBob, "myfile.txt");
  3. Create a blob with the stringified JSON data and append it to the FormData object. This causes the Content-type of the second part in the multipart request to be "application/json" instead of the file type.

  4. Send the request to the server.

  5. Request Details:
    Content-Type: undefined. This causes the browser to set the Content-Type to multipart/form-data and fill the boundary correctly. Manually setting Content-Type to multipart/form-data will fail to fill in the boundary parameter of the request.

Javascript Code:

formData = new FormData();


formData.append("file", document.forms[formName].file.files[0]);
formData.append('properties', new Blob([JSON.stringify({
"name": "root",
"password": "root"
})], {
type: "application/json"
}));

Request Details:

method: "POST",
headers: {
"Content-Type": undefined
},
data: formData

Request Payload:

Accept:application/json, text/plain, */*
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryEBoJzS3HQ4PgE1QB


------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
Content-Disposition: form-data; name="file"; filename="myfile.txt"
Content-Type: application/txt




------WebKitFormBoundaryvijcWI2ZrZQ8xEBN
Content-Disposition: form-data; name="properties"; filename="blob"
Content-Type: application/json




------WebKitFormBoundaryvijcWI2ZrZQ8xEBN--

This must work!

client (angular):

$scope.saveForm = function () {
var formData = new FormData();
var file = $scope.myFile;
var json = $scope.myJson;
formData.append("file", file);
formData.append("ad",JSON.stringify(json));//important: convert to JSON!
var req = {
url: '/upload',
method: 'POST',
headers: {'Content-Type': undefined},
data: formData,
transformRequest: function (data, headersGetterFunction) {
return data;
}
};

Backend-Spring Boot:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public @ResponseBody
Advertisement storeAd(@RequestPart("ad") String adString, @RequestPart("file") MultipartFile file) throws IOException {


Advertisement jsonAd = new ObjectMapper().readValue(adString, Advertisement.class);
//do whatever you want with your file and jsonAd

We've seen in our projects that a post request with JSON and files is creating a lot of confusion between the frontend and backend developers, leading to unnecessary wastage of time.

Here's a better approach: convert file bytes array to Base64 string and send it in the JSON.

public Class UserDTO {
private String firstName;
private String lastName;
private FileDTO profilePic;
}


public class FileDTO {
private String base64;
// just base64 string is enough. If you want, send additional details
private String name;
private String type;
private String lastModified;
}


@PostMapping("/user")
public String saveUser(@RequestBody UserDTO user) {
byte[] fileBytes = Base64Utils.decodeFromString(user.getProfilePic().getBase64());
....
}

JS code to convert file to base64 string:

var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function () {


const userDTO = {
firstName: "John",
lastName: "Wick",
profilePic: {
base64: reader.result,
name: file.name,
lastModified: file.lastModified,
type: file.type
}
}
  

// post userDTO
};
reader.onerror = function (error) {
console.log('Error: ', error);
};

For Angular2+ users. Try to send JSON payload in a mixed part request as below.

formData.append("jsonPayload", new Blob([JSON.stringify(json)], {
type: "application/json"
}));

Given below complete function.

submit() {
const formData = new FormData();
formData.append('file', this.myForm.get('fileSource').value);
var json = {
"key":"value"
};


formData.append("jsonPayload", new Blob([JSON.stringify(json)], {
type: "application/json"
}));


this.http.post('http://localhost:8080/api/mixed-part-endpoint', formData)
.subscribe(res => {
console.log(res);
alert('Uploaded Successfully.');
})
}

You can also use the next way a list List<MultipartFile> and @RequestPart("myObj") as parameters in your method inside a @RestController


@PostMapping()
@ResponseStatus(HttpStatus.CREATED)
public String create(@RequestPart("file") List<MultipartFile> files, @RequestPart("myObj") MyJsonDTOClass myObj) throws GeneralSecurityException, IOException {
// your code


}

and in the axios side with a bit of react:

            const jsonStr = JSON.stringify(myJsonObj);
const blob = new Blob([jsonStr], {
type: 'application/json'
});


let formData = new FormData();
formData.append("myObj",blob );
formData.append("file", this.state.fileForm); // check your control
let url = `your url`
let method = `post`
let headers =
{
'Accept': 'application/json',
'Content-Type': 'application/json'
};
}


axios({
method,
url,
data: formData,
headers
}).then(res => {
console.log(res);
console.log(res.data);
});