From 86f7fc593a08fc49a276d93dbc5cc60730870e48 Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 10 Feb 2026 13:26:44 +0700 Subject: [PATCH] dev add utf encoding --- .../handler/ImageFoundRequestHandler.java | 22 ++- .../SearchApiToYandexResponseMapper.java | 1 + .../service/search/YandexSearchService.java | 153 +++++++++++------- 3 files changed, 114 insertions(+), 62 deletions(-) diff --git a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java index 5a0868b..37112b3 100644 --- a/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/ImageFoundRequestHandler.java @@ -18,7 +18,10 @@ import ru.soune.nocopy.service.search.GoogleVisionSearchService; import ru.soune.nocopy.service.tariff.TariffConstants; import ru.soune.nocopy.service.tariff.TariffInfoService; +import java.io.IOException; +import java.util.List; import java.util.Map; +import java.util.concurrent.TimeoutException; @Slf4j @Component @@ -58,7 +61,24 @@ public class ImageFoundRequestHandler implements RequestHandler { //TODO uncommited when add billing // GoogleVisionSearchResponse googleVisionSearchResponse = googleVisionSearchService.searchByFileEntity(fileId); - String yandexReverseSearchResponse = yandexSearchService.searchReverseByPublicUrl(fileEntity); + String yandexReverseSearchResponse; + try { + yandexReverseSearchResponse = yandexSearchService.searchReverseByPublicUrlWithTimeout(fileEntity, 20); + } catch (TimeoutException e) { + log.warn("Search timeout for file {}, returning empty results", fileId); + return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), + "Search completed (partial)", Map.of( + "results", List.of(), + "total", 0, + "searchStatus", "timeout" + )); + } catch (IOException e) { + log.error("Search failed for file {}", fileId, e); + return new BaseResponse(request.getMsgId(), 20007, + "Search service temporarily unavailable", + Map.of("fileId", fileId)); + } + int page = imageSearchRequest.getPage() != null ? imageSearchRequest.getPage() : 1; int pageSize = imageSearchRequest.getPageSize() != null ? imageSearchRequest.getPageSize() : 10; diff --git a/src/main/java/ru/soune/nocopy/service/search/SearchApiToYandexResponseMapper.java b/src/main/java/ru/soune/nocopy/service/search/SearchApiToYandexResponseMapper.java index 1758d16..5403d04 100644 --- a/src/main/java/ru/soune/nocopy/service/search/SearchApiToYandexResponseMapper.java +++ b/src/main/java/ru/soune/nocopy/service/search/SearchApiToYandexResponseMapper.java @@ -50,6 +50,7 @@ public class SearchApiToYandexResponseMapper { } response.setImages(imageResults); + return response; } diff --git a/src/main/java/ru/soune/nocopy/service/search/YandexSearchService.java b/src/main/java/ru/soune/nocopy/service/search/YandexSearchService.java index f062098..2e7a6d1 100644 --- a/src/main/java/ru/soune/nocopy/service/search/YandexSearchService.java +++ b/src/main/java/ru/soune/nocopy/service/search/YandexSearchService.java @@ -21,7 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; @Slf4j @Service @@ -102,66 +102,6 @@ public class YandexSearchService { return callReverseImageApiByUrl(publicUrl); } -// private String callReverseImageApiByUrl(String imageUrl) throws IOException { -// if (searchApiKey == null || searchApiKey.isEmpty()) { -// throw new IllegalStateException("SearchAPI key not configured. Set searchapi.api-key property"); -// } -// -// if (imageUrl == null || imageUrl.trim().isEmpty()) { -// throw new IllegalArgumentException("Image URL cannot be null or empty"); -// } -// -// HttpUrl url = HttpUrl.parse("https://searchapi.io/api/v1/search") -// .newBuilder() -// .addQueryParameter("engine", "yandex_reverse_image") -// .addQueryParameter("api_key", searchApiKey) -// .addQueryParameter("url", imageUrl) -// .addQueryParameter("wait", "true") -// .addQueryParameter("timeout", "30000") -// .build(); -// -// log.info("Calling Yandex Reverse Image API for URL: {}", imageUrl); -// log.debug("Full API URL: {}", url); -// -// Request request = new Request.Builder() -// .url(url) -// .header("Accept", "application/json") -// .header("User-Agent", "Mozilla/5.0 (compatible; YourApp/1.0)") -// .header("Accept-Encoding", "gzip") -// .build(); -// -// try (Response response = httpClient.newCall(request).execute()) { -// ResponseBody responseBody = response.body(); -// -// log.info("Response code: {}", response.code()); -// log.debug("Response headers: {}", response.headers()); -// -// if (!response.isSuccessful()) { -// String errorBody = responseBody != null ? responseBody.string() : "No error body"; -// log.error("API request failed: {} - {}", response.code(), errorBody); -// throw new IOException("API request failed: " + response.code() + " - " + response.message()); -// } -// -// if (responseBody == null) { -// log.error("Response body is null for successful request"); -// throw new IOException("Response body is null"); -// } -// -// String responseContent = responseBody.string(); -// log.info("Response body length: {} characters", responseContent.length()); -// -// if (log.isDebugEnabled() && responseContent.length() > 0) { -// log.debug("Response body (first 500 chars): {}", -// responseContent.substring(0, Math.min(responseContent.length(), 500))); -// } -// -// return responseContent; -// } catch (IOException e) { -// log.error("Failed to call Yandex Reverse Image API for URL: {}", imageUrl, e); -// throw e; -// } -// } - private String callReverseImageApiByUrl(String imageUrl) throws IOException { if (searchApiKey == null || searchApiKey.isEmpty()) { throw new IllegalStateException("SearchAPI key not configured. Set searchapi.api-key property"); @@ -267,6 +207,97 @@ public class YandexSearchService { } } + public String searchReverseByPublicUrlWithTimeout(FileEntity fileEntity, int timeoutSeconds) + throws IOException, TimeoutException { + + if (!isImageFile(fileEntity)) { + log.error("File not image: {}", fileEntity.getMimeType()); + throw new NotValidFieldException("File not image", new BaseResponse(20007, + MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(), + Map.of("file_type", fileEntity.getMimeType()))); + } + + String publicUrl = String.format("%s/api/files/public/%s", appBaseUrl, fileEntity.getId()); + log.info("Searching reverse for image: {}", publicUrl); + + // Создаем ExecutorService с одним потоком + ExecutorService executor = Executors.newSingleThreadExecutor(); + + try { + // Запускаем задачу с таймаутом + Future future = executor.submit(() -> + callReverseImageApiByUrlWithTimeout(publicUrl, timeoutSeconds) + ); + + // Получаем результат с таймаутом + return future.get(timeoutSeconds, TimeUnit.SECONDS); + + } catch (TimeoutException e) { + log.error("Yandex search timeout after {} seconds for file: {}", timeoutSeconds, fileEntity.getId()); + throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("Search interrupted", e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; + } + throw new IOException("Search failed", cause); + } finally { + executor.shutdownNow(); + } + } + + private String callReverseImageApiByUrlWithTimeout(String imageUrl, int timeoutSeconds) throws IOException { + if (searchApiKey == null || searchApiKey.isEmpty()) { + throw new IllegalStateException("SearchAPI key not configured"); + } + + int apiTimeout = Math.max(1, timeoutSeconds - 2) * 1000; + + HttpUrl url = HttpUrl.parse("https://searchapi.io/api/v1/search") + .newBuilder() + .addQueryParameter("engine", "yandex_reverse_image") + .addQueryParameter("api_key", searchApiKey) + .addQueryParameter("url", imageUrl) + .addQueryParameter("wait", "true") + .addQueryParameter("timeout", String.valueOf(apiTimeout)) + .build(); + + log.info("Calling Yandex API with timeout {}ms: {}", apiTimeout, imageUrl); + + Request request = new Request.Builder() + .url(url) + .header("Accept", "application/json") + .header("User-Agent", "Mozilla/5.0") + .build(); + + long startTime = System.currentTimeMillis(); + + try (Response response = httpClient.newCall(request).execute()) { + long duration = System.currentTimeMillis() - startTime; + + log.info("Response code: {}, duration: {}ms", response.code(), duration); + + if (!response.isSuccessful()) { + String errorBody = response.body() != null ? response.body().string() : "No error body"; + log.error("API request failed: {} - {}", response.code(), errorBody); + throw new IOException("API request failed: " + response.code()); + } + + ResponseBody body = response.body(); + if (body == null) { + throw new IOException("Empty response body"); + } + + String responseBody = body.string(); + log.info("Response body length: {} characters", responseBody.length()); + + return responseBody; + } + } + private String readAll(BufferedReader reader) throws IOException { StringBuilder sb = new StringBuilder(); String line;