Java Spring Boot 控制器 - 将 Multipart 和 JSON 上传到 DTO
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/49845355/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot controller - Upload Multipart and JSON to DTO
提问by Arian
I want to upload a file inside a form to a Spring Boot API endpoint.
我想将表单内的文件上传到 Spring Boot API 端点。
The UI is written in React:
UI 是用 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);
}
This is the java side code:
这是java端代码:
@RequestMapping(path = "/{groupId}", method = RequestMethod.POST)
public ExpenseSnippetGetDto create(@RequestBody ExpensePostDto expenseDto, @PathVariable long groupId, Principal principal, BindingResult result) throws IOException {
//..
}
But I get this exception on the Java side:
但是我在 Java 端遇到了这个异常:
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryapHVvBsdZYc6j4Af;charset=UTF-8' not supported
How should I resolve this issue? The similar API endpoints and JavaScript side code is already working.
我应该如何解决这个问题?类似的 API 端点和 JavaScript 端代码已经在工作。
Note
笔记
I've seen a solution where it suggests that the request body should have 2 attributes: one which the JSON section goes under, another for the image. I'd like to see if it is possible to have it automatically converted to DTO.
我见过一个解决方案,它建议请求正文应该有 2 个属性:一个是 JSON 部分,另一个是图像。我想看看是否可以将它自动转换为 DTO。
Update 1
更新 1
The upload payload sent by the client should be converted to the following DTO:
客户端发送的上传负载应转换为以下 DTO:
public class ExpensePostDto extends ExpenseBaseDto {
private MultipartFile image;
private String description;
private List<Long> sharers;
}
So you can say it's a mix of JSON and multipart.
所以你可以说它是 JSON 和multipart的混合体。
Solution
解决方案
The solution to the problem is to use FormData
on the front-end and ModelAttribute
on the backend:
问题的解决方法是FormData
在前端和ModelAttribute
后端使用:
@RequestMapping(path = "/{groupId}", method = RequestMethod.POST,
consumes = {"multipart/form-data"})
public ExpenseSnippetGetDto create(@ModelAttribute ExpensePostDto expenseDto, @PathVariable long groupId, Principal principal) throws IOException {
//...
}
and on the front-end, get rid of Content-Type
as it should be determined by the browser itself, and use FormData
(standard JavaScript). That should solve the problem.
在前端,去掉Content-Type
应该由浏览器本身决定的,并使用FormData
(标准JavaScript)。那应该可以解决问题。
采纳答案by UsamaAmjad
Yes, you can simply do it via wrapper class.
是的,您可以简单地通过包装类来完成。
1) Create a Class
to hold form data:
1)创建一个Class
来保存表单数据:
public class FormWrapper {
private MultipartFile image;
private String title;
private String description;
}
2) Create an HTML form
for submitting data:
2)创建form
用于提交数据的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>
3) Create a method to receive form's text
data and multipart
file:
3)创建一个方法来接收表单的text
数据和multipart
文件:
@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
:
4)保存方法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);
}
}
回答by Stephan
I built my most recent file upload application in AngularJS and SpringBoot which are similar enough in syntax to help you here.
我在 AngularJS 和 SpringBoot 中构建了我最近的文件上传应用程序,它们的语法非常相似,可以帮助您。
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.
需要注意的一件事是,Angular 会自动为我在“Content-Type”标头值上设置多部分 MIME 类型和边界。你的可能没有,在这种情况下你需要自己设置。
My application expects a JSON response from the server, thus the 'Accept' header.
我的应用程序需要来自服务器的 JSON 响应,因此是“接受”标头。
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.
您自己传入 FormData 对象,因此您需要确保您的表单将 File 设置为您映射到控制器上的任何属性。在我的情况下,它被映射到 FormData 对象上的“文件”参数。
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.
您可以根据需要添加任意数量的其他 @RequestParam,包括代表表单其余部分的 DTO,只需确保将其结构化为 FormData 对象的子项。
The key take-away here is that each @RequestParam is an attribute on the FormData object body payload on the multipart request.
这里的关键是每个@RequestParam 都是多部分请求的 FormData 对象主体有效负载上的一个属性。
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 {
//...
}
}
回答by this_is_om_vm
Add the consumer type to your request mapping .it should work fine.
将消费者类型添加到您的请求映射中。它应该可以正常工作。
@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 {
//...
}
}
回答by GSSwain
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.
我使用纯 JS 和 Spring Boot 创建了一个类似的东西。这是回购。我正在发送一个User
对象 asJSON
和 aFile
作为multipart/form-data
请求的一部分。
The relevant snippets are below
相关片段如下
The Controller
code
该Controller
代码
@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
和JS
代码
<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>
回答by Raghvendra Garg
@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.
这似乎是最简单的方法,其他方法可能是添加您自己的 messageConverter。
回答by ShaneCoder
You have to tell spring you're consuming multipart/form-data
by adding consumes = "multipart/form-data"
to the RequestMapping
annotation. Also remove the RequestBody
annotation from the expenseDto
parameter.
您必须multipart/form-data
通过添加consumes = "multipart/form-data"
到RequestMapping
注释来告诉您正在使用的spring 。还要RequestBody
从expenseDto
参数中删除注释。
@RequestMapping(path = "/{groupId}", consumes = "multipart/form-data", method = RequestMethod.POST)
public ExpenseSnippetGetDto create(ExpensePostDto expenseDto,
@PathVariable long groupId, Principal principal, BindingResult result)
throws IOException {
//..
}
With the posted ExpensePostDto
the title
in the request is ignored.
随着发布ExpensePostDto
的title
在请求被忽略。
Edit
编辑
You'll also need to change the content type to multipart/form-data
. Sounds like that's the default for post
based on some other answers. Just to be safe, I would specify it:
您还需要将内容类型更改为multipart/form-data
. 听起来这是post
基于其他一些答案的默认设置。为了安全起见,我会指定它:
'Content-Type': 'multipart/form-data'
回答by Femi Nefa
Remove this from the react front end:
从反应前端中删除它:
'Content-Type': 'application/json'
Modify the Java side controller:
修改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);
}
This can be written better but tried keeping it as close to your original code as much as I could. I hope it helps.
这可以写得更好,但尽量让它尽可能接近你的原始代码。我希望它有帮助。
回答by visrahane
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).
我有一个类似的用例,我有一些 JSON 数据和图像上传(将其视为尝试注册个人详细信息和个人资料图像的用户)。
Referring to @Stephan and @GSSwain answer I came up with a solution with Spring Boot and AngularJs.
参考@Stephan 和@GSSwain 的回答,我想出了一个使用 Spring Boot 和 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:
和 SpringBoot 控制器:
@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;
}