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>