Configuración de Feign para la Carga Múltiple de Archivos en Spring Cloud

Para habilitar la transmisión de múltiples recursos binarios a través de clientes remotos en ecosistemas Spring Cloud, es necesario reemplazar el codificador por defecto. La implementación base de Feign no gestiona nativamente los flujos MultipartFile, por lo que se requiere una capa de adaptación que transforme la carga en el formato multipart/form-data esperado por los servidores REST.

  1. Integración de dependencias Maven

El módulo consumidor debe incorporar los artefactos de procesamiento de formularios junto con la utilidad estándar de Apache para la gestión de subida de archivos:

<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.0.3</version>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
  1. Desarrollo del codificador personalizado

Se define una clase que extiende FormEncoder y sobrescribe el método de codificación. Esta adaptación intercepta objetos de tipo MultipartFile individual o agrupado, encapsulándolos en un mapa con la clave identificadora del recurso antes de delegar la construcción de la plantilla HTTP.

import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.ContentType;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringManyMultipartFilesWriter;
import feign.form.spring.SpringSingleMultipartFileWriter;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;

public class CodificadorMultipartFeign extends FormEncoder {

    public CodificadorMultipartFeign() {
        this(new Encoder.Default());
    }

    public CodificadorMultipartFeign(Encoder codificadorSecundario) {
        super(codificadorSecundario);
        MultipartFormContentProcessor procesador = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
        procesador.addWriter(new SpringSingleMultipartFileWriter());
        procesador.addWriter(new SpringManyMultipartFilesWriter());
    }

    @Override
    public void encode(Object payload, Type tipoCuerpo, RequestTemplate plantilla) throws EncodeException {
        if (tipoCuerpo.equals(MultipartFile.class)) {
            MultipartFile recurso = (MultipartFile) payload;
            Map<String, Object> estructuraCarga = Collections.singletonMap(recurso.getName(), payload);
            super.encode(estructuraCarga, MAP_STRING_WILDCARD, plantilla);
            return;
        }
        
        if (tipoCuerpo.equals(MultipartFile[].class)) {
            MultipartFile[] loteRecursos = (MultipartFile[]) payload;
            if (loteRecursos != null && loteRecursos.length > 0) {
                String claveOrigen = loteRecursos[0].getName();
                Map<String, Object> estructuraCarga = Collections.singletonMap(claveOrigen, payload);
                super.encode(estructuraCarga, MAP_STRING_WILDCARD, plantilla);
                return;
            }
        }
        
        super.encode(payload, tipoCuerpo, plantilla);
    }
}
  1. Exposición del componente de configuración

Una vez validada la lógica de procesamiento, se registra como bean de Spring con ámbito prototype. Esto garantiza que el framework instancie un codificador fresco para cada definición de cliente remoto, evitando conflictos de estado entre llamadas concurrentes.

import feign.codec.Encoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;

@Configuration
public class ConfiguracionSoporteMultipart {

    @Bean
    @Primary
    @Scope("prototype")
    public Encoder encoderFormulariosMultipart() {
        return new CodificadorMultipartFeign();
    }
}
  1. Declaración de la interfaz del cliente remoto

Finalmente, se vincula la interfaz @FeignClient a la configuración previamente definida. Los métodos que esperan recibir archivoss deben emplear @RequestPart para mapear los recursos binarios y @RequestParam para los metadatos complementarios, especificando explícitamente el tipo de contenido multipart.

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

@FeignClient(
    name = "servicio-gestion-documentos",
    configuration = ConfiguracionSoporteMultipart.class
)
public interface ClienteGestionArchivos {

    @PostMapping(
        value = "/api/v1/almacenamiento/lote",
        consumes = MediaType.MULTIPART_FORM_DATA_VALUE
    )
    String procesarLote(
        @RequestPart("recursos") MultipartFile[] documentos,
        @RequestParam("marcaTemporal") String fechaRegistro
    );

    @PostMapping(
        value = "/api/v1/almacenamiento/individual",
        consumes = MediaType.MULTIPART_FORM_DATA_VALUE
    )
    String procesarUnico(
        @RequestPart("recurso") MultipartFile archivo
    );
}

Etiquetas: spring-cloud-openfeign Multipart-Upload java encoder rest-api

Publicado el 10-22 01:52