diff --git a/build.gradle b/build.gradle index ba3b7ee7..46212e3a 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-api:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' + implementation 'commons-io:commons-io:2.6' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' @@ -72,10 +73,14 @@ dependencies { implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - // elasticsearch - implementation 'org.springframework.data:spring-data-elasticsearch:' + //AWS lambda + implementation 'com.amazonaws:aws-java-sdk-lambda:1.12.767' + + //elasticsearch + implementation 'org.springframework.data:spring-data-elasticsearch' + } tasks.named('test') { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/FaceVector.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/FaceVector.java index a5f8adb4..b05afbe3 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/FaceVector.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/FaceVector.java @@ -15,16 +15,9 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Document(indexName = "face_vectors") public class FaceVector { - @Id - private String id; - @Field(type = FieldType.Long) private String shareGroupId; - @Field(type = FieldType.Keyword) - private String keyValue; - @Field(type = FieldType.Date) + private String name; private String date; - @Field(type = FieldType.Dense_Vector) private List faceVector; } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/PhotoEs.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/PhotoEs.java index e4a6478d..3e3642bb 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/PhotoEs.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/PhotoEs.java @@ -13,20 +13,12 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Document(indexName = "photos_es") public class PhotoEs { - @Id - private String id; - @Field(type = FieldType.Long) + private Long rdsId; private Long shareGroupId; - @Field(type = FieldType.Keyword) private String url; - @Field(type = FieldType.Keyword) private String name; - @Field(type = FieldType.Date) private String createdAt; - @Field(type = FieldType.Long) private List faceTag; - @Field(type = FieldType.Long) private List downloadTag; } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/SampleFaceVector.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/SampleFaceVector.java index 51b35ca5..0f61323b 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/SampleFaceVector.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/document/SampleFaceVector.java @@ -15,12 +15,7 @@ @Builder @NoArgsConstructor @AllArgsConstructor -@Document(indexName = "sample_face_vectors") public class SampleFaceVector { - @Id - private String id; - @Field(type = FieldType.Long) private Long memberId; - @Field(type = FieldType.Dense_Vector) private List faceVector; } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/face_vectors.json b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/face_vectors.json index b36b48dc..ba1a9280 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/face_vectors.json +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/face_vectors.json @@ -14,7 +14,7 @@ "shareGroupId" : { "type" : "long" }, - "keyValue" : { + "name" : { "type" : "keyword" }, "createdAt" : { @@ -23,7 +23,7 @@ }, "faceVector" : { "type": "dense_vector", - "dims": 128, + "dims": 512, "index": true, "similarity" : "dot_product" } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/photos_es.json b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/photos_es.json index c1525e96..906f6dda 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/photos_es.json +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/photos_es.json @@ -1,4 +1,3 @@ - { "settings" : { "index" :{ @@ -12,6 +11,9 @@ "required" : true }, "properties" : { + "rdsId" : { + "type" : "long" + }, "shareGroupId" : { "type" : "long" }, diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/sample_photo_vectors.json b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/sample_photo_vectors.json index ceb7f512..a034dc13 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/sample_photo_vectors.json +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/index/sample_photo_vectors.json @@ -13,8 +13,8 @@ }, "faceVector" : { "type": "dense_vector", - "dims": 128, - "index": true, + "dims": 512, + "index": false, "similarity" : "dot_product" } } diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/FaceVectorRepository.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/FaceVectorRepository.java deleted file mode 100644 index e0a52c7d..00000000 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/FaceVectorRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.umc.naoman.domain.photo.elasticsearch.repository; - -import com.umc.naoman.domain.photo.elasticsearch.document.FaceVector; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface FaceVectorRepository extends ElasticsearchRepository { -} diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java index d78fa7dd..da92b187 100644 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java +++ b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsClientRepository.java @@ -6,6 +6,7 @@ import co.elastic.clients.elasticsearch.core.SearchResponse; import co.elastic.clients.elasticsearch.core.search.Hit; import com.umc.naoman.domain.photo.elasticsearch.document.PhotoEs; +import com.umc.naoman.domain.photo.entity.Photo; import com.umc.naoman.global.error.BusinessException; import com.umc.naoman.global.error.code.ElasticsearchErrorCode; import lombok.RequiredArgsConstructor; @@ -27,23 +28,24 @@ public class PhotoEsClientRepository { private final ElasticsearchClient elasticsearchClient; //사진 업로드 시 ES에 벌크로 업로드 - public void savePhotoBulk(List url, List nameList, Long shareGroupId) { - List photoEsList = new ArrayList<>(); - for(int i=0; i photoList) { + List photoEsList = photoList.stream() + .map(photo -> PhotoEs.builder() + .rdsId(photo.getId()) + .shareGroupId(photo.getShareGroup().getId()) + .faceTag(new ArrayList<>()) + .downloadTag(new ArrayList<>()) + .url(photo.getUrl()) + .name(photo.getName()) + .createdAt(esTimeFormat(photo.getCreatedAt())) + .build()) + .toList(); BulkRequest.Builder bulkBuilder = new BulkRequest.Builder(); for(PhotoEs photoEs :photoEsList){ bulkBuilder.operations(op ->op .index(idx -> idx .index("photos_es") - .routing(shareGroupId.toString()) + .routing(photoEs.getShareGroupId().toString()) .document(photoEs) ) ); @@ -84,7 +86,7 @@ public Page findPhotoEsByShareGroupId(Long shareGroupId, Pageable pagea } //특정 공유 그룹의 얼굴이 태그된 사진 검색 - public Page findPhotoEsByShareGroupIdAndFaceTag(Long shareGroupId,Long faceTag, Pageable pageable) throws IOException{ + public Page findPhotoEsByShareGroupIdAndFaceTag(Long shareGroupId,Long faceTag, Pageable pageable) { SearchResponse response = null; try{ response = elasticsearchClient.search(s->s diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsRepository.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsRepository.java deleted file mode 100644 index e081d3be..00000000 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/PhotoEsRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.umc.naoman.domain.photo.elasticsearch.repository; - -import com.umc.naoman.domain.photo.elasticsearch.document.PhotoEs; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface PhotoEsRepository extends ElasticsearchRepository { -} diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/SampleFaceVectorRepository.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/SampleFaceVectorRepository.java deleted file mode 100644 index 98a866d9..00000000 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/repository/SampleFaceVectorRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.umc.naoman.domain.photo.elasticsearch.repository; - -import com.umc.naoman.domain.photo.elasticsearch.document.SampleFaceVector; -import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface SampleFaceVectorRepository extends ElasticsearchRepository { -} diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java deleted file mode 100644 index 0e52701a..00000000 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsService.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.umc.naoman.domain.photo.elasticsearch.service; - -public interface PhotoEsService { -} diff --git a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsServiceImpl.java deleted file mode 100644 index 1b465064..00000000 --- a/src/main/java/com/umc/naoman/domain/photo/elasticsearch/service/PhotoEsServiceImpl.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.umc.naoman.domain.photo.elasticsearch.service; - -import com.umc.naoman.domain.photo.elasticsearch.repository.FaceVectorRepository; -import com.umc.naoman.domain.photo.elasticsearch.repository.PhotoEsRepository; -import com.umc.naoman.domain.photo.service.PhotoService; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class PhotoEsServiceImpl implements PhotoEsService { - -} diff --git a/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionService.java b/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionService.java new file mode 100644 index 00000000..0d66aaf3 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionService.java @@ -0,0 +1,9 @@ +package com.umc.naoman.domain.photo.service; + +import java.util.List; + +public interface FaceDetectionService { + + void detectFaceUploadPhoto(List photoNameList, Long shareGroupId); + void detectFaceJoinShareGroup(Long memberId, Long shareGroupId); +} diff --git a/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionServiceImpl.java b/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionServiceImpl.java new file mode 100644 index 00000000..70508e51 --- /dev/null +++ b/src/main/java/com/umc/naoman/domain/photo/service/FaceDetectionServiceImpl.java @@ -0,0 +1,81 @@ +package com.umc.naoman.domain.photo.service; +import com.amazonaws.services.lambda.AWSLambda; +import com.amazonaws.services.lambda.model.InvocationType; +import com.amazonaws.services.lambda.model.InvokeRequest; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.naoman.domain.shareGroup.service.ShareGroupService; +import com.umc.naoman.global.error.BusinessException; +import com.umc.naoman.global.error.code.AwsLambdaErrorCode; +import lombok.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class FaceDetectionServiceImpl implements FaceDetectionService { + @Value("${spring.lambda.function.detect_face_upload_photo}") + private String detectFaceUploadPhotoLambda; + @Value("${spring.lambda.function.detect_face_join_share_group}") + private String detectFaceJoinShareGroupLambda; + private final AWSLambda awsLambda; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final ShareGroupService shareGroupService; + + @Getter + @AllArgsConstructor + private class DetectFacePhotoPayload { + private List photoNameList; + private List memberIdList; + private Long shareGroupId; + } + + @Getter + @AllArgsConstructor + private class DetectFaceShareGroupPayload { + private Long memberId; + private Long shareGroupId; + } + + @Override + public void detectFaceUploadPhoto(List photoNameList, Long shareGroupId) { + List memberIdList = shareGroupService.findProfileListByShareGroupId(shareGroupId).stream() + .map(profile -> profile.getMember().getId()) + .collect(Collectors.toList()); + DetectFacePhotoPayload payLoad = new DetectFacePhotoPayload(photoNameList, memberIdList, shareGroupId); + String lambdaPayload = null; + + try { + lambdaPayload = objectMapper.writeValueAsString(payLoad); + } catch (JsonProcessingException e) { + throw new BusinessException(AwsLambdaErrorCode.AWS_JsonProcessing_Exception, e); + } + InvokeRequest invokeRequest = new InvokeRequest() + .withInvocationType(InvocationType.Event) //비동기 호출 + .withFunctionName(detectFaceUploadPhotoLambda) + .withPayload(lambdaPayload); + + awsLambda.invoke(invokeRequest); + } + + @Override + public void detectFaceJoinShareGroup(Long memberId, Long shareGroupId) { + DetectFaceShareGroupPayload payLoad = new DetectFaceShareGroupPayload(memberId, shareGroupId); + String lambdaPayload = null; + + try { + lambdaPayload = objectMapper.writeValueAsString(payLoad); + } catch (JsonProcessingException e) { + throw new BusinessException(AwsLambdaErrorCode.AWS_JsonProcessing_Exception, e); + } + InvokeRequest invokeRequest = new InvokeRequest() + .withInvocationType(InvocationType.Event) //비동기 호출 + .withFunctionName(detectFaceJoinShareGroupLambda) + .withPayload(lambdaPayload); + + awsLambda.invoke(invokeRequest); + } +} diff --git a/src/main/java/com/umc/naoman/global/config/AsyncConfig.java b/src/main/java/com/umc/naoman/global/config/AsyncConfig.java new file mode 100644 index 00000000..5f760c59 --- /dev/null +++ b/src/main/java/com/umc/naoman/global/config/AsyncConfig.java @@ -0,0 +1,25 @@ +package com.umc.naoman.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; + +@EnableAsync +@Configuration +public class AsyncConfig implements AsyncConfigurer { + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(20); + executor.setQueueCapacity(30); + executor.setKeepAliveSeconds(30); + executor.setThreadNamePrefix("async-"); + executor.initialize(); + return executor; + } +} \ No newline at end of file diff --git a/src/main/java/com/umc/naoman/global/config/S3Config.java b/src/main/java/com/umc/naoman/global/config/AwsConfig.java similarity index 74% rename from src/main/java/com/umc/naoman/global/config/S3Config.java rename to src/main/java/com/umc/naoman/global/config/AwsConfig.java index 87a51e2c..4c9969fc 100644 --- a/src/main/java/com/umc/naoman/global/config/S3Config.java +++ b/src/main/java/com/umc/naoman/global/config/AwsConfig.java @@ -2,6 +2,8 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.lambda.AWSLambda; +import com.amazonaws.services.lambda.AWSLambdaClientBuilder; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3ClientBuilder; import org.springframework.beans.factory.annotation.Value; @@ -10,7 +12,7 @@ import org.springframework.context.annotation.Primary; @Configuration -public class S3Config { +public class AwsConfig { @Value("${spring.cloud.aws.credentials.access-key}") private String accessKey; @@ -32,4 +34,12 @@ public AmazonS3 amazonS3() { .withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider())) .build(); } + + @Bean + public AWSLambda awsLambda() { + return AWSLambdaClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider())) + .build(); + } } diff --git a/src/main/java/com/umc/naoman/global/error/code/AwsLambdaErrorCode.java b/src/main/java/com/umc/naoman/global/error/code/AwsLambdaErrorCode.java new file mode 100644 index 00000000..3816f2be --- /dev/null +++ b/src/main/java/com/umc/naoman/global/error/code/AwsLambdaErrorCode.java @@ -0,0 +1,16 @@ +package com.umc.naoman.global.error.code; + +import com.umc.naoman.global.error.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum AwsLambdaErrorCode implements ErrorCode { + AWS_JsonProcessing_Exception(500, "EA000", "AWS Lambda JsonProcessingException 발생"), + ; + + private final int status; + private final String code; + private final String message; +}