17
respostas

Donwload de arquivos zip

Tem arquivos em um banco de dados.

O parâmetro é byte[] e no banco de dados no postgres é bytea.

Como posso ter vários arquivos no registro, como faço o download em spring, colocando todos arquivos em um arquivo zip e fazer o donwload no navegador ?

17 respostas

Vc está utilizando o que, Spring Boot com Angular? Quais tecnologias estas utilizando?

O zip é fácil de resolver, o Java já fornece classes para manipular Zip files.

Dá uma olhada no item 3 deste artigo:

https://www.baeldung.com/java-compress-and-uncompress

Att.

Vc está utilizando o que, Spring Boot com Angular?

Sim

Quais tecnologias estas utilizando? Spring e angular

Olá Guilherme, estou utilizando Spring Boot e Angular 7 num sistema aqui, e faço da seguinte forma:

No meu Component onde tenho o download terei o seguinte:

TS:

download(anexo: Anexo) {
    const url = URL.createObjectURL(
      this.ioService.base64ToBlob(
        anexo.conteudo,
        anexo.contentType,
        512)
    );
    window.open(url);
  }

HTML:

<button type="button" class="..." (click)="download(anexo)" >
    Download
</button>

io-service.ts:

/**
   * Convert a base64 string in a Blob according to the data and contentType.
   *
   * @param base64Data {String} Pure base64 string without contentType
   * @param contentType {String} the content type of the file i.e (application/pdf - text/plain)
   * @param sliceSize {Int} SliceSize to process the byteCharacters
   * @see http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
   * @return Blob
   */
  base64ToBlob(base64Data: string, contentType: string, sliceSize: number): Blob {
    contentType = contentType || '';
    sliceSize = sliceSize || 512;

    const byteCharacters = atob(base64Data);
    const byteArrays = [];

    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);

      const byteNumbers = new Array(slice.length);

      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }

      const byteArray = new Uint8Array(byteNumbers);

      byteArrays.push(byteArray);
    }

    const blob = new Blob(byteArrays, {type: contentType});

    return blob;
  }

No meu Controller (SpringBoot) eu retorno o arquivo como base64, então é simplesmente um atributo string, sem mistério. Na realidade retorno uma lista de anexos (AnexoTO):

@GetMapping("/v1/anexo/filtro/{associacaoId}")
    public ResponseEntity<?> filterGet(HttpServletRequest request, @PathVariable("associacaoId") Long associacaoId) {
    try {
        List<AnexoTO> list = service().consultarPorAssociacao(associacaoId);

        return prepareResponseList(list);
    } catch (Exception e) {
        logger.error(e.getMessage(), e);

        return buildResponseError(messageFactory.error("ERR_005", e));
    }
    }

AnexoTO:

public class AnexoTO implements Serializable, Comparable<AnexoTO> {

    private Long id;
    private String nome;
    private TipoAnexoEnum tipoAnexo;
    private String conteudo;
    private String contentType;

    ...
}

Também mantenho o contentType do arquivo, para saber como abrir no download.

No meu caso eu salvo dados do anexo (arquivo) no banco de dados, mas o conteúdo do arquivo salvo no FileSystem, mas poderia ser no banco também. Mas isso é indiferente.

No teu caso, deverás "zipar" os arquivos e fazer base64 desse Zip retornando um objeto (que o SpringBoot transformará num JSON), sendo o contentType application/zip.

Pra ler o arquivo do FileSystem como String faço o seguinte:

public String loadFromFileSystem(String conteudo) throws IOException {
        String filename = conteudo;
        Path path = Paths.get(anexoRootFolder + File.separator + filename);
        if (Files.exists(path)) {
            return new String(Files.readAllBytes(path));
        }

        return null;
    }

Isso tudo está funcionando em produção "like a charm", só fazer as devidas adaptações:

Espero que tenha ajudado.

Att.

Este io-service.ts, converte o que veio do servidor e faz o download em zip ?

Não entendi o método: loadFromFileSystem

Esse io-service.ts é uma classe de serviço minha, que converte o base64 num objeto URL, que será usado para abrir numa outra aba (window.open).

download(anexo: Anexo) {
    const url = URL.createObjectURL(
      this.ioService.base64ToBlob(
        anexo.conteudo,
        anexo.contentType,
        512)
    );
    window.open(url);
  }

Att.

O loadFileSystem existe pq eu gravo o arquivo (upload) em base64, que é String, no FileSystem.

Então preciso carregá-lo do FileSystem, como disse poderia ser a partir do banco de dados.

Att.

Então no banco de dados que trabalho, tem o arquivo em base64 e o arquivo em bytes.

Qual é a melhor maneira de trazer ou converter ?

Qdo retorna em formato String, base64, fica fácil porque vc retorna o objeto JSON no teu Controller, por exemplo:

public class AnexoTO {
    private String contentType;
    private String content;
}

No atributo content vc teria o conteúdo do arquivo, no formato base64.

Como vc está mantendo os arquivos? No banco ou em FileSystem?

Como vc está mantendo os arquivos? No banco ou em FileSystem?

Banco.

Entao, comecei mantendo no banco, o cliente solicitou posteriormente para mante em FileSystem.

Como vc persiste nao importa muito, pois em ambos eu gravei em base64, ou seja, String.

Att.

Valeu

Fiz assim:

    @GetMapping(value = "/download/{id}", produces = "application/zip")
    public void download(@PathVariable("id") String id,
            HttpServletResponse response) {
        try {
            response.setContentType("application/zip");
            response.setStatus(HttpServletResponse.SC_OK);
            response.addHeader("Content-Disposition", "attachment; filename=\"arquivos_conta_receber_" + idDtoParaEntidade(id) + ".zip\"");
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream);
            ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream);

            ContaPagarReceberArquivoImagemEntity cprArqImg = new ContaPagarReceberArquivoImagemEntity();

            cprArqImg.setContaPagarReceber(converterEntidadeContaReceber(new ContaReceberDTO(id)));

            RetornoJackson listarTodos = listarTodos(cprArqImg, null);

            ArrayList<File> files = new ArrayList<>(2);

            for (Object object : listarTodos.getLista()) {
                ContaPagarReceberArquivoImagemEntity cprArqImgZip = (ContaPagarReceberArquivoImagemEntity) object;
                byte[] logomarcaFoto = cprArqImgZip.getArquivoImagem().getLogomarcaFoto();

            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

Faz o donwload, mas sem arquivo nenhum. o que pode estar faltando ?

Dá este erro ao fazer o donwload. Imagem em anexo.

Para cada logomarca obtida vc precisa criar um ZipEntry. Veja o código de exemplo abaixo:

public class ZipMultipleFiles {
    public static void main(String[] args) throws IOException {
        List<String> srcFiles = Arrays.asList("test1.txt", "test2.txt");
        FileOutputStream fos = new FileOutputStream("multiCompressed.zip");
        ZipOutputStream zipOut = new ZipOutputStream(fos);
        for (String srcFile : srcFiles) {
            File fileToZip = new File(srcFile);
            FileInputStream fis = new FileInputStream(fileToZip);
            ZipEntry zipEntry = new ZipEntry(fileToZip.getName());
            zipOut.putNextEntry(zipEntry);

            byte[] bytes = new byte[1024];
            int length;
            while((length = fis.read(bytes)) >= 0) {
                zipOut.write(bytes, 0, length);
            }
            fis.close();
        }
        zipOut.close();
        fos.close();
    }
}

Adapta este exemplo a tua situação. No exemplo ele usa um FileInputStream para ler os bytes dos arquivos a partir do FileSystem. No teu caso vc já tem os bytes da logomarca.

Att.

Pelo que entendi.

Aqui seria os nomes List srcFiles = Arrays.asList("test1.txt", "test2.txt");

Aonde que entra os arquivos, não entendi.

Deixa tentar fazer esta adaptação aqui:

for (Object object : listarTodos.getLista()) {
    ContaPagarReceberArquivoImagemEntity cprArqImgZip = (ContaPagarReceberArquivoImagemEntity) object;
    byte[] logomarcaFoto = cprArqImgZip.getArquivoImagem().getLogomarcaFoto();

    String nomeArquivo = <???>;
    ZipEntry zipEntry = new ZipEntry(nomeArquivo);
    zipOutputStream.putNextEntry(zipEntry);

    zipOutputStream.write(logomarcaFoto, 0, logomarcaFoto.length);
}

Acho que seria isto. Vc precisa resolver o nome do arquivo () que acredito esteja no objeto cprArqImgZip.

Att.

Vou testar e falo aqui.

Obrigado