From d29258855e4c28e4e57d1f01430273e92906d8e3 Mon Sep 17 00:00:00 2001 From: backdev-1 Date: Wed, 22 Apr 2026 16:24:43 +0700 Subject: [PATCH] dev merge --- .../controller/MigrationController.java | 41 ++++++ .../nocopy/migration/S3MigrationTool.java | 119 ++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 src/main/java/ru/soune/nocopy/controller/MigrationController.java create mode 100644 src/main/java/ru/soune/nocopy/migration/S3MigrationTool.java diff --git a/src/main/java/ru/soune/nocopy/controller/MigrationController.java b/src/main/java/ru/soune/nocopy/controller/MigrationController.java new file mode 100644 index 0000000..b898e95 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/controller/MigrationController.java @@ -0,0 +1,41 @@ +package ru.soune.nocopy.controller; + + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.soune.nocopy.migration.S3MigrationTool; + +import java.util.concurrent.CompletableFuture; + +@RestController +@RequestMapping("/api/admin/migration") +@RequiredArgsConstructor +@Slf4j +public class MigrationController { + + private final S3MigrationTool migrationTool; + + @PostMapping("/start") + public ResponseEntity startMigration() { + log.info("Migration triggered via API"); + + CompletableFuture.runAsync(() -> { + try { + migrationTool.migrationFilesToS3(); + } catch (Exception e) { + log.error("Migration failed", e); + } + }); + + return ResponseEntity.ok("Migration started. Check logs for progress."); + } + + @PostMapping("/status") + public ResponseEntity getStatus() { + return ResponseEntity.ok("Check application logs and migration_failed.txt for details"); + } +} diff --git a/src/main/java/ru/soune/nocopy/migration/S3MigrationTool.java b/src/main/java/ru/soune/nocopy/migration/S3MigrationTool.java new file mode 100644 index 0000000..cdb28e5 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/migration/S3MigrationTool.java @@ -0,0 +1,119 @@ +package ru.soune.nocopy.migration; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.repository.FileEntityRepository; +import ru.soune.nocopy.service.file.cloud.CloudStorageService; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +@Component +@Slf4j +@RequiredArgsConstructor +public class S3MigrationTool { + + private final FileEntityRepository fileEntityRepository; + private final CloudStorageService cloudStorageService; + + private static final Path FAILED_LOG = Paths.get("migration_failed.txt"); + + public void migrationFilesToS3() { + log.info("=== START MIGRATION ==="); + clearLogFile(); + + List allFiles = fileEntityRepository.findAll(); + log.info("Total files: {}", allFiles.size()); + + int success = 0; + List failedIds = new ArrayList<>(); + + for (FileEntity file : allFiles) { + if (migrateFile(file)) { + success++; + } else { + failedIds.add(file.getId()); + } + } + + log.info("=== COMPLETED: success {}, failed {} ===", success, failedIds.size()); + if (!failedIds.isEmpty()) { + log.info("Failed file IDs: {}", String.join(", ", failedIds)); + log.info("Details saved to: {}", FAILED_LOG.toAbsolutePath()); + } + } + + private boolean migrateFile(FileEntity file) { + StringBuilder errors = new StringBuilder(); + + try { + cloudStorageService.saveFilesToStorage( + file.getMimeType(), file.getFileExtension(), file.getFilePath() + ); + } catch (Exception e) { + errors.append("main:").append(e.getMessage()).append("; "); + } + + if ("image".equals(file.getMimeType())) { + try { + cloudStorageService.saveFilesToStorage( + file.getMimeType(), file.getFileExtension(), file.getMediumPath() + ); + } catch (Exception e) { + errors.append("medium:").append(e.getMessage()).append("; "); + } + try { + cloudStorageService.saveFilesToStorage( + file.getMimeType(), file.getFileExtension(), file.getThumbnailPath() + ); + } catch (Exception e) { + errors.append("thumb:").append(e.getMessage()).append("; "); + } + } + + if (file.getProtectedFilePath() != null) { + try { + cloudStorageService.saveFilesToStorage( + file.getMimeType() != null ? file.getMimeType() : "application", + file.getFileExtension(), + file.getProtectedFilePath() + ); + } catch (Exception e) { + errors.append("protected:").append(e.getMessage()).append("; "); + } + } + + if (errors.length() > 0) { + writeToFile(String.format("%s | %s | %s", + file.getId(), file.getOriginalFileName(), errors)); + log.warn("Failed to migrate: {} | {}", file.getId(), file.getOriginalFileName()); + return false; + } + + return true; + } + + private void clearLogFile() { + try { + Files.deleteIfExists(FAILED_LOG); + } catch (IOException e) { + log.warn("Failed to clear log file"); + } + } + + private void writeToFile(String line) { + try { + Files.write(FAILED_LOG, (line + "\n").getBytes(), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } catch (IOException e) { + log.error("Failed to write to log file", e); + } + } +} \ No newline at end of file