This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package ru.soune.nocopy.configuration;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
|
||||
requestFactory.setConnectTimeout(30000);
|
||||
requestFactory.setReadTimeout(60000);
|
||||
restTemplate.setRequestFactory(requestFactory);
|
||||
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package ru.soune.nocopy.dto.file;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class GoogleVisionSearchResponse {
|
||||
|
||||
@JsonProperty("bestGuessLabels")
|
||||
private List<BestGuessLabel> bestGuessLabels;
|
||||
|
||||
@JsonProperty("fullMatchingImages")
|
||||
private List<ImageResult> fullMatchingImages;
|
||||
|
||||
@JsonProperty("visuallySimilarImages")
|
||||
private List<ImageResult> visuallySimilarImages;
|
||||
|
||||
@JsonProperty("pagesWithMatchingImages")
|
||||
private List<PageResult> pagesWithMatchingImages;
|
||||
|
||||
@JsonProperty("partialMatchingImages")
|
||||
private List<ImageResult> partialMatchingImages;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class BestGuessLabel {
|
||||
@JsonProperty("label")
|
||||
private String label;
|
||||
|
||||
@JsonProperty("languageCode")
|
||||
private String languageCode;
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class ImageResult {
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
@JsonProperty("score")
|
||||
private Float score;
|
||||
|
||||
@JsonProperty("height")
|
||||
private Integer height;
|
||||
|
||||
@JsonProperty("width")
|
||||
private Integer width;
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class PageResult {
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
@JsonProperty("pageTitle")
|
||||
private String pageTitle;
|
||||
|
||||
@JsonProperty("fullMatchingImages")
|
||||
private List<ImageResult> fullMatchingImages;
|
||||
|
||||
@JsonProperty("partialMatchingImages")
|
||||
private List<ImageResult> partialMatchingImages;
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,14 @@ package ru.soune.nocopy.handler;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.soune.nocopy.dto.BaseRequest;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.file.GoogleVisionSearchResponse;
|
||||
import ru.soune.nocopy.dto.file.ImageSearchRequest;
|
||||
import ru.soune.nocopy.dto.file.YandexSearchResponse;
|
||||
import ru.soune.nocopy.service.GoogleVisionSearchService;
|
||||
import ru.soune.nocopy.service.YandexSearchService;
|
||||
|
||||
@Slf4j
|
||||
@@ -21,6 +22,8 @@ public class ImageFoundRequestHandler implements RequestHandler {
|
||||
|
||||
private final YandexSearchService yandexSearchService;
|
||||
|
||||
private final GoogleVisionSearchService googleVisionSearchService;
|
||||
|
||||
@Override
|
||||
public BaseResponse handle(BaseRequest request) throws Exception {
|
||||
ImageSearchRequest imageSearchRequest = objectMapper.convertValue(request.getMessageBody(),
|
||||
@@ -30,6 +33,9 @@ public class ImageFoundRequestHandler implements RequestHandler {
|
||||
|
||||
YandexSearchResponse response = yandexSearchService.searchByFileEntity(fileId);
|
||||
|
||||
//TODO uncommited when add billing
|
||||
// GoogleVisionSearchResponse googleVisionSearchResponse = googleVisionSearchService.searchByFileEntity(fileId);
|
||||
|
||||
return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
|
||||
MessageCode.SUCCESS.getDescription(), response);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
package ru.soune.nocopy.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.cloud.vision.v1.*;
|
||||
import com.google.protobuf.ByteString;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.soune.nocopy.dto.BaseResponse;
|
||||
import ru.soune.nocopy.dto.MessageCode;
|
||||
import ru.soune.nocopy.dto.file.GoogleVisionSearchResponse;
|
||||
import ru.soune.nocopy.entity.file.FileEntity;
|
||||
import ru.soune.nocopy.exception.NotValidFieldException;
|
||||
import ru.soune.nocopy.repository.FileEntityRepository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class GoogleVisionSearchService {
|
||||
private final FileEntityRepository fileEntityRepository;
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private ImageAnnotatorClient visionClient;
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws IOException {
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
|
||||
initVisionClient();
|
||||
}
|
||||
|
||||
private void initVisionClient() throws IOException {
|
||||
try (InputStream credentialsStream = new ClassPathResource("config/google-service-account.json").getInputStream()) {
|
||||
GoogleCredentials credentials = GoogleCredentials.fromStream(credentialsStream);
|
||||
|
||||
ImageAnnotatorSettings settings = ImageAnnotatorSettings.newBuilder()
|
||||
.setCredentialsProvider(() -> credentials)
|
||||
.build();
|
||||
|
||||
this.visionClient = ImageAnnotatorClient.create(settings);
|
||||
}
|
||||
}
|
||||
|
||||
public GoogleVisionSearchResponse searchByFileEntity(String fileId) throws IOException {
|
||||
FileEntity fileEntity = fileEntityRepository.findById(fileId)
|
||||
.orElseThrow(() -> {
|
||||
throw new NotValidFieldException("File not found", new BaseResponse(20007,
|
||||
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
|
||||
Map.of("fileId", fileId)));
|
||||
});
|
||||
|
||||
if (!isImageFile(fileEntity)) {
|
||||
throw new NotValidFieldException("File not image", new BaseResponse(20007,
|
||||
MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(),
|
||||
Map.of("file_type", fileEntity.getMimeType())));
|
||||
}
|
||||
|
||||
byte[] fileBytes;
|
||||
try {
|
||||
fileBytes = readFileFromDisk(fileEntity);
|
||||
} catch (IOException e) {
|
||||
throw new NotValidFieldException("File not found or cannot read file", new BaseResponse(20007,
|
||||
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
|
||||
Map.of("fileId", fileId,
|
||||
"filePath", fileEntity.getFilePath())));
|
||||
}
|
||||
|
||||
return callGoogleVisionApi(fileBytes);
|
||||
}
|
||||
|
||||
private boolean isImageFile(FileEntity fileEntity) {
|
||||
String mimeType = fileEntity.getMimeType();
|
||||
return mimeType != null && mimeType.startsWith("image");
|
||||
}
|
||||
|
||||
private byte[] readFileFromDisk(FileEntity fileEntity) throws IOException {
|
||||
Path filePath = Path.of(fileEntity.getFilePath());
|
||||
|
||||
if (!Files.exists(filePath) || !Files.isReadable(filePath)) {
|
||||
throw new IOException("Cannot read file: " + fileEntity.getFilePath());
|
||||
}
|
||||
|
||||
return Files.readAllBytes(filePath);
|
||||
}
|
||||
|
||||
private GoogleVisionSearchResponse callGoogleVisionApi(byte[] imageBytes) throws IOException {
|
||||
ByteString imgBytes = ByteString.copyFrom(imageBytes);
|
||||
Image image = Image.newBuilder()
|
||||
.setContent(imgBytes)
|
||||
.build();
|
||||
|
||||
Feature feature = Feature.newBuilder()
|
||||
.setType(Feature.Type.WEB_DETECTION)
|
||||
.setMaxResults(20)
|
||||
.build();
|
||||
|
||||
AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
|
||||
.addFeatures(feature)
|
||||
.setImage(image)
|
||||
.build();
|
||||
|
||||
BatchAnnotateImagesResponse response = visionClient.batchAnnotateImages(List.of(request));
|
||||
|
||||
if (response.getResponsesCount() == 0) {
|
||||
throw new IOException("Empty response from Google Vision API");
|
||||
}
|
||||
|
||||
AnnotateImageResponse singleResponse = response.getResponses(0);
|
||||
|
||||
if (singleResponse.hasError()) {
|
||||
throw new IOException("Google Vision API error: " + singleResponse.getError().getMessage());
|
||||
}
|
||||
|
||||
return convertToResponse(singleResponse.getWebDetection());
|
||||
}
|
||||
|
||||
private GoogleVisionSearchResponse convertToResponse(WebDetection webDetection) {
|
||||
GoogleVisionSearchResponse response = new GoogleVisionSearchResponse();
|
||||
|
||||
if (webDetection.getBestGuessLabelsCount() > 0) {
|
||||
response.setBestGuessLabels(
|
||||
webDetection.getBestGuessLabelsList().stream()
|
||||
.map(label -> {
|
||||
GoogleVisionSearchResponse.BestGuessLabel dtoLabel =
|
||||
new GoogleVisionSearchResponse.BestGuessLabel();
|
||||
dtoLabel.setLabel(label.getLabel());
|
||||
dtoLabel.setLanguageCode(label.getLanguageCode());
|
||||
return dtoLabel;
|
||||
})
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
if (webDetection.getFullMatchingImagesCount() > 0) {
|
||||
response.setFullMatchingImages(
|
||||
webDetection.getFullMatchingImagesList().stream()
|
||||
.map(img -> {
|
||||
GoogleVisionSearchResponse.ImageResult dtoImg =
|
||||
new GoogleVisionSearchResponse.ImageResult();
|
||||
dtoImg.setUrl(img.getUrl());
|
||||
dtoImg.setScore(img.getScore());
|
||||
return dtoImg;
|
||||
})
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
if (webDetection.getVisuallySimilarImagesCount() > 0) {
|
||||
response.setVisuallySimilarImages(
|
||||
webDetection.getVisuallySimilarImagesList().stream()
|
||||
.map(img -> {
|
||||
GoogleVisionSearchResponse.ImageResult dtoImg =
|
||||
new GoogleVisionSearchResponse.ImageResult();
|
||||
dtoImg.setUrl(img.getUrl());
|
||||
dtoImg.setScore(img.getScore());
|
||||
return dtoImg;
|
||||
})
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
if (webDetection.getPagesWithMatchingImagesCount() > 0) {
|
||||
response.setPagesWithMatchingImages(
|
||||
webDetection.getPagesWithMatchingImagesList().stream()
|
||||
.map(page -> {
|
||||
GoogleVisionSearchResponse.PageResult dtoPage =
|
||||
new GoogleVisionSearchResponse.PageResult();
|
||||
dtoPage.setUrl(page.getUrl());
|
||||
dtoPage.setPageTitle(page.getPageTitle());
|
||||
return dtoPage;
|
||||
})
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
if (webDetection.getPartialMatchingImagesCount() > 0) {
|
||||
response.setPartialMatchingImages(
|
||||
webDetection.getPartialMatchingImagesList().stream()
|
||||
.map(img -> {
|
||||
GoogleVisionSearchResponse.ImageResult dtoImg =
|
||||
new GoogleVisionSearchResponse.ImageResult();
|
||||
dtoImg.setUrl(img.getUrl());
|
||||
dtoImg.setScore(img.getScore());
|
||||
return dtoImg;
|
||||
})
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "ncp-1-483610",
|
||||
"private_key_id": "94ea762d9a56a3362155ab33f8959bfec5ae8b42",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCwjY9sYQfeqHwr\nqKgr8sw3Qfao/vzTa9wnmbZvrM6UJ3ukMkj08w50rP0udKxTRWnH6rpSQnD696GS\nY0/qrjEO1LeMdPJ+R0v8B+Z3PlpuQtfmxXALF4QbGQxeWg9NK7o4JM0kANkV80me\nxo6T7WnywQm1SfBIPZjwLBhkn7pfcdTDJFiZkH073yB6/tPsgd6TRQDmlvGsrGmq\nCf8E/DhxaFIHUpoUEGpxeuFPmvAicEWdURCtCXeiAxRQv+Gy0mP7uGGY6C3SeYfJ\n5PNLopgnN2ATCeIp1EjJnH28sAEvE0e/K/9z4ZThgh1mB1CaLkaQweq3YQWoiyGF\nfcoPpJ8LAgMBAAECggEABluSppx35BS9I/VW2P5NTFAbycok4JgpvWNTpoHajos9\ncJQ+/FMkWh9AnsOk0PrW7FQPkZbC6sawEs1wu0q6iYYvdedgNBvtKI5ARlsAdqgB\njlXdywh1wWQNhfhIRMcmVY89s+Yz6w5vwD/2Mm2COzOoXdbjyDYvo7ZyymzWEUnA\nwaxg7aF5cGC+iqf+l2Ym8XdY2ZavvyZ8RwgKfQGfcPuhMogg+H6au4yvXPBNxv0u\nxDCbOm2ezA4cLyKyFqi6S5KiGmk2LnCOoLrkIalmTQhnptFZofGs4ezuOvhZ6ZTi\nQVDayHzbBygmFzJn1wzw83IQ7QgMmWYoEjC+JcRlgQKBgQDg6U3QPxhdvX1dCzBj\nef6yUrIeJhiILvKnAzi6MUhaWP0mU6kcIIn8DS78ojML+leKh2R7JQFFrt87kHjK\npcaTC7wkgNCa6e3d+U95EIVUvPVrvQ5O4zIM7evTiPEeZhN9Dhuh0o3eeYxfIqZO\nsN0y8F/xPWdGW5CcdL7dcTOUkwKBgQDI9Qzr7poRaHM8jzS0M4Q4cVsYdC2P6KgI\nWD9Qbbl1rVi14YLt1FSd4w7U4YJ/kaWOrdtKklqAvM3oq5J0oEQekpixsxFvXdW+\ngJTzOoOq2lgCThBagwVKOFkAH/CwPkhFPwuVBgfVNiajbrcGF6i5VWzIHCXT+IDe\naSw64dqOqQKBgDp9EYpNTjXaeEaBCWVlLVIMZVunxotrwhiiotbwyAMOz05vRTQW\nVivg9c4nFCVSRf+1c/D+T5Vig5UG3hK9B6Xn0Fah1R3kJcKq+frey/2cYipRcO4c\n8UAhg0lwfFvOadUEnTT4/4HSlCmNZjhikDOWBS1ELZ5DY5j8V0JZFPPXAoGAXi0B\nLjw2dbwGbTYLk/ukljMBZvdjNtLolGiO22lghbaEIVCa5Ewij4+OLtO0LYabGL9/\nSnZF9ZkFwmlNjFxjMBSxfG2X2SIXflyR8V7Vv6btob7lyRUn0H2RsA5H5MB7bAA8\ntE0MNK5Y8zR6j19dEeXnwev3ClymQBT3xmx72WkCgYAEhx+piCTvD+2SFBweM3J4\ng6LefBXFWHSlftQF1Er5mI+5kr4i3M5d6zh8lKcLpygSS1q5/03CMUxEiLTZMLmG\nVX/+UgEWzdEe1Dr/7YPYcOSPiBpTRyPiq6KQC/KtPPWHouGyaPO1SAAZhlk97dQs\n9mosf4Re69Re8U6MxxGw9Q==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "image-search-service@ncp-1-483610.iam.gserviceaccount.com",
|
||||
"client_id": "105492884344006453429",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/image-search-service%40ncp-1-483610.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
Reference in New Issue
Block a user