Implementação de Upload e Download de Arquivos com Spring Boot e Vue.js

Esturtura de Banco de Dados

Para gerenciar o armazenamento de metadados dos arquivos, utilizaremos uma tabela no MySQL que armazena informações como o hash MD5 para evitar duplicidade e o caminho de acesso.

CREATE TABLE `repositorio_arquivos` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `nome_arquivo` VARCHAR(255) DEFAULT NULL COMMENT 'Nome original do arquivo',
  `extensao` VARCHAR(50) DEFAULT NULL COMMENT 'Tipo do arquivo',
  `tamanho_kb` BIGINT DEFAULT NULL COMMENT 'Tamanho em KB',
  `link_acesso` VARCHAR(255) DEFAULT NULL COMMENT 'URL de download',
  `deletado` TINYINT(1) DEFAULT '0',
  `ativo` TINYINT(1) DEFAULT '1',
  `codigo_md5` VARCHAR(255) DEFAULT NULL COMMENT 'Hash para verificação de duplicidade',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Configuração do Ambiente Spring Boot

No arquivo application.yml, definimos o diretório físico onde os arquivos serão salvos e configuramos os limites de tamanho para as requisições multipart.

diretorio:
  upload:
    local: C:/armazenamento/arquivos/

spring:
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 200MB

Desenvolvimento do Backend

Modelo de Entidade

Utilizando o MyBatis Plus e Lombok para mapear a tabela do banco de dados.

@Data
@TableName("repositorio_arquivos")
public class Arquivo {
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String nomeArquivo;
    private String extensao;
    private Long tamanhoKb;
    private String linkAcesso;
    private String codigoMd5;
    private Boolean deletado;
    private Boolean ativo;
}

Controlador de Arquivos

O controlador abaixo gerencia o upload com verificação de MD5 para evitar redundância de dados no servidor e o donwload através de fluxo de bytes.

@RestController
@RequestMapping("/api/arquivos")
public class ArquivoController {

    @Value("${diretorio.upload.local}")
    private String caminhoBase;

    @Resource
    private ArquivoMapper arquivoMapper;

    @PostMapping("/enviar")
    public String realizarUpload(@RequestParam MultipartFile file) throws IOException {
        String nomeOriginal = file.getOriginalFilename();
        String extensao = FileNameUtil.extName(nomeOriginal);
        long tamanho = file.getSize();

        // Gerar identificador único para o sistema de arquivos
        String uuid = UUID.randomUUID().toString();
        String nomeArmazenamento = uuid + "." + extensao;

        File pastaDestino = new File(caminhoBase);
        if (!pastaDestino.exists()) {
            pastaDestino.mkdirs();
        }

        File arquivoFinal = new File(caminhoBase + nomeArmazenamento);
        String md5 = SecureUtil.md5(file.getInputStream());
        
        // Verificar se o arquivo já existe no servidor via MD5
        Arquivo registroExistente = buscarPorMd5(md5);
        String urlRetorno;

        if (registroExistente != null) {
            urlRetorno = registroExistente.getLinkAcesso();
        } else {
            file.transferTo(arquivoFinal);
            urlRetorno = "http://localhost:9090/api/arquivos/download/" + nomeArmazenamento;
        }

        // Persistir metadados
        Arquivo novoArquivo = new Arquivo();
        novoArquivo.setNomeArquivo(nomeOriginal);
        novoArquivo.setExtensao(extensao);
        novoArquivo.setTamanhoKb(tamanho / 1024);
        novoArquivo.setLinkAcesso(urlRetorno);
        novoArquivo.setCodigoMd5(md5);
        arquivoMapper.insert(novoArquivo);

        return urlRetorno;
    }

    @GetMapping("/download/{identificador}")
    public void baixarArquivo(@PathVariable String identificador, HttpServletResponse response) throws IOException {
        File arquivoLocal = new File(caminhoBase + identificador);
        if (!arquivoLocal.exists()) return;

        byte[] dados = FileUtil.readBytes(arquivoLocal);
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(identificador, "UTF-8"));
        response.setContentType("application/octet-stream");
        
        OutputStream saida = response.getOutputStream();
        saida.write(dados);
        saida.flush();
        saida.close();
    }

    private Arquivo buscarPorMd5(String md5) {
        QueryWrapper<Arquivo> query = new QueryWrapper<>();
        query.eq("codigo_md5", md5);
        return arquivoMapper.selectOne(query);
    }
}

Implementação no Frontend (Vue.js + Element UI)

Componente de Upload

Utilizamos o componente el-upload para enviar o arquivo ao servidor e atualizar a interface após o sucesso.

<el-upload 
  action="http://localhost:9090/api/arquivos/enviar" 
  :show-file-list="false" 
  :on-success="onUploadSuccess">
  <el-button icon="el-icon-upload" type="success">Novo Upload</el-button>
</el-upload>

<script>
methods: {
  onUploadSuccess(response) {
    this.$message.success("Arquivo processado com sucesso!");
    this.carregarListaArquivos(); // Recarrega a grid
  }
}
</script>

Ação de Download

Para baixar o arquivo, basta abrir o link gerado pelo backend em uma nova aba ou janela.

<el-table-column label="Ações">
  <template slot-scope="scope">
    <el-button size="mini" @click="executarDownload(scope.row.linkAcesso)">Baixar</el-button>
  </template>
</el-table-column>

<script>
executarDownload(url) {
  window.open(url);
}
</script>

Tags: Spring Boot Vue.js MySQL MyBatis Plus Element UI

Publicado em 6-25 05:24