Я хочу загрузить файл внутри формы в конечную точку 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 уже работают.
Я видел решение, в котором предполагается, что тело запроса должно иметь 2 атрибута: один, в котором находится раздел JSON, другой для изображения. Я хотел бы посмотреть, возможно ли его автоматическое преобразование в DTO.
Полезные данные загрузки, отправленные клиентом, должны быть преобразованы в следующий DTO:
public class ExpensePostDto extends ExpenseBaseDto {
private MultipartFile image;
private String description;
private List<Long> sharers;
}
Таким образом, вы можете сказать, что это смесь JSON и составной.
Решение проблемы - использовать 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). Это должно решить проблему.
Я изменил его на multipart/form-data, но все равно получаю ту же ошибку.
Это именно то, что вам нужно: stackoverflow.com/questions/25699727/…



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я создал свое последнее приложение для загрузки файлов на 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 автоматически устанавливает для меня многостраничный тип MIME и границу в значении заголовка Content-Type. Ваш не может, и в этом случае вам нужно установить его самостоятельно.
Мое приложение ожидает от сервера ответа JSON, поэтому заголовок «Принять».
Вы сами передаете объект FormData, поэтому вам нужно убедиться, что ваша форма устанавливает File для любого атрибута, который вы сопоставляете на своем контроллере. В моем случае он отображается на параметр 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 {
//...
}
}
Я все еще получаю ту же ошибку. Ваша конечная точка принимает только один параметр, который является file и имеет тип данных multipart form. Мой - это комбинация json и multipart. Я обновляю свой пост, добавляя DTO.
Вы не можете этого сделать. Если вы посмотрите на чистый составной пакет, тело зарезервировано для данных файла. Вы можете указывать параметры пути, но не дополнительные полезные данные. Я должен был заметить это, когда ответил. Я исправлю свой ответ, когда буду отключен от мобильного телефона.
Что это за пример на Axios github тогда: github.com/axios/axios/blob/master/examples/upload/index.htm l
Они делают то же самое, что и я в приведенном выше примере, с formData.append (). Я думаю, вы не понимаете, как пакет создается за кулисами. Если у вас есть работающая копия их примера, я предлагаю вам посмотреть сетевой трафик в Chrome и посмотреть на структуру пакетов.
как заполнить otherData из formData.append('expenseDto',otherData);? Я пробовал var otherData = {'name':'a'}, но выдает ошибку Cannot convert value of type 'java.lang.String' to required type 'ExpenseDto'.
у вашего объекта ExpenseDTO есть один атрибут name и нет других обязательных атрибутов? Spring автоматически пытается десериализовать полезную нагрузку в POJO, предполагая, что POJO содержит и помечен как действительный из предоставленного JSON.
Добавьте тип потребителя в сопоставление запросов. Он должен работать нормально.
@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 {
//...
}
}
Да, вы можете просто сделать это через класс-оболочку.
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);
}
}
Не могли бы вы рассказать мне, как получить символы utf-8, если я передал сюда переменную "title". Потому что в настоящее время я получаю ???? за это. Английские символы работают нормально.
@VijayShegokar, вы добавляли CharacterEncodingFilter в web.xml?
да. На самом деле приложений два. Первое приложение пересылает запрос другому приложению через Zuul Proxy. Я также получаю заголовок, описывающий эти значения как повторяющиеся (через запятую) во втором контроллере приложения. Но если я скопирую тот же код в первый контроллер приложения и получу доступ к нему, все будет работать нормально.
Я думаю, вам следует создать для него отдельный вопрос, чтобы люди могли понять проблему. Или, если вы найдете похожие вопросы, отметьте меня.
Я создал похожую вещь, используя чистый 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>
У меня это сработало. Просто добавляя, нужно было добавить: processData: false, contentType: false, cache: false, чтобы все работало хорошо. Spring Boot 2.1.7. и не нужно было добавлять "потребляет".
@GSSwain у меня работает нормально. Как протестировать конечную точку из POSTMAN.
идеально! Благодарность
@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.
Вы должны сообщить Spring, что потребляете multipart/form-data, добавив consumes = "multipart/form-data" в аннотацию RequestMapping. Также удалите аннотацию 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 {
//..
}
С опубликованным ExpensePostDtotitle в запросе игнорируется.
Редактировать
Вам также необходимо изменить тип содержимого на 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);
}
Это можно было бы написать лучше, но постарался максимально приблизить его к исходному коду. Я надеюсь, что это помогает.
У меня был аналогичный вариант использования, когда у меня были некоторые данные 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.info("success", response)
// This function handles success
}, function (response) {
console.info("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;
}
Ваш тип содержимого неверен, FormData не создает
application/json