[英]Spring Boot controller - Upload Multipart and JSON to DTO
我想将表单内的文件上传到 Spring Boot API 端点。
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);
}
这是 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 端代码已经在运行。
我见过一个解决方案,它建议请求正文应该有 2 个属性:一个是 JSON 部分,另一个是图像。 我想看看是否可以将它自动转换为 DTO。
客户端发送的上传负载应该转换为以下DTO:
public class ExpensePostDto extends ExpenseBaseDto {
private MultipartFile image;
private String description;
private List<Long> sharers;
}
所以你可以说它是 JSON 和multipart的混合体。
解决问题的方法是在前端使用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 {
//...
}
在前端,去掉Content-Type
,因为它应该由浏览器本身决定,并使用FormData
(标准 JavaScript)。 那应该可以解决问题。
是的,您可以简单地通过包装类来完成。
1)创建一个Class
来保存表单数据:
public class FormWrapper {
private MultipartFile image;
private String title;
private String description;
}
2) 创建用于提交数据的 HTML form
:
<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)创建一个方法来接收表单的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)保存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);
}
}
我使用纯 JS 和 Spring Boot 创建了一个类似的东西。 这是回购。 我将一个User
对象作为JSON
和一个File
作为multipart/form-data
请求的一部分发送。
相关片段如下
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 + "]";
}
}
}
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>
我有一个类似的用例,我有一些 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 控制器:
@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;
}
@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";
}
这似乎是最简单的方法,其他方法可能是添加您自己的 messageConverter。
我在 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 响应,因此是“接受”标头。
您自己传入 FormData 对象,因此您需要确保您的表单将 File 设置为您映射到控制器上的任何属性。 在我的情况下,它被映射到 FormData 对象上的“文件”参数。
我的控制器端点如下所示:
@POST
@RequestMapping("/upload")
public ResponseEntity<Object> upload(@RequestParam("file") MultipartFile file)
{
if (file.isEmpty()) {
return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
} else {
//...
}
}
您可以根据需要添加任意数量的其他 @RequestParam,包括代表表单其余部分的 DTO,只需确保将其结构化为 FormData 对象的子项。
这里的关键是每个@RequestParam 都是多部分请求的 FormData 对象主体有效负载上的一个属性。
如果我要修改我的代码以容纳您的数据,它看起来像这样:
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,consumes = "multipart/form-data")
{
if (file.isEmpty()) {
return new ResponseEntity<Object>(HttpStatus.BAD_REQUEST);
} else {
//...
}
}
您必须通过在RequestMapping
注释中添加consumes = "multipart/form-data"
来告诉 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
请求中的title
将被忽略。
编辑
您还需要将内容类型更改为multipart/form-data
。 听起来这是基于其他一些答案的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);
}
这可以写得更好,但尽量让它尽可能接近你的原始代码。 我希望它有帮助。
在我的例子中,问题是 DTO class 具有公共字段而不是 getter/setter 方法
public class MyDTO {
public MultipartFile[] files;
public String user;
public String text;
}
DTO class 必须有 getters/setters,否则它不会工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.