Żądanie wieloczęściowe Spring MVC z JSON

84

Chcę opublikować plik z danymi JSON przy użyciu Spring MVC. Więc opracowałem usługę odpoczynku jako

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

Kiedy wysyłam żądanie od pozostałego klienta content-Type = multipart/form-data or multipart/mixed, otrzymuję następny wyjątek: org.springframework.web.multipart.support.MissingServletRequestPartException

Czy ktoś może mi pomóc w rozwiązaniu tego problemu?

Czy mogę użyć @RequestPartdo wysłania zarówno Multipart, jak i JSON na serwer?

Sunil Kumar
źródło
Czy określiłeś org.springframework.web.multipart.commons.CommonsMultipartResolverw kontekście swojego serwletu?
Will Keeling
tak, jest dodawany w moim pliku spring.xml. <bean id = "multipartResolver" class = "org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name = "maxUploadSize" value = "300000000" /> </bean>
Sunil Kumar

Odpowiedzi:

200

W ten sposób zaimplementowałem Spring MVC Multipart Request z danymi JSON.

Żądanie wieloczęściowe z danymi JSON (nazywane również Wieloczęściowym mieszanym):

Opierając się na usłudze RESTful w wydaniu Spring 4.0.2, żądanie HTTP z pierwszą częścią jako dane w formacie XML lub JSON, a drugą częścią jako plik można uzyskać za pomocą @RequestPart. Poniżej znajduje się przykładowa realizacja.

Fragment Java:

Usługa Rest w kontrolerze będzie miała mieszane @RequestPart i MultipartFile do obsługi takiego żądania 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);
}

Fragment interfejsu (JavaScript):

  1. Utwórz obiekt FormData.

  2. Dołącz plik do obiektu FormData, wykonując jeden z poniższych kroków.

    1. Jeśli plik został przesłany przy użyciu elementu wejściowego typu „plik”, dołącz go do obiektu FormData. formData.append("file", document.forms[formName].file.files[0]);
    2. Bezpośrednio dołącz plik do obiektu FormData. formData.append("file", myFile, "myfile.txt");LUBformData.append("file", myBob, "myfile.txt");
  3. Utwórz obiekt BLOB ze zdefiniowanymi danymi JSON i dołącz go do obiektu FormData. Powoduje to, że typ zawartości drugiej części w żądaniu wieloczęściowym to „application / json” zamiast typu pliku.

  4. Wyślij żądanie do serwera.

  5. Poproś o szczegóły:
    Content-Type: undefined. Powoduje to, że przeglądarka ustawia Content-Type na multipart / form-data i prawidłowo wypełnia granice. Ręczne ustawienie Content-Type na multipart / form-data nie spowoduje wypełnienia parametru brzegowego żądania.

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

Szczegóły prośby:

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

Poproś o ładunek:

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--
Sunil Kumar
źródło
1
Ładnie wykonane. Musiałem skorzystać processData: false, contentType: falsezJQuery $ajax()
sura2k
1
@SunilKumar, jeśli muszę podać przesyłanie pliku jako opcjonalne ...? Z formularzem Data.Jak mogę to zrobić. Ponieważ jeśli obraz nie jest wybrany, dostajęRequired request part file is not present
Hema
1
Jak dla mnie „nowy Blob ([JSON.stringify (...)]” część zrobiła to .. Wszystko inne miałem na swoim miejscu. Thx.
Ostati
4
@SunilKumar czy musiałeś określić konwerter dla ConnectionProperties? Jeśli używam pojo, jak pokazano powyżej dla ConnectionProperties, otrzymuję ... HttpMediaTypeNotSupportedException: Typ zawartości „application / octet-stream” nie jest obsługiwany Jeśli zmienię POJO na String, to działa. Więc nie jest jasne, jak przebiega konwersja do POJO?
user2412398
1
Dla jasności: @NotBlankadnotacja w parametrze metody MultipartFile nie sprawdza, czy plik jest pusty. Nadal możliwe jest przesyłanie dokumentów zawierających 0 bajtów.
sn42
14

To musi działać!

klient (kątowy):

$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
mohi
źródło
1

Jak mówi dokumentacja:

Wywoływane, gdy nie można znaleźć części żądania „multipart / form-data” identyfikowanej przez jego nazwę.

Może to być spowodowane tym, że żądanie nie jest danymi wieloczęściowymi / formularzowymi, ponieważ część nie jest obecna w żądaniu lub aplikacja internetowa nie jest poprawnie skonfigurowana do przetwarzania żądań wieloczęściowych - np. Brak MultipartResolver.

Vaelyr
źródło
0

W naszych projektach widzieliśmy, że żądanie postu z JSON i plikami powoduje wiele zamieszania między programistami frontendu i backendu, co prowadzi do niepotrzebnego marnowania czasu.

Oto lepsze podejście: przekonwertuj tablicę bajtów pliku na ciąg Base64 i wyślij ją w formacie 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());
    ....
}

Kod JS do konwersji pliku na ciąg 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);
};
Pijany tatuś
źródło