Skip to content

Commit

Permalink
Merge pull request #70 from Na-o-man/feature/#56/call_faceDetection_l…
Browse files Browse the repository at this point in the history
…ambda

[FEAT] 사진 업로드 때, 새로운 맴버가 그룹에 들어갈 때, 얼굴 인식 로직
  • Loading branch information
bflykky authored Aug 10, 2024
2 parents 05f1c51 + 9fbc4a1 commit 73a63c2
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 88 deletions.
11 changes: 8 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Float> faceVector;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long> faceTag;
@Field(type = FieldType.Long)
private List<Long> downloadTag;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Float> faceVector;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"shareGroupId" : {
"type" : "long"
},
"keyValue" : {
"name" : {
"type" : "keyword"
},
"createdAt" : {
Expand All @@ -23,7 +23,7 @@
},
"faceVector" : {
"type": "dense_vector",
"dims": 128,
"dims": 512,
"index": true,
"similarity" : "dot_product"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

{
"settings" : {
"index" :{
Expand All @@ -12,6 +11,9 @@
"required" : true
},
"properties" : {
"rdsId" : {
"type" : "long"
},
"shareGroupId" : {
"type" : "long"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
},
"faceVector" : {
"type": "dense_vector",
"dims": 128,
"index": true,
"dims": 512,
"index": false,
"similarity" : "dot_product"
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,23 +28,24 @@ public class PhotoEsClientRepository {
private final ElasticsearchClient elasticsearchClient;

//사진 업로드 시 ES에 벌크로 업로드
public void savePhotoBulk(List<String> url, List<String> nameList, Long shareGroupId) {
List<PhotoEs> photoEsList = new ArrayList<>();
for(int i=0; i<url.size(); i++){
PhotoEs photoEs = PhotoEs.builder()
.shareGroupId(shareGroupId)
.url(url.get(i))
.name(nameList.get(i))
.createdAt(esTimeFormat(LocalDateTime.now()))
.build();
photoEsList.add(photoEs);
}
public void savePhotoBulk(List<Photo> photoList) {
List<PhotoEs> 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)
)
);
Expand Down Expand Up @@ -84,7 +86,7 @@ public Page<PhotoEs> findPhotoEsByShareGroupId(Long shareGroupId, Pageable pagea
}

//특정 공유 그룹의 얼굴이 태그된 사진 검색
public Page<PhotoEs> findPhotoEsByShareGroupIdAndFaceTag(Long shareGroupId,Long faceTag, Pageable pageable) throws IOException{
public Page<PhotoEs> findPhotoEsByShareGroupIdAndFaceTag(Long shareGroupId,Long faceTag, Pageable pageable) {
SearchResponse<PhotoEs> response = null;
try{
response = elasticsearchClient.search(s->s
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.umc.naoman.domain.photo.service;

import java.util.List;

public interface FaceDetectionService {

void detectFaceUploadPhoto(List<String> photoNameList, Long shareGroupId);
void detectFaceJoinShareGroup(Long memberId, Long shareGroupId);
}
Original file line number Diff line number Diff line change
@@ -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<String> photoNameList;
private List<Long> memberIdList;
private Long shareGroupId;
}

@Getter
@AllArgsConstructor
private class DetectFaceShareGroupPayload {
private Long memberId;
private Long shareGroupId;
}

@Override
public void detectFaceUploadPhoto(List<String> photoNameList, Long shareGroupId) {
List<Long> 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);
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/umc/naoman/global/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 73a63c2

Please sign in to comment.