dev conver files
Test Workflow / test (push) Successful in 4s

This commit is contained in:
vladp
2026-02-26 00:27:31 +07:00
parent 45010bb79a
commit 983b5eddda
8 changed files with 172 additions and 89 deletions
@@ -0,0 +1,44 @@
package ru.soune.nocopy.configuration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.file.ImageResizeService;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class ImageMigration {
@Autowired
private FileEntityRepository fileRepository;
@Autowired
private ImageResizeService imageResizeService;
@EventListener(ApplicationReadyEvent.class)
public void migrate() {
List<FileEntity> images = fileRepository.findByThumbnailPathIsNull();
for (FileEntity file : images) {
try {
Path path = Paths.get(file.getProtectedFilePath());
if (Files.exists(path)) {
byte[] data = Files.readAllBytes(path);
imageResizeService.generateSizes(file, data);
fileRepository.save(file);
}
} catch (Exception e) {
log.error("Error migrating {}: {}", file.getId(), e.getMessage());
}
}
}
}
@@ -14,6 +14,7 @@ import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.file.CheckCounterService;
import ru.soune.nocopy.service.file.FileStorageService;
import ru.soune.nocopy.service.file.ImageResizeService;
import java.io.FileNotFoundException;
import java.net.URLEncoder;
@@ -41,24 +42,8 @@ public class FileController {
@Autowired
private UserRepository userRepository;
// @GetMapping("/public/{fileId}")
// public ResponseEntity<Resource> getPublicFile(
// @PathVariable String fileId) throws IOException {
//
// FileEntity fileEntity = fileRepository.findById(fileId)
// .orElseThrow(() -> new RuntimeException("File not found"));
//
// Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath());
//
// ContentDisposition contentDisposition = ContentDisposition.inline()
// .filename(fileEntity.getOriginalFileName(), StandardCharsets.UTF_8)
// .build();
//
// return ResponseEntity.ok()
// .contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension()))
// .header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString())
// .body(resource);
// }
@Autowired
private ImageResizeService imageResizeService;
@GetMapping("/public/{fileId}")
public ResponseEntity<Resource> getPublicFile(@PathVariable String fileId) {
@@ -117,14 +102,16 @@ public class FileController {
}
}
@GetMapping("/protected/{fileId}")
@GetMapping("/protected/{fileId}/{type}")
public ResponseEntity<Resource> getProtectedFile(
@PathVariable String fileId) throws IOException {
@PathVariable String fileId, @PathVariable String type) throws IOException {
FileEntity fileEntity = fileRepository.findById(fileId)
.orElseThrow(() -> new RuntimeException("File not found"));
Resource resource = fileStorageService.loadFileAsResource(fileEntity.getProtectedFilePath());
String path = imageResizeService.getPath(fileEntity, type);
Resource resource = fileStorageService.loadFileAsResource(path);
if (fileEntity.getProtectionStatus() == ProtectionStatus.NOT_PROTECTED ||
fileEntity.getProtectionStatus() == ProtectionStatus.PROCESSING) {
@@ -81,6 +81,12 @@ public class FileEntity {
@JsonIgnore
private String signature;
@Column(name = "thumbnail_path")
private String thumbnailPath;
@Column(name = "medium_path")
private String mediumPath;
@OneToOne(mappedBy = "file", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private ImageHashEntity imageHash;
@@ -61,4 +61,6 @@ public interface FileEntityRepository extends JpaRepository<FileEntity, String>
@Query("SELECT MAX(f.supportId) FROM FileEntity f")
Integer findMaxSupportId();
List<FileEntity> findByThumbnailPathIsNull();
}
@@ -211,7 +211,8 @@ public class FileSimilarityService {
.supportId(similarImageProjection.getSupportId())
.uploadDate(similarImageProjection.getUploadDate())
.status(similarImageProjection.getProtectionStatus())
.url(baseUrl + "/api/files/protected/" + similarImageProjection.getId())
// .url(baseUrl + "/api/files/protected/" + similarImageProjection.getId())
.url(baseUrl + "/api/files/protected/" + similarImageProjection.getId() + "/thumbnail")
.build();
}
@@ -255,13 +255,14 @@ public class FileEntityService {
fileEntityRepository.save(fileEntity);
}
private ImageResizeService imageResizeService;
@Transactional
public void writeProtectedFile(String id, byte[] data, String fileExt) throws IOException {
FileEntity fileEntity = fileEntityRepository.findById(id)
.orElseThrow(() -> new RuntimeException("File not found: " + id));
String extension = determineFileExtension(fileExt, fileEntity);
log.info("EXTENSION: {}", extension);
Path protectedFilePath = prepareProtectedPath(fileEntity, extension);
if (Files.exists(protectedFilePath)) {
@@ -270,6 +271,10 @@ public class FileEntityService {
Files.write(protectedFilePath, data);
if (imageResizeService.isImage(extension)) {
imageResizeService.generateSizes(fileEntity, data);
}
fileEntity.setProtectedFilePath(protectedFilePath.toString());
fileEntity.setProtectedAt(LocalDateTime.now());
fileEntity.setUpdatedAt(LocalDateTime.now());
@@ -0,0 +1,104 @@
package ru.soune.nocopy.service.file;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.entity.file.FileEntity;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
@Service
@Slf4j
public class ImageResizeService {
private static final int THUMBNAIL_SIZE = 150;
private static final int MEDIUM_SIZE = 600;
@Value("${file.storage.path}")
private String storagePath;
/**
* Проверяет, является ли файл изображением
*/
public boolean isImage(String extension) {
return extension != null && List.of("jpg", "jpeg", "png", "gif", "webp")
.contains(extension.toLowerCase());
}
/**
* Генерирует два размера для изображения
*/
@Transactional
public void generateSizes(FileEntity file, byte[] data) throws IOException {
if (!isImage(file.getFileExtension())) {
return;
}
try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
BufferedImage original = ImageIO.read(bis);
if (original == null) {
log.warn("Cannot read image: {}", file.getId());
return;
}
String baseName = file.getId().replace(".", "_");
if (original.getWidth() > THUMBNAIL_SIZE) {
String thumbPath = resizeAndSave(original, baseName + "_thumb", THUMBNAIL_SIZE);
file.setThumbnailPath(thumbPath);
}
if (original.getWidth() > MEDIUM_SIZE) {
String mediumPath = resizeAndSave(original, baseName + "_medium", MEDIUM_SIZE);
file.setMediumPath(mediumPath);
}
}
}
/**
* Ресайзит и сохраняет
*/
private String resizeAndSave(BufferedImage original, String fileName, int targetWidth) throws IOException {
int targetHeight = (int) ((double) original.getHeight() / original.getWidth() * targetWidth);
BufferedImage resized = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(original, 0, 0, targetWidth, targetHeight, null);
g.dispose();
Path dirPath = Paths.get(storagePath, "resized");
Files.createDirectories(dirPath);
Path filePath = dirPath.resolve(fileName + ".jpg");
ImageIO.write(resized, "jpg", filePath.toFile());
return filePath.toString();
}
/**
* Получить путь к нужному размеру
*/
public String getPath(FileEntity file, String size) {
if ("thumbnail".equals(size) && file.getThumbnailPath() != null) {
return file.getThumbnailPath();
}
if ("medium".equals(size) && file.getMediumPath() != null) {
return file.getMediumPath();
}
return file.getProtectedFilePath();
}
}
@@ -127,72 +127,6 @@ public class SearchImageService {
return mimeType != null && mimeType.startsWith("image");
}
// private String callReverseImageApiByUrl(String imageUrl, String engine, String searchType) throws IOException {
// if (searchApiKey == null || searchApiKey.isBlank()) {
// throw new IllegalStateException("SearchAPI key not configured");
// }
//
// OkHttpClient client = engine.contains("yandex_reverse_image") ? yandexHttpClient : googleHttpClient;
//
// HttpUrl url = HttpUrl.parse("https://www.searchapi.io/api/v1/search")
// .newBuilder()
// .addQueryParameter("engine", engine)
// .addQueryParameter("api_key", searchApiKey)
// .addQueryParameter("url", imageUrl)
// .addQueryParameter("search_type", searchType)
// .addQueryParameter("t_", String.valueOf(System.currentTimeMillis()))
// .build();
//
// Request request = new Request.Builder()
// .url(url)
// .header("Accept", "application/json")
// .header("User-Agent", "Mozilla/5.0")
// .build();
//
// int maxAttempts = 2;
// IOException lastException = null;
//
// for (int attempt = 1; attempt <= maxAttempts; attempt++) {
// long start = System.currentTimeMillis();
//
// try (Response response = client.newCall(request).execute()) {
// long duration = System.currentTimeMillis() - start;
// log.info("Yandex response code={}, duration={}ms, attempt={}",
// response.code(), duration, attempt);
//
// if (!response.isSuccessful()) {
// throw new IOException("API error: " + response.code());
// }
//
// ResponseBody body = response.body();
// if (body == null) {
// throw new IOException("Empty response body");
// }
//
// return body.string();
//
// } catch (IOException e) {
// lastException = e;
// log.warn("Attempt {} failed for image: {}, error: {}",
// attempt, imageUrl, e.getMessage());
//
// if (attempt == maxAttempts) {
// log.error("All {} attempts failed for image: {}", maxAttempts, imageUrl);
// throw lastException;
// }
//
// try {
// Thread.sleep(500L * attempt);
// } catch (InterruptedException ie) {
// Thread.currentThread().interrupt();
// throw new IOException("Retry interrupted", ie);
// }
// }
// }
//
// throw lastException;
// }
private String callReverseImageApiByUrl(String imageUrl, String engine, String searchType) throws IOException {
if (searchApiKey == null || searchApiKey.isBlank()) {
throw new IllegalStateException("SearchAPI key not configured");