Загрузка файла Spring Cloud Feign Client

При отправке почтового запроса из одного микросервиса в другой с использованием имитации клиента spring cloud netflix я получаю следующую ошибку в Postman:

{
"timestamp": 1506933777413,
"status": 500,
"error": "Internal Server Error",
"exception": "feign.codec.EncodeException",
"message": "Could not write JSON: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])",
"path": "/attachments"
}

И моя консоль eclipse показывает следующее исключение:

com. support.StandardMultipartHttpServletRequest $ StandardMultipartFile ["inputStream"] -> java.io.FileInputStream ["fd"]) в com.fasterxml.jackson.databind.JsonMappingException.from (JsonMappingException.java: 284) ~ [-jackson 2.8 9.jar: 2.8.9] в com.fasterxml.jackson.databind.SerializerProvider.mappingException (SerializerProvider.java:1110) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson. databind.SerializerProvider.reportMappingProblem (SerializerProvider.java:1135) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty (UnknownSerializer.java:69 ) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.d atabind.ser.impl.UnknownSerializer.serialize (UnknownSerializer.java:32) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField (BeanPropertyWriter.java : 704) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields (BeanSerializerBase.java:689) ~ [jackson-databind-2.8. 9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.BeanSerializer.serialize (BeanSerializer.java:155) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml. jackson.databind.ser.BeanPropertyWriter.serializeAsField (BeanPropertyWriter.java:704) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields (BeanSerializeFields (BeanSerializeFields) .java: 689) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.BeanSerializer.serialize (BeanSerializer.java:155) ~ [jackson-databind-2.8. 9.jar: 2.8.9] в com.fasterxml.jackson.databind.ser.De faultSerializerProvider.serializeValue (DefaultSerializerProvider.java:292) ~ [jackson-databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ObjectWriter $ Prefetch.serialize (ObjectWriter.java:1429) ~ [jackson -databind-2.8.9.jar: 2.8.9] в com.fasterxml.jackson.databind.ObjectWriter.writeValue (ObjectWriter.java:951) ~ [jackson-databind-2.8.9.jar: 2.8.9]

ОБНОВЛЕНИЕ 1

Это мой воображаемый интерфейс:

@FeignClient(name="attachment-service", fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {

@RequestMapping("upload")
void upload(@RequestPart(name="file") MultipartFile file, @RequestParam(name="attachableId") Long attachableId, 
        @RequestParam(name="className") String className, @RequestParam(name="appName") String appName);

А это основной контроллер микросервиса:

@RestController
public class AttachmentController implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = -4431842080646836475L;

@Autowired
AttachmentService attachmentService;

@RequestMapping(value = "attachments", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void upload(@RequestPart MultipartFile file, @RequestParam Long attachableId, @RequestParam String className, @RequestParam String appName) throws Exception {
    attachmentService.uploadFile(file, attachableId, className, appName);
}

}

Мне определенно не хватает какого-то сериализатора здесь
Любое предложение будет оценено!
Спасибо


person ZiOS    schedule 02.10.2017    source источник
comment
идентичны ли pojos на этих двух микросервисах? Если их поля и имя совпадают, вам не понадобится сериализатор. Покажите фрагмент вашего кода с остальными, определенными в одной службе, и тем же методом в вашем интерфейсе feinclient   -  person mlecz    schedule 02.10.2017
comment
@mlecz да, все pojos находятся в стартере, который интегрирован в оба микросервиса   -  person ZiOS    schedule 02.10.2017
comment
@mlecz взгляните на обновление 1, пожалуйста   -  person ZiOS    schedule 02.10.2017
comment
эти 2 выглядят одинаково. Не знаю, как тебе помочь. Я вижу, вы снова обновили этот пост, но перед обновлением я видел 2 метода, связанных с URL-адресом вложений, одно получение, одно сообщение. Может быть, попробовать добавить RequestMapping.get, чтобы симулировать клиента?   -  person mlecz    schedule 02.10.2017
comment
Я нашел решение, добавив некоторые зависимости для симулированной формы ..   -  person ZiOS    schedule 02.10.2017


Ответы (3)


После нескольких дней поиска решения я нашел это. Вы должны начать добавлять симулированную форму для пружинной зависимости:

<dependency>
   <groupId>io.github.openfeign.form</groupId>
   <artifactId>feign-form-spring</artifactId>
   <version>3.3.0</version>
</dependency

Тогда вашему воображаемому клиенту понадобится этот кодировщик пружинной формы:

@FeignClient(
    name="attachment-service",  
    configuration = {AttachmentFeignClient.MultipartSupportConfig.class}
    fallback=AttachmentHystrixFallback.class)
public interface AttachmentFeignClient {

@RequestMapping(value= {"upload"}, consumes = {"multipart/form-data"})
void upload(
    @RequestPart(name="file") MultipartFile file, 
    @RequestParam(name="attachableId") Long attachableId, 
    @RequestParam(name="className") String className,
    @RequestParam(name="appName") String appName);

 public class MultipartSupportConfig {
    @Bean
    @Primary
    @Scope("prototype")
    public Encoder feignFormEncoder() {
        return new SpringFormEncoder();
    }
  }
}

Надеюсь, это кому-то поможет.

person Martin Choraine    schedule 09.05.2018
comment
Привет, Мартин, я сделал ту же конфигурацию, что и вы, но получаю "Файл" необходимой части запроса отсутствует. Вы видели эту ошибку? - person djeison; 08.12.2018
comment
из-за метода загрузки установите файл параметров, поэтому вы должны установить файл с именем file - person bp zhang; 23.05.2019

Обновление 13.01.2020
Настройте FeignClient для обработки MultiPartFile с помощью @RequestPart
Обратите внимание, что этот подход подходит, только если у вас есть один RequestPart, представляющий все тело. Наличие нескольких RequestPart - еще одна проблема с FeignClient.


Конфигурация feignclient аналогична настройке @ marting-choraine, но путем добавления дополнительной конфигурации кодировщика в FeignConfigurationClass.

Итак, в том же примере, который я привожу ниже, вместо реализации настраиваемого картографа вам нужно будет сделать что-то вроде этого

@Configuration
@EnableFeignClients(basePackages = "YourPackage")
public class FeignConfiguration {

  @Bean
  public Encoder feignFormEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
      return new SpringFormEncoder(new SpringEncoder(messageConverters));
  }
}

TL; DR
Преобразуйте MultiPartFile в MultiValueMap. См. Пример ниже


Ответ, упомянутый @ martin-choraine, является правильным и лучшим ответом на подпись вашего метода FeignClient, то же самое как фактическая подпись конечной точки, которую вы пытаетесь вызвать. Однако есть способ обойти эту проблему, который не требует от вас определения FormEncoder или добавления каких-либо дополнительных зависимостей, потому что в некоторых приложениях вам это не разрешено (корпоративное дерьмо); все, что вам нужно, это преобразовать ваш MultipartFile в MultiValueMap, и он будет отлично работать, поскольку стандартный кодировщик сможет его сериализовать.


Фактическая конечная точка, которая будет вызываться

@PostMapping(path = "/add", consumes = MULTIPART_FORM_DATA_VALUE, produces = APPLICATION_JSON_VALUE)
 public MyResponseObject add(@RequestParam(name = "username") String username,
                             @RequestPart(name = "filetoupload") MultipartFile file) {
              Do something
}

Метод POST в FeignClient должен выглядеть следующим образом

@PostMapping(path = "/myApi/add", consumes = MULTIPART_FORM_DATA_VALUE, 
              produces = APPLICATION_JSON_VALUE)
 public MyResponseObject addFile(@RequestParam(name = "username") String username,
                           @RequestPart(name = "filetoupload") MultiValueMap<String, Object> file);

Затем, когда вы вызываете addFile, вы должны предоставить MultiValueMap следующим образом

public MyResponseObject addFileInAnotherEndPoint(String username, MultipartFile file) throws IOException {

    MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
    ByteArrayResource contentsAsResource = new ByteArrayResource(file.getBytes()) {
        @Override
        public String getFilename() {
            return file.getOriginalFilename();
        }
    };
    multiValueMap.add("filetoupload", contentsAsResource);
    multiValueMap.add("fileType", file.getContentType());
    return this.myFeignClient.addFile(username, multiValueMap);
}
person Ahmed Kareem    schedule 31.10.2019
comment
есть идеи, как реализовать то же самое для нескольких файлов? Я тоже проверил ваши комментарии на GitHub. Я пробовал FeignSpringFormEncoder, но теперь выдает com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.BufferedInputStream. - person Mohammed Idris; 18.11.2019
comment
@MohammedIdris У меня на самом деле никогда не было ситуации, когда мне нужно было бы отправить список файлов. Однако когда я искал свою проблему, у меня была эта библиотека FormEncoder. Кроме того, этот вопрос о переполнении стека здесь может вам помочь. - person Ahmed Kareem; 21.11.2019
comment
Я пробовал это, но это не работает, нашел другой FeignEncoder, который не закодировал запрос с bodyType. Я пробовал использовать restTemplate, который требует написания большего количества кода по сравнению с Feign, однако у меня есть другой микросервис (тот, который принимает изображения и обрабатывает их) при обнаружении эврики. Если я смогу каким-то образом подключить RestTemplate через eureka, этого пока будет достаточно. Я не хочу указывать номер порта и url в параметре url rest (если это возможно) - person Mohammed Idris; 21.11.2019
comment
Под неработающим я подразумеваю, что метод encode в классе Encoder выдает NPE. Я мог бы опубликовать ошибку в github openFeign, но я решил использовать подход restTemplate с асинхронными вызовами для каждого изображения, что, как я понял, является лучшим подходом. Я попытался устранить симулированную ошибку, но из-за отсутствия предыдущего опыта и меньшего количества примеров в сети мне не удалось устранить ошибку. Я пробовал комментировать другие проблемы с github, но ответа пока нет. Я новичок в микросервисах, если бы кто-нибудь мог указать мне пример (ы), где имя клиента eureka используется в restTemplate, было бы здорово. Заранее спасибо. - person Mohammed Idris; 21.11.2019
comment
Что касается вашего вопроса о разрешении идентификаторов служб из клиента Eureka, я думаю, что вам нужна балансировка нагрузки, в частности @LoadBalanced из весеннего облака. Посмотрите на этот вопрос stackOverFlow здесь, объясняющий LoadBalanced и RibbonClient, углубитесь в детали, и вы надеюсь получить хороший ответ. - person Ahmed Kareem; 03.12.2019

Я добавил consumes = MediaType.MULTIPART_FORM_DATA_VALUE в сообщение, и это работает для меня. это мой метод имитации клиента, и сначала я работал с formdata

@PostMapping(path=Urls.UPLOAD_FILE_IN_LIBELLE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
    public void uploadFileInLibelle(
            @RequestParam String processus,
            @RequestParam String level0Name,
            @RequestParam String nomFichier,
            @RequestParam String nomLibelle,
            @RequestParam String anneeFolderName,
            @RequestParam String semaineFolderName,
            @RequestPart   MultipartFile fichier);

это мой угловатый фронт

public uploadFiles(
        nomFichier: string,
        nomLibelle: string,
        processus: string,
        level0Name: string,
        semaineFolderName: string,
        anneeFolderName: string,
        byte: File
    ): Observable<any> {
        const formData = new FormData();
        formData.set('processus', processus);
        formData.set('level0Name', level0Name);
        formData.set('nomLibelle', nomLibelle);
        formData.set('anneeFolderName', anneeFolderName);
        formData.set('semaineFolderName', semaineFolderName);
        formData.set('nomFichier', nomFichier);
        formData.set('fichier', byte);

        return this.httpClient.post(this.urlUploadFile, formData);
    }
person fatma    schedule 07.11.2019