Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] Como retornar MultipartFile na resposta da Api?

Estou participando do Challange de backend, onde dicidi construir a api em java com Spring Boot, e estou com dificuldades para trabalhar com o arquivo de imagens. A api deve trabalhar com os seguintes campos:

  • Nome do usuário
  • Foto do usuário
  • Depoimento

Escolhi para trabalhar com as imagens o tipo MultipartFile, não sei se é a melhor escolha, mas funcionou para receber as informações na rota de cadastrar, onde enviei as informações para a rota no controller com o Content-Type sendo multipart/form-data, recebo as informações através de um dto, crio a entidade e salvo ela no banco de dados MySql.

No campo de foto no MySql utilizei o tipo BLOB.

Porém para retornar estou com problemas em serializar o tipo MultiPartFile, o jackson não esta conseguindo fazer essa função por padrão.

Gostaria de saber se existe uma forma simples para serializar essa informação, ou se posso retornar o tipo byte[] sem transforma-lo no tipo MultiPartFile (já que esse é o tipo retornado do banco de dados da coluna BLOB)?

Procurei algumas soluções, e uma delas sugere criar um serializador personalizado para o tipo MultiPartFile, mas acredito que seja um pouco complicado essa opção.

Abaixo tem a rota para listar do controller:

@GetMapping
public ResponseEntity<Page<ListTestimonialDto>> listTestimonial(
    @PageableDefault(size = 10, sort = { "name" }) Pageable pagination) {      
  var dto = repositoryTestimonial.findAll(pagination).map(ListTestimonialDto::new);
  
  return ResponseEntity.ok(dto);
}

E o DTO para listar os depoimentos:

public record ListTestimonialDto(
    Long id,
    String name,
    @JsonSerialize(using = ByteArraySerializer.class)
    MultipartFile picture,
    String testimonial) {
  public ListTestimonialDto(Testimonial entity) {
    this(entity.getId(), entity.getName(), 
         new ByteArrayMultipartFile(entity.getPicture()), entity.getTestimonial());
  }
}

Após adicionar o @JsonSerialize(using = ByteArraySerializer.class) no campo picture a aplicação retornou o erro abaixo, e antes de fazer esse anotação a aplicação apenas informava que não encontrou um serializador para o tipo MultipartFile.

Could not write JSON: class com.___.ByteArrayMultipartFile cannot be cast to class 
[B (com.___.ByteArrayMultipartFile is in unnamed module of loader 
org.springframework.boot.devtools.restart.classloader.RestartClassLoader @59a7eda0; 
[B is in module java.base of loader 'bootstrap')
2 respostas

Juliu, bom dia.

Você poderia mandar o link do seu projeto no git? Pois para você fazer o upload e download de arquivos você precisa fazer diversas configurações. Eu tenho um projeto didatico que eu fiz para mim a motivos de estudo, caso queira eu te passo e você da uma olhada nos passos que foram aplicados...

solução!

Bom dia Maxuel.

Já consegui resolver esse problema. Mas o que me causou problema de verdade foi os testes unitários, demorei um tempo até conseguir enviar um arquivo com o formato MultiPartFile no MockMvc.

Para quem estiver com problemas e quiser dar uma olhada no código, o link para o meu repositório é JornadaMilhas github.

Mas a solução é que aparentemente o formato MultiPartFile não é para ser serializado, acredito que é por esse motivo que o JacksonTester não consegue serializar ele. Logo no método listTestimonial eu retornei a lista de bytes (byte[]) normalmente, sem tentar converter para o formato MultiPartFile.

Já para o front end que vai consumir a api, é preciso configurar a tag <img> para o formato base64 (que é o formato para o byte array do Java), e fazemos isso adicionando o seguinte texto no src da tag:

image.src = "data:image/png;base64," + byteArray;

Não esquecer de adicionar um virgula após o base64 e antes da lista de bytes.

A rota no controller ficou:

@GetMapping
public ResponseEntity<Page<ListTestimonialDto>> listTestimonial(
    @PageableDefault(size = 10, sort = { "name" }) Pageable pagination) {
  var dto = repositoryTestimonial.findAllByActiveTrue(pagination);
  return ResponseEntity.ok(dto);
}

E o DTO ficou:

public record ListTestimonialDto(
    Long id,
    String name,
    byte[] picture,
    String testimonial) {
  public ListTestimonialDto(Testimonial entity) {
    this(entity.getId(), entity.getName(), entity.getPicture(), entity.getTestimonial());
  }
}

E para quem estiver com problemas para testar a rota, tenho um exemplo abaixo:

var response = mvc.perform(multipart("/depoimentos").file(createMockMultipartFile())
        .param(this.nameField, this.name)
        .param(this.testimonialField, this.testimonial))
        .andReturn().getResponse();

No método perform do MockMvc ao invés de adicionar o verbo da requisição (POST, GET...), é preciso utilizar o multipart, ele é utilizado para enviar um arquivo na requisição e ja possui por padrão o verbo POST. Em seguida é encadeado o método file para enviar um arquivo no formato MultiPartFile. Caso seja preciso enviar outros campos na requisição, é possivel utilizar o método param passando o nome do campo e o valor do campo, lembrando que o nome deve ser igual ao que esta no DTO da rota.