Skip to content

Commit

Permalink
Merge pull request #55 from Na-o-man/feature/#25/elasticsearch-config…
Browse files Browse the repository at this point in the history
…-and-simple-crud

[FEAT] 엘라스틱서치 기본 설정 및 사진 저장, 조희 repository 구현
  • Loading branch information
bflykky authored Aug 4, 2024
2 parents bb56e5d + 14f47e0 commit a97b122
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ dependencies {
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.0")
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:'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.umc.naoman.domain.photo.elasticsearch.document;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.List;

@Getter
@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 date;
@Field(type = FieldType.Dense_Vector)
private List<Float> faceVector;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.umc.naoman.domain.photo.elasticsearch.document;

import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "photos_es")
public class PhotoEs {
@Id
private String id;
@Field(type = FieldType.Long)
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
@@ -0,0 +1,26 @@
package com.umc.naoman.domain.photo.elasticsearch.document;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.List;

@Getter
@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
@@ -0,0 +1,32 @@
{
"settings" : {
"index" :{
"number_of_shards" : 5,
"number_of_replicas" : 1
}
},
"mappings" : {
"dynamic": "false",
"_routing": {
"required" : true
},
"properties" : {
"shareGroupId" : {
"type" : "long"
},
"keyValue" : {
"type" : "keyword"
},
"createdAt" : {
"type" : "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"faceVector" : {
"type": "dense_vector",
"dims": 128,
"index": true,
"similarity" : "dot_product"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

{
"settings" : {
"index" :{
"number_of_shards" : 2,
"number_of_replicas" : 2
}
},
"mappings" : {
"dynamic": "false",
"_routing": {
"required" : true
},
"properties" : {
"shareGroupId" : {
"type" : "long"
},
"url" : {
"type" : "keyword"
},
"name" : {
"type" : "keyword"
},
"createdAt" : {
"type" : "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"faceTag" : {
"type" : "long"
},
"downloadTag" : {
"type" : "long"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"settings" : {
"index" :{
"number_of_shards" : 3,
"number_of_replicas" : 1
}
},
"mappings" : {
"dynamic": "false",
"properties" : {
"memberId" : {
"type" : "long"
},
"faceVector" : {
"type": "dense_vector",
"dims": 128,
"index": true,
"similarity" : "dot_product"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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<FaceVector,String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package com.umc.naoman.domain.photo.elasticsearch.repository;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.BulkResponse;
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.global.error.BusinessException;
import com.umc.naoman.global.error.code.ElasticsearchErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Repository
@RequiredArgsConstructor
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);
}
BulkRequest.Builder bulkBuilder = new BulkRequest.Builder();
for(PhotoEs photoEs :photoEsList){
bulkBuilder.operations(op ->op
.index(idx -> idx
.index("photos_es")
.routing(shareGroupId.toString())
.document(photoEs)
)
);
}
try {
BulkResponse result = elasticsearchClient.bulk(bulkBuilder.build());
} catch (IOException e) {
throw new BusinessException(ElasticsearchErrorCode.ELASTICSEARCH_IOEXCEPTION, e);
}
}

//특정 공유 그룹의 모든 사진 검색
public Page<PhotoEs> findPhotoEsByShareGroupId(Long shareGroupId, Pageable pageable) {
SearchResponse<PhotoEs> response = null;

pageable.getPageNumber();
try{
response = elasticsearchClient.search(s->s
.index("photos_es")
.routing(shareGroupId.toString())
.from(getFrom(pageable))
.size(pageable.getPageSize())
.sort(sort -> sort
.field(f -> f
.field("createdAt")))
.query(q->q
.term(t->t
.field("shareGroupId")
.value(shareGroupId)
)
),
PhotoEs.class
);
} catch (IOException e) {
throw new BusinessException(ElasticsearchErrorCode.ELASTICSEARCH_IOEXCEPTION, e);
}
return toPagePhotoEs(response.hits().hits(), pageable);
}

//특정 공유 그룹의 얼굴이 태그된 사진 검색
public Page<PhotoEs> findPhotoEsByShareGroupIdAndFaceTag(Long shareGroupId,Long faceTag, Pageable pageable) throws IOException{
SearchResponse<PhotoEs> response = null;
try{
response = elasticsearchClient.search(s->s
.index("photos_es")
.routing(shareGroupId.toString())
.from(getFrom(pageable))
.size(pageable.getPageSize())
.sort(sort -> sort
.field(f -> f
.field("createdAt")))
.query(q->q
.bool(b->b
.must(m->m
.term(t->t
.field("shareGroupId")
.value(shareGroupId)
)
)
.must(m->m
.term(t->t
.field("faceTag")
.value(faceTag)
)
)
)
),
PhotoEs.class
);
} catch (IOException e) {
throw new BusinessException(ElasticsearchErrorCode.ELASTICSEARCH_IOEXCEPTION, e);
}

return toPagePhotoEs(response.hits().hits(), pageable);
}

//특정 공유 그룹의 얼굴이 태그되지 않은 사진 검색
public Page<PhotoEs> findPhotoEsByShareGroupIdAndNotFaceTag(Long shareGroupId, Pageable pageable){
SearchResponse<PhotoEs> response = null;
try{
response = elasticsearchClient.search(s->s
.index("photos_es")
.routing(shareGroupId.toString())
.from(getFrom(pageable))
.size(pageable.getPageSize())
.sort(sort -> sort
.field(f -> f
.field("createdAt")))
.query(q->q
.bool(b -> b
.must(m -> m
.term(t -> t
.field("shareGroupId")
.value(shareGroupId)
)
)
.mustNot(mn -> mn
.exists(e -> e
.field("faceTag")
)
)
)
),
PhotoEs.class
);
}catch (IOException e){
throw new BusinessException(ElasticsearchErrorCode.ELASTICSEARCH_IOEXCEPTION, e);
}

return toPagePhotoEs(response.hits().hits(), pageable);
}

String esTimeFormat(LocalDateTime localDateTime){
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return localDateTime.format(dateTimeFormatter);
}

private Page<PhotoEs> toPagePhotoEs(List<Hit<PhotoEs>> hits, Pageable pageable){
List<PhotoEs> photoEsList = hits.stream().map(Hit::source).collect(Collectors.toList());
return new PageImpl<>(photoEsList, pageable, hits.size());
}

private int getFrom(Pageable pageable){
return pageable.getPageNumber() * pageable.getPageSize();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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<PhotoEs, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
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<SampleFaceVector,String> {
}
Loading

0 comments on commit a97b122

Please sign in to comment.