Spring MVC Multipart Request mit JSON

80

Ich möchte eine Datei mit einigen JSON-Daten mit Spring MVC veröffentlichen. Also habe ich einen Restservice als entwickelt

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

Wenn ich eine Anfrage vom Rest-Client mit sende content-Type = multipart/form-data or multipart/mixed, erhalte ich die nächste Ausnahme: org.springframework.web.multipart.support.MissingServletRequestPartException

Kann mir jemand bei der Lösung dieses Problems helfen?

Kann ich @RequestPartsowohl Multipart als auch JSON an einen Server senden?

Sunil Kumar
quelle
Haben Sie org.springframework.web.multipart.commons.CommonsMultipartResolverin Ihrem Servlet-Kontext ein angegeben?
Will Keeling
Ja, es wird in meiner spring.xml hinzugefügt. <bean id = "multipartResolver" class = "org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name = "maxUploadSize" value = "300000000" /> </ bean>
Sunil Kumar

Antworten:

194

So habe ich Spring MVC Multipart Request mit JSON-Daten implementiert.

Multipart-Anfrage mit JSON-Daten (auch Mixed Multipart genannt):

Basierend auf dem RESTful-Dienst in Spring 4.0.2 Release kann eine HTTP-Anforderung mit dem ersten Teil als XML- oder JSON-formatierte Daten und dem zweiten Teil als Datei mit @RequestPart ausgeführt werden. Unten finden Sie die Beispielimplementierung.

Java-Snippet:

Der Restdienst in Controller verfügt über gemischte @RequestPart- und MultipartFile-Dateien, um eine solche Multipart + JSON-Anforderung zu bedienen.

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

Front-End-Snippet (JavaScript):

  1. Erstellen Sie ein FormData-Objekt.

  2. Hängen Sie die Datei mit einem der folgenden Schritte an das FormData-Objekt an.

    1. Wenn die Datei mit einem Eingabeelement vom Typ "Datei" hochgeladen wurde, hängen Sie sie an das FormData-Objekt an. formData.append("file", document.forms[formName].file.files[0]);
    2. Hängen Sie die Datei direkt an das FormData-Objekt an. formData.append("file", myFile, "myfile.txt");ODERformData.append("file", myBob, "myfile.txt");
  3. Erstellen Sie einen Blob mit den stringifizierten JSON-Daten und hängen Sie ihn an das FormData-Objekt an. Dies bewirkt, dass der Inhaltstyp des zweiten Teils in der mehrteiligen Anforderung "application / json" anstelle des Dateityps ist.

  4. Senden Sie die Anfrage an den Server.

  5. Details anfordern :
    Content-Type: undefined. Dies bewirkt, dass der Browser den Inhaltstyp auf Mehrteil- / Formulardaten setzt und die Grenze korrekt ausfüllt. Wenn Sie Content-Type manuell auf Multipart- / Formulardaten einstellen, wird der Grenzparameter der Anforderung nicht ausgefüllt.

Javascript Code:

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

Anfragedetails:

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

Nutzlast anfordern:

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
quelle
1
Schön gemacht. Ich musste processData: false, contentType: falsemitJQuery $ajax()
sura2k
1
@SunilKumar, Wenn ich das Hochladen von Dateien als optional angeben muss ..? Mit Formulardaten. Wie kann ich das tun? Denn wenn das Bild nicht ausgewählt ist, Required request part file is not present
bekomme
1
Für mich hat es der Teil "New Blob ([JSON.stringify (...)]" getan. Ich hatte alles andere an Ort und Stelle. Thx.
Ostati
4
@SunilKumar Mussten Sie Converter für ConnectionProperties angeben? Wenn ich ein Pojo wie oben für ConnectionProperties verwende, erhalte ich ... HttpMediaTypeNotSupportedException: Inhaltstyp 'application / octet-stream' wird nicht unterstützt Wenn ich das POJO in String ändere, funktioniert es. Es ist also nicht klar, wie die Umstellung auf das POJO erfolgt?
user2412398
1
Nur um dies zu verdeutlichen: Die @NotBlankAnmerkung zum Parameter der MultipartFile-Methode prüft nicht, ob die Datei leer ist. Es ist weiterhin möglich, Dokumente mit 0 Bytes hochzuladen.
sn42
14

Das muss funktionieren!

Client (eckig):

$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
quelle
1

Wie die Dokumentation sagt:

Wird ausgelöst, wenn der Teil einer "Multipart / Formulardaten" -Anforderung, der durch seinen Namen identifiziert wird, nicht gefunden werden kann.

Dies kann daran liegen, dass es sich bei der Anforderung nicht um mehrteilige / Formulardaten handelt, entweder weil der Teil nicht in der Anforderung vorhanden ist oder weil die Webanwendung für die Verarbeitung mehrteiliger Anforderungen nicht richtig konfiguriert ist - z. B. kein MultipartResolver.

Vaelyr
quelle
0

Wir haben in unseren Projekten gesehen, dass eine Post-Anfrage mit JSON und Dateien viel Verwirrung zwischen den Frontend- und Backend-Entwicklern verursacht, was zu unnötiger Zeitverschwendung führt.

Hier ist ein besserer Ansatz: Konvertieren Sie das Datei-Byte-Array in eine Base64-Zeichenfolge und senden Sie es in 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-Code zum Konvertieren der Datei in eine Base64-Zeichenfolge:

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);
};
Betrunkener Papa
quelle