Gestion d’images avec Spring Boot : téléversement et exposition HTTP

Spring Boot embarque Tomcat, aucune installation supplémentaire n’est requise. Il suffit de déposer les fichiers dans un dossier local puis d’exposer ce dossier via une URL pour que les navigateurs y accèdent.

Dépendanecs Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Contrôleur REST

package com.demo.media.controller;

import com.demo.media.payload.UploadResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class MediaUploadController {

    private static final DateTimeFormatter PATH_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd");

    @Value("${storage.root}")
    private String rootFolder;

    @PostMapping("/photos")
    public ResponseEntity<UploadResponse> handleUpload(@RequestParam("file") MultipartFile file) {
        LocalDate today = LocalDate.now();
        String subFolder = today.format(PATH_FORMAT);

        Path targetDir = Paths.get(rootFolder, subFolder);
        if (!Files.exists(targetDir)) {
            try {
                Files.createDirectories(targetDir);
            } catch (IOException e) {
                return ResponseEntity.internalServerError()
                        .body(new UploadResponse("500", "Impossible de créer le dossier", null));
            }
        }

        String extension = com.google.common.io.Files.getFileExtension(file.getOriginalFilename());
        String randomName = UUID.randomUUID().toString().replace("-", "") + "." + extension;
        Path destination = targetDir.resolve(randomName);

        try {
            Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            return ResponseEntity.internalServerError()
                    .body(new UploadResponse("500", "Erreur d’écriture", null));
        }

        String publicUrl = "/photos/" + subFolder + "/" + randomName;
        return ResponseEntity.ok(new UploadResponse("200", "Envoi réussi", publicUrl));
    }
}

Classe de réponse JSON

package com.demo.media.payload;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class UploadResponse {
    private String status;
    private String message;
    private String url;
}

Configuration du mapping statique

package com.demo.media.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class StaticResourceConfig implements WebMvcConfigurer {

    @Value("${storage.root}")
    private String diskLocation;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/photos/**")
                .addResourceLocations("file:" + diskLocation);
    }
}

Paramètres appilcatifs (application.yml)

storage:
  root: C:/media/

server:
  port: 8080

spring:
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

Après démarrage, un fichier déposé via POST /api/photos sera accessible à l’adresse :

http://localhost:8080/photos/2024/05/12/7f3a9c2b.jpg

Étiquettes: spring-boot multipart-upload resource-mapping Java web-mvc

Publié le 29 juillet à 10h46