Многокомпонентный запрос Spring MVC с JSON

84

Я хочу опубликовать файл с некоторыми данными JSON с помощью Spring MVC. Итак, я разработал сервис отдыха как

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

Когда я отправляю запрос от остального клиента content-Type = multipart/form-data or multipart/mixed, я получаю следующее исключение: org.springframework.web.multipart.support.MissingServletRequestPartException

Может ли кто-нибудь помочь мне в решении этого вопроса?

Могу ли я использовать @RequestPartдля отправки на сервер как Multipart, так и JSON?

Сунил Кумар
источник
Вы указали a org.springframework.web.multipart.commons.CommonsMultipartResolverв контексте вашего сервлета?
Уилл Килинг
да, он добавлен в мой spring.xml. <bean id = "multipartResolver" class = "org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name = "maxUploadSize" value = "300000000" /> </bean>
Сунил Кумар

Ответы:

200

Вот как я реализовал Spring MVC Multipart Request с данными JSON.

Составной запрос с данными JSON (также называемый смешанным составным):

На основе службы RESTful в выпуске Spring 4.0.2 HTTP-запрос с первой частью в виде данных в формате XML или JSON, а второй частью в виде файла может быть выполнен с помощью @RequestPart. Ниже приведен пример реализации.

Фрагмент Java:

Служба отдыха в контроллере будет иметь смешанные @RequestPart и MultipartFile для обслуживания такого запроса Multipart + JSON.

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

Фрагмент внешнего интерфейса (JavaScript):

  1. Создайте объект FormData.

  2. Добавьте файл в объект FormData, используя один из следующих шагов.

    1. Если файл был загружен с использованием элемента ввода типа «файл», добавьте его в объект FormData. formData.append("file", document.forms[formName].file.files[0]);
    2. Непосредственно добавьте файл к объекту FormData. formData.append("file", myFile, "myfile.txt");ИЛИ ЖЕformData.append("file", myBob, "myfile.txt");
  3. Создайте большой двоичный объект со строковыми данными JSON и добавьте его в объект FormData. Это приводит к тому, что Content-type второй части в составном запросе будет «application / json» вместо типа файла.

  4. Отправьте запрос на сервер.

  5. Запрос о подробностях:
    Content-Type: undefined. Это заставляет браузер устанавливать Content-Type на multipart / form-data и правильно заполнять границу. Если вручную установить Content-Type на multipart / form-data, не удастся заполнить граничный параметр запроса.

Код Javascript:

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

Детали запроса:

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

Запросить данные:

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--
Сунил Кумар
источник
1
Отлично сделано. Пришлось использовать processData: false, contentType: falseсJQuery $ajax()
sura2k
1
@SunilKumar, если мне нужно указать загрузку файла по желанию ..? С помощью формы Data.Как я могу это сделать. Потому что, если изображение не выбрано, я получаюRequired request part file is not present
Хема
1
Для меня часть "new Blob ([JSON.stringify (...)]" сделала это .. У меня все остальное было на месте.
Спасибо
4
@SunilKumar вам нужно было указать Converter для ConnectionProperties? Если я использую pojo, как показано выше, для ConnectionProperties, я получаю ... HttpMediaTypeNotSupportedException: тип содержимого «application / octet-stream» не поддерживается. Если я изменяю POJO на String, он работает. Так что непонятно, как происходит преобразование в POJO?
user2412398
1
Чтобы прояснить это: @NotBlankаннотация к параметру метода MultipartFile на самом деле не проверяет, пуст ли файл. По-прежнему можно загружать документы с 0 байтами.
sn42
14

Это должно сработать!

клиент (угловой):

$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
мохи
источник
1

Как говорится в документации:

Возникает, когда часть запроса multipart / form-data, идентифицированная по имени, не может быть найдена.

Это может быть связано с тем, что запрос не является multipart / form-data, либо потому, что часть отсутствует в запросе, либо потому, что веб-приложение неправильно настроено для обработки многостраничных запросов - например, нет MultipartResolver.

Ваэлыр
источник
0

В наших проектах мы видели, что почтовый запрос с JSON и файлами создает путаницу между разработчиками внешнего и внутреннего интерфейса, что приводит к ненужной трате времени.

Вот лучший подход: преобразовать массив байтов файла в строку Base64 и отправить его в формате 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 для преобразования файла в строку base64:

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);
};
Пьяный папа
источник