/
Sturon
/
Diplom
Обзор
Документация
Войти
/
Sturon
/
Diplom
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
backend/src/main/java/ru/diplom/vision/service/StorageService.java
210 строк
9 KB
Sturon
Initial
03 июн 2026, 15:01
03 июн 2026, 15:01
17b44a2
Код
Авторство
О чём код?
package ru.diplom.vision.service; import jakarta.annotation.PostConstruct; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.UUID; import javax.imageio.ImageIO; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; import ru.diplom.vision.config.StorageProperties; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; @Service public class StorageService { private static final DateTimeFormatter FILE_TIMESTAMP = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"); private static final String DEFAULT_IMAGE_EXTENSION = "png"; private final Path rootPath; public StorageService(StorageProperties storageProperties) { this.rootPath = Path.of(storageProperties.root()).toAbsolutePath().normalize(); } @PostConstruct public void initialize() { try { Files.createDirectories(rootPath); Files.createDirectories(rootPath.resolve("reference-images")); Files.createDirectories(rootPath.resolve("inspection-items/raw")); Files.createDirectories(rootPath.resolve("inspection-items/annotated")); Files.createDirectories(rootPath.resolve("defects")); } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось инициализировать хранилище"); } } public StoredFileDescriptor storeUploadedImage(String folder, MultipartFile file, String preferredName) { if (file.isEmpty()) { throw new ResponseStatusException(BAD_REQUEST, "Файл изображения пустой"); } String extension = extensionFromName(file.getOriginalFilename()); String safeFileName = generateFileName(preferredName, extension); Path folderPath = ensureFolder(folder); Path target = folderPath.resolve(safeFileName); try (InputStream inputStream = file.getInputStream()) { Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING); BufferedImage image = ImageIO.read(target.toFile()); if (image == null) { Files.deleteIfExists(target); throw new ResponseStatusException(BAD_REQUEST, "Загруженный файл не является поддерживаемым изображением"); } return new StoredFileDescriptor(toRelativePath(target), image.getWidth(), image.getHeight(), detectMimeType(target)); } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось сохранить изображение"); } } public String storeBufferedImage(String folder, BufferedImage image, String preferredName) { Path folderPath = ensureFolder(folder); String fileName = generateFileName(preferredName, DEFAULT_IMAGE_EXTENSION); Path target = folderPath.resolve(fileName); try { ImageIO.write(image, DEFAULT_IMAGE_EXTENSION, target.toFile()); return toRelativePath(target); } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось сохранить обработанное изображение"); } } public String copyExistingFile(String folder, Path source, String preferredName) { if (!Files.exists(source) || Files.isDirectory(source)) { throw new ResponseStatusException(BAD_REQUEST, "Файл источника изображения не найден"); } Path folderPath = ensureFolder(folder); String extension = extensionFromName(source.getFileName().toString()); String fileName = generateFileName(preferredName, extension); Path target = folderPath.resolve(fileName); try { Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); return toRelativePath(target); } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось скопировать изображение"); } } public BufferedImage readImage(String relativePath) { if (relativePath == null || relativePath.isBlank()) { throw new ResponseStatusException(BAD_REQUEST, "Путь к изображению не задан"); } Path target = resolve(relativePath); try { BufferedImage image = ImageIO.read(target.toFile()); if (image == null) { throw new ResponseStatusException(BAD_REQUEST, "Изображение не удалось прочитать"); } return image; } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось прочитать изображение"); } } public String resolvePublicUrl(String relativePath) { if (relativePath == null || relativePath.isBlank()) { return null; } return "/storage/" + relativePath.replace('\\', '/'); } public void delete(String relativePath) { if (relativePath == null || relativePath.isBlank()) { return; } try { Files.deleteIfExists(resolve(relativePath)); } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось удалить изображение"); } } public boolean isWritable() { try { Files.createDirectories(rootPath); Path probe = rootPath.resolve(".healthcheck-" + UUID.randomUUID() + ".tmp"); Files.writeString(probe, "ok", StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); Files.deleteIfExists(probe); return true; } catch (IOException exception) { return false; } } public Path resolve(String relativePath) { Path resolved = rootPath.resolve(relativePath).normalize(); if (!resolved.startsWith(rootPath)) { throw new IllegalArgumentException("Некорректный путь к файлу"); } return resolved; } private Path ensureFolder(String folder) { Path targetFolder = rootPath.resolve(folder).normalize(); if (!targetFolder.startsWith(rootPath)) { throw new IllegalArgumentException("Некорректный путь хранилища"); } try { Files.createDirectories(targetFolder); return targetFolder; } catch (IOException exception) { throw new ResponseStatusException(INTERNAL_SERVER_ERROR, "Не удалось подготовить директорию хранилища"); } } private String toRelativePath(Path path) { return rootPath.relativize(path).toString().replace('\\', '/'); } private String generateFileName(String preferredName, String extension) { String baseName = sanitizeFileName(preferredName); if (baseName.isBlank()) { baseName = "image"; } String timestamp = LocalDateTime.now().format(FILE_TIMESTAMP); return "%s-%s-%s.%s".formatted(baseName, timestamp, UUID.randomUUID().toString().substring(0, 8), extension); } private String sanitizeFileName(String raw) { if (raw == null) { return ""; } return raw.toLowerCase(Locale.ROOT) .replaceAll("\\.[a-z0-9]{1,5}$", "") .replaceAll("[^a-z0-9-_]+", "-") .replaceAll("-{2,}", "-") .replaceAll("^-|-$", ""); } private String extensionFromName(String fileName) { if (fileName == null || !fileName.contains(".")) { return DEFAULT_IMAGE_EXTENSION; } String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); return extension.isBlank() ? DEFAULT_IMAGE_EXTENSION : extension; } private String detectMimeType(Path path) throws IOException { String mimeType = Files.probeContentType(path); return mimeType == null ? "image/png" : mimeType; } public record StoredFileDescriptor(String relativePath, int width, int height, String mimeType) { } }