
En este tutorial vamos a realizar el backend en Spring boot de un proyecto para subir múltiples/varios archivos MultipartFile[]. Si quieres ver la parte frontend da clic aquí
El resultado que vamos a obtener con este proyecto mas la parte de Front en Angular es este:

Nuestro archivos se van a guardar en una carpeta que definamos en nuestro backend

Tabla de Contenido
Creación del proyecto en Spring Initializr.
Nos dirigimos a la página de Spring Initializr donde vamos a crear un proyecto con la siguiente estructura:

Presionamos generate y una vez descargado, lo vamos a abrir con nuestro IDE preferido, yo voy a usar IntelliJ IDEA
Creación de estructura del proyecto
Creamos los siguientes paquetes y clases:

- Controller –> Para exponer nuestros endpoints.
- Exception –> Para manejar la excepción de tamaño excedido
- Message –> Modelo del mensaje que vamos a devolver en las respuestas
- Model –> Modelo del archivo
- Service -> Interfaz donde vamos a declarar los métodos a implementar, InterfazImp es ya la implementación de la lógica de los métodos de la interfaz
Creación del modelo del archivo
Paquete model
public class FileModel { private String name; private String url; public FileModel() { } public FileModel(String name, String url) { this.name = name; this.url = url; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
Creación de la Interfaz de Servicio
Paquete service
import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Path; import java.util.stream.Stream; public interface FileService { /* Metodo para crear la carpeta donde vamos a guardar los archivos */ public void init(); /* Metodo para guardar los archivos */ public void save(MultipartFile file); /* Metodo para cargar un archivo */ public Resource load(String filename); /* Metodo para borrar todos los archivos cada vez que se inicie el servidor */ public void deleteAll(); /* Metodo para Cargar todos los archivos */ public Stream<Path> loadAll(); /* Metodo para Borrar un archivo */ public String deleteFile(String filename); }
Implementación de la interfaz
Paquete Service
import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.util.FileSystemUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; @Service public class FileServiceImp implements FileService { //Nombre de la carpeta donde vamos a almacenar los archivos //Se crea a nivel de raiz la carpeta private final Path root = Paths.get("uploads"); @Override public void init() { try { Files.createDirectory(root); } catch (IOException e) { throw new RuntimeException("No se puede inicializar la carpeta uploads"); } } @Override public void save(MultipartFile file) { try { //copy (que queremos copiar, a donde queremos copiar) Files.copy(file.getInputStream(), this.root.resolve(file.getOriginalFilename())); } catch (IOException e) { throw new RuntimeException("No se puede guardar el archivo. Error " + e.getMessage()); } } @Override public Resource load(String filename) { try { Path file = root.resolve(filename); Resource resource = new UrlResource(file.toUri()); if(resource.exists() || resource.isReadable()){ return resource; }else{ throw new RuntimeException("No se puede leer el archivo "); } }catch (MalformedURLException e){ throw new RuntimeException("Error: " + e.getMessage()); } } @Override public void deleteAll() { FileSystemUtils.deleteRecursively(root.toFile()); } @Override public Stream<Path> loadAll(){ //Files.walk recorre nuestras carpetas (uploads) buscando los archivos // el 1 es la profundidad o nivel que queremos recorrer // :: Referencias a metodos // Relativize sirve para crear una ruta relativa entre la ruta dada y esta ruta try{ return Files.walk(this.root,1).filter(path -> !path.equals(this.root)) .map(this.root::relativize); }catch (RuntimeException | IOException e){ throw new RuntimeException("No se pueden cargar los archivos "); } } @Override public String deleteFile(String filename){ try { Boolean delete = Files.deleteIfExists(this.root.resolve(filename)); return "Borrado"; }catch (IOException e){ e.printStackTrace(); return "Error Borrando "; } } }
Modelo de respuesta del mensaje
Paquete message
public class FileMessage { private String message; public FileMessage(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Creación del controlador
Paquete controller
package com.vmanuelpm.controller; import com.vmanuelpm.message.FileMessage; import com.vmanuelpm.model.FileModel; import com.vmanuelpm.service.FileService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @Controller @CrossOrigin("*") public class FileController { //Inyectamos el servicio @Autowired FileService fileService; @PostMapping("/upload") public ResponseEntity<FileMessage> uploadFiles(@RequestParam("files")MultipartFile[] files){ String message = ""; try{ List<String> fileNames = new ArrayList<>(); Arrays.asList(files).stream().forEach(file->{ fileService.save(file); fileNames.add(file.getOriginalFilename()); }); message = "Se subieron los archivos correctamente " + fileNames; return ResponseEntity.status(HttpStatus.OK).body(new FileMessage(message)); }catch (Exception e){ message = "Fallo al subir los archivos"; return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new FileMessage(message)); } } @GetMapping("/files") public ResponseEntity<List<FileModel>> getFiles(){ List<FileModel> fileInfos = fileService.loadAll().map(path -> { String filename = path.getFileName().toString(); String url = MvcUriComponentsBuilder.fromMethodName(FileController.class, "getFile", path.getFileName().toString()).build().toString(); return new FileModel(filename, url); }).collect(Collectors.toList()); return ResponseEntity.status(HttpStatus.OK).body(fileInfos); } @GetMapping("/files/{filename:.+}") public ResponseEntity<Resource> getFile(@PathVariable String filename){ Resource file = fileService.load(filename); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+file.getFilename() + "\"").body(file); } @GetMapping("/delete/{filename:.+}") public ResponseEntity<FileMessage> deleteFile(@PathVariable String filename) { String message = ""; try { message = fileService.deleteFile(filename); return ResponseEntity.status(HttpStatus.OK).body(new FileMessage(message)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new FileMessage(message)); } } }
- @CrossOrigin(*) esta configurado de esta manera para permitir que apunten a nuestros endpoint desde cualquier origen.
- Importante colocar la anotación @Controller para indicarle a Spring que es un controlador
- @Autowired es para indicar que vamos a realizar una inyección de dependencias.
- @GetMapping y @PostMapping son el tipo de petición HTTP que indicamos
Configuración del tamaño máximo del Multipart
En el archivo application.properties vamos a colocar lo siguiente:
#Maximo del archivo para cada request spring.servlet.multipart.max-file-size=500KB #Maximo del multipart spring.servlet.multipart.max-request-size=500KB
Creación de la excepción de tamaño máximo excedido
Paquete exception
//este import hace referencia al modelo del mensaje import com.vmanuelpm.message.FileMessage; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.multipart.MaxUploadSizeExceededException; @ControllerAdvice public class FileUploadException { @ExceptionHandler(MaxUploadSizeExceededException.class) public ResponseEntity<FileMessage> maxSizeException(MaxUploadSizeExceededException exc){ return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED) .body(new FileMessage("Uno o mas archivos exceden el tamaño maximo")); } }
En caso de que el tamaño máximo sea excedido cuando realicen una petición se va a dispara esta excepción MaxUploadSizeExceededException
Iniciar la creación del storage
El metodo init() creado anteriormente en la interfaz del servicio nos permite crear nuestro storage, en este caso la carpeta “uploads” en la raíz del proyecto, además cada vez que hagamos un bootRun se limpia el storage con el método deleteAll(), en el archivo main de nuestro proyecto hacemos lo siguiente
import com.vmanuelpm.service.FileService; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; @SpringBootApplication public class UploadFilesApplication implements CommandLineRunner { @Resource FileService fileService; public static void main(String[] args) { SpringApplication.run(UploadFilesApplication.class, args); } @Override public void run(String... arg) throws Exception{ fileService.deleteAll(); fileService.init(); } /* En caso que tenga problema con los cors agregar este Bean */ @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST","PUT", "DELETE"); } }; } }
IMPORTANTE no olvidar implementar CommandLineRunner ya que nos permite ejecutar código después de que la aplicación de Spring inicia.
Probando los servicios
Primero construimos nuestra aplicación:

Luego corremos la aplicación:

Correr aplicación por medio de comando:
./gradlew bootRun
Utilizamos Postman para probar nuestros servicios:




En un navegador para obtener el archivo coloca el siguiente endpoint
http://localhost:8080/files/firma.png

Dejo el link al repositorio de este proyecto para que lo puedas ver si tienes dudas.
Este es el tutorial en video
Si quieres realizar la parte Front da clic aquí
2 Replies to “Angular y Spring. Múltiple subida de archivos (Backend) con Java Spring Boot”