@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user