Solicitud de múltiples partes de Spring MVC con JSON

84

Quiero publicar un archivo con algunos datos JSON usando Spring MVC. Así que desarrollé un servicio de descanso como

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

Cuando envío una solicitud del resto del cliente con content-Type = multipart/form-data or multipart/mixed, obtengo la siguiente excepción: org.springframework.web.multipart.support.MissingServletRequestPartException

¿Alguien puede ayudarme a resolver este problema?

¿Puedo usar @RequestPartpara enviar Multipart y JSON a un servidor?

Sunil Kumar
fuente
¿Ha especificado un org.springframework.web.multipart.commons.CommonsMultipartResolveren su contexto de servlet?
Will Keeling
sí, se agrega en mi spring.xml. <bean id = "multipartResolver" class = "org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name = "maxUploadSize" value = "300000000" /> </bean>
Sunil Kumar

Respuestas:

198

Así es como implementé Spring MVC Multipart Request con JSON Data.

Solicitud multiparte con datos JSON (también denominada multiparte mixta):

Basado en el servicio RESTful en Spring 4.0.2 Release, la solicitud HTTP con la primera parte como datos formateados en XML o JSON y la segunda parte como un archivo se puede lograr con @RequestPart. A continuación se muestra la implementación de muestra.

Fragmento de Java:

El servicio de descanso en Controller habrá mezclado @RequestPart y MultipartFile para atender dicha solicitud 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);
}

Fragmento de front-end (JavaScript):

  1. Cree un objeto FormData.

  2. Agregue el archivo al objeto FormData siguiendo uno de los pasos siguientes.

    1. Si el archivo se ha cargado utilizando un elemento de entrada de tipo "archivo", añádalo al objeto FormData. formData.append("file", document.forms[formName].file.files[0]);
    2. Anexe directamente el archivo al objeto FormData. formData.append("file", myFile, "myfile.txt");OformData.append("file", myBob, "myfile.txt");
  3. Cree un blob con los datos JSON en cadena y añádalo al objeto FormData. Esto hace que el tipo de contenido de la segunda parte de la solicitud multiparte sea "application / json" en lugar del tipo de archivo.

  4. Envía la solicitud al servidor.

  5. Detalle de la información:
    Content-Type: undefined. Esto hace que el navegador establezca Content-Type en multipart / form-data y complete el límite correctamente. La configuración manual de Content-Type en multipart / form-data no completará el parámetro de límite de la solicitud.

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

Pedir detalles:

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

Solicitar carga útil:

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
fuente
1
Bien hecho. Tuve que usar processData: false, contentType: falseconJQuery $ajax()
sura2k
1
@SunilKumar, si necesito dar la carga de archivos como opcional ...? Con los datos del formulario. ¿Cómo puedo hacer esto? Porque si no se selecciona la imagen, estoy recibiendoRequired request part file is not present
Hema
1
Para mí, la parte "new Blob ([JSON.stringify (...)]" lo hizo ... Tenía todo lo demás en su lugar. Gracias
Ostati
4
@SunilKumar, ¿tenía que especificar Converter para ConnectionProperties? Si uso un pojo como se muestra arriba para ConnectionProperties, obtengo ... HttpMediaTypeNotSupportedException: El tipo de contenido 'application / octet-stream' no es compatible. Si cambio el POJO a String, funciona. Entonces, ¿no está claro cómo está sucediendo la conversión al POJO?
user2412398
1
Solo para dejar esto claro: la @NotBlankanotación en el parámetro del método MultipartFile no verificará si el archivo está vacío. Todavía es posible cargar documentos con 0 bytes.
sn42
14

¡Esto debe funcionar!

cliente (angular):

$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;
        }
      };

Bota de resorte trasero:

@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
fuente
1

Como dice la documentación:

Se genera cuando no se encuentra la parte de una solicitud "multipart / form-data" identificada por su nombre.

Esto puede deberse a que la solicitud no es multipart / form-data, ya sea porque la parte no está presente en la solicitud o porque la aplicación web no está configurada correctamente para procesar solicitudes multiparte, por ejemplo, no MultipartResolver.

Vaelyr
fuente
0

Hemos visto en nuestros proyectos que una solicitud de publicación con JSON y archivos está creando mucha confusión entre los desarrolladores de frontend y backend, lo que lleva a una pérdida de tiempo innecesaria.

Aquí hay un mejor enfoque: convierta la matriz de bytes del archivo a una cadena Base64 y envíelo en 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());
    ....
}

Código JS para convertir un archivo a una cadena 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);
};
Papá borracho
fuente