Skip to content

Commit cd01cfd

Browse files
authored
Merge pull request #15 from BHC-Chicken/refactor/like-filter
refactor : elasticsearch like filter 수정
2 parents e6e9e8f + 8781f7d commit cd01cfd

12 files changed

Lines changed: 375 additions & 251 deletions
Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,81 @@
11
package inu.codin.codin.domain.elasticsearch.convertor;
22

33
import inu.codin.codin.domain.elasticsearch.document.LectureDocument;
4+
import inu.codin.codin.domain.elasticsearch.document.dto.EmotionInfo;
5+
import inu.codin.codin.domain.elasticsearch.document.dto.ScheduleInfo;
6+
import inu.codin.codin.domain.elasticsearch.document.dto.SemesterInfo;
47
import inu.codin.codin.domain.lecture.entity.Lecture;
8+
import inu.codin.codin.domain.lecture.entity.LectureSchedule;
9+
import inu.codin.codin.domain.lecture.entity.LectureSemester;
10+
import inu.codin.codin.domain.lecture.entity.LectureTag;
511
import org.springframework.stereotype.Component;
612

713
import java.util.List;
14+
import java.util.Set;
815

916
@Component
1017
public class LectureDocumentConverter {
1118

1219
public LectureDocument convertToDocument(Lecture lecture) {
20+
1321
return LectureDocument.builder()
1422
.id(lecture.getId())
1523
.lectureNm(lecture.getLectureNm())
1624
.grade(lecture.getGrade())
17-
.department(lecture.getDepartment() != null ? lecture.getDepartment().name() : null)
18-
.starRating(lecture.getStarRating())
19-
.likes((long) lecture.getLikes())
20-
.hits((long) lecture.getHits())
25+
.credit(lecture.getCredit())
2126
.professor(lecture.getProfessor())
27+
.department(lecture.getDepartment() != null ? lecture.getDepartment().name() : null)
2228
.type(lecture.getType() != null ? lecture.getType().name() : null)
2329
.lectureType(lecture.getLectureType())
2430
.evaluation(lecture.getEvaluation() != null ? lecture.getEvaluation().name() : null)
25-
.preCourses(lecture.getPreCourse() != null ? List.of(lecture.getPreCourse()) : List.of())
26-
.tags(lecture.getTags() != null ? lecture.getTags().stream()
27-
.map(tag -> tag.getTag().getTagName())
28-
.toList() : List.of())
29-
.semesters(lecture.getSemester() != null ? lecture.getSemester().stream()
30-
.map(ls -> ls.getSemester().getString())
31-
.toList() : List.of())
31+
.preCourses(toPreCourses(lecture.getPreCourse()))
32+
.starRating(lecture.getStarRating())
33+
.likes(lecture.getLikes())
34+
.hits(lecture.getHits())
35+
.semesters(toSemesterInfos(lecture.getSemester()))
36+
.tags(toTagNames(lecture.getTags()))
37+
.schedule(toScheduleInfos(lecture.getSchedule()))
38+
.emotion(EmotionInfo.from(lecture.getEmotion()))
39+
.syllabus(lecture.getSyllabus())
40+
.aiSummary(lecture.getAiSummary())
3241
.build();
3342
}
43+
44+
private List<String> toPreCourses(String preCourse) {
45+
if (preCourse == null || preCourse.isBlank()) {
46+
return List.of();
47+
}
48+
49+
return List.of(preCourse.split(",\\s*")); // 쉼표 기준으로 분리하는 예시
50+
}
51+
52+
private List<SemesterInfo> toSemesterInfos(Set<LectureSemester> semesters) {
53+
if (semesters == null) {
54+
return List.of();
55+
}
56+
57+
return semesters.stream()
58+
.map(SemesterInfo::from)
59+
.toList();
60+
}
61+
62+
private List<String> toTagNames(Set<LectureTag> tags) {
63+
if (tags == null) {
64+
return List.of();
65+
}
66+
67+
return tags.stream()
68+
.map(lectureTag -> lectureTag.getTag().getTagName())
69+
.toList();
70+
}
71+
72+
private List<ScheduleInfo> toScheduleInfos(Set<LectureSchedule> schedules) {
73+
if (schedules == null) {
74+
return List.of();
75+
}
76+
77+
return schedules.stream()
78+
.map(ScheduleInfo::from)
79+
.toList();
80+
}
3481
}
Lines changed: 47 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package inu.codin.codin.domain.elasticsearch.document;
22

3+
import inu.codin.codin.domain.elasticsearch.document.dto.EmotionInfo;
4+
import inu.codin.codin.domain.elasticsearch.document.dto.ScheduleInfo;
5+
import inu.codin.codin.domain.elasticsearch.document.dto.SemesterInfo;
36
import lombok.AccessLevel;
47
import lombok.AllArgsConstructor;
58
import lombok.Builder;
69
import lombok.Getter;
10+
711
import org.springframework.data.annotation.Id;
8-
import org.springframework.data.elasticsearch.annotations.Document;
9-
import org.springframework.data.elasticsearch.annotations.Field;
10-
import org.springframework.data.elasticsearch.annotations.FieldType;
12+
import org.springframework.data.elasticsearch.annotations.*;
1113

1214
import java.util.List;
1315

@@ -17,8 +19,6 @@
1719
@AllArgsConstructor(access = AccessLevel.PROTECTED)
1820
public class LectureDocument {
1921

20-
// todo: 강의계획서, 강의계획서 AI 요약본 ... 필드가 필요함.
21-
2222
@Id
2323
private Long id;
2424

@@ -28,21 +28,15 @@ public class LectureDocument {
2828
@Field(type = FieldType.Integer)
2929
private Integer grade;
3030

31-
@Field(type = FieldType.Keyword)
32-
private String department;
33-
34-
@Field(type = FieldType.Double)
35-
private Double starRating;
36-
37-
@Field(type = FieldType.Long)
38-
private Long likes;
39-
40-
@Field(type = FieldType.Long)
41-
private Long hits;
31+
@Field(type = FieldType.Integer)
32+
private Integer credit;
4233

4334
@Field(type = FieldType.Keyword)
4435
private String professor;
4536

37+
@Field(type = FieldType.Keyword)
38+
private String department;
39+
4640
@Field(type = FieldType.Keyword)
4741
private String type;
4842

@@ -55,29 +49,46 @@ public class LectureDocument {
5549
@Field(type = FieldType.Text, analyzer = "nori")
5650
private List<String> preCourses;
5751

52+
@Field(type = FieldType.Double)
53+
private Double starRating;
54+
55+
@Field(type = FieldType.Integer)
56+
private Integer likes;
57+
58+
@Field(type = FieldType.Integer)
59+
private Integer hits;
60+
61+
@Field(type = FieldType.Nested)
62+
private List<SemesterInfo> semesters;
63+
5864
@Field(type = FieldType.Keyword)
5965
private List<String> tags;
6066

61-
@Field(type = FieldType.Keyword)
62-
private List<String> semesters;
67+
@Field(type = FieldType.Nested)
68+
private List<ScheduleInfo> schedule;
69+
70+
@Field(type = FieldType.Object)
71+
private EmotionInfo emotion;
6372

6473
// 강의계획서 전체
65-
// @Field(type = FieldType.Text,
66-
// analyzer = "nori",
67-
// searchAnalyzer = "nori",
68-
// indexOptions = IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS,
69-
// termVector = TermVector.WITH_POSITIONS_OFFSETS,
70-
// fields = {
71-
// @InnerField(suffix = "keyword", type = FieldType.Keyword)
72-
// })
73-
// private String syllabus; // 강의계획서 전체
74-
75-
// AI 요약본
76-
// @Field(type = FieldType.Text,
77-
// analyzer = "standard",
78-
// searchAnalyzer = "standard",
79-
// indexOptions = IndexOptions.POSITIONS,
80-
// termVector = TermVector.NO,
81-
// store = true) // 자주 조회되는 요약본만 store
82-
// private String aiSummary;
74+
@MultiField(
75+
mainField = @Field(type = FieldType.Text,
76+
analyzer = "nori",
77+
searchAnalyzer = "nori",
78+
indexOptions = IndexOptions.offsets,
79+
termVector = TermVector.with_positions_offsets),
80+
otherFields = {
81+
@InnerField(suffix = "keyword", type = FieldType.Keyword)
82+
}
83+
)
84+
private String syllabus;
85+
86+
// AI 요약본
87+
@Field(type = FieldType.Text,
88+
analyzer = "standard",
89+
searchAnalyzer = "standard",
90+
indexOptions = IndexOptions.positions,
91+
termVector = TermVector.no,
92+
store = true)
93+
private String aiSummary;
8394
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package inu.codin.codin.domain.elasticsearch.document.dto;
2+
3+
import inu.codin.codin.domain.lecture.entity.Emotion;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import org.springframework.data.elasticsearch.annotations.Field;
7+
import org.springframework.data.elasticsearch.annotations.FieldType;
8+
9+
@Getter
10+
@Builder
11+
public class EmotionInfo {
12+
@Field(type = FieldType.Integer)
13+
private int hard;
14+
15+
@Field(type = FieldType.Integer)
16+
private int ok;
17+
18+
@Field(type = FieldType.Integer)
19+
private int best;
20+
21+
public static EmotionInfo from(Emotion emotion) {
22+
if (emotion == null) {
23+
return null;
24+
}
25+
26+
return EmotionInfo.builder()
27+
.hard(emotion.getHard())
28+
.ok(emotion.getOk())
29+
.best(emotion.getBest())
30+
.build();
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package inu.codin.codin.domain.elasticsearch.document.dto;
2+
3+
import inu.codin.codin.domain.lecture.entity.LectureSchedule;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import org.springframework.data.elasticsearch.annotations.Field;
7+
import org.springframework.data.elasticsearch.annotations.FieldType;
8+
9+
@Getter
10+
@Builder
11+
public class ScheduleInfo {
12+
@Field(type = FieldType.Keyword)
13+
private String day;
14+
15+
@Field(type = FieldType.Keyword)
16+
private String startTime;
17+
18+
@Field(type = FieldType.Keyword)
19+
private String endTime;
20+
21+
@Field(type = FieldType.Keyword)
22+
private String roomInfo;
23+
24+
public static ScheduleInfo from(LectureSchedule schedule) {
25+
26+
return ScheduleInfo.builder()
27+
.day(schedule.getDayOfWeek().name())
28+
.startTime(schedule.getStart())
29+
.endTime(schedule.getEnd())
30+
.roomInfo(schedule.getRoom() != null ? String.valueOf(schedule.getRoom().getRoomNum()) : "미정")
31+
.build();
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package inu.codin.codin.domain.elasticsearch.document.dto;
2+
3+
import inu.codin.codin.domain.lecture.entity.LectureSemester;
4+
import inu.codin.codin.domain.lecture.entity.Semester;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import org.springframework.data.elasticsearch.annotations.Field;
8+
import org.springframework.data.elasticsearch.annotations.FieldType;
9+
10+
@Getter
11+
@Builder
12+
public class SemesterInfo {
13+
@Field(type = FieldType.Integer)
14+
private Integer year;
15+
16+
@Field(type = FieldType.Integer)
17+
private Integer quarter;
18+
19+
public static SemesterInfo from(LectureSemester lectureSemester) {
20+
Semester semester = lectureSemester.getSemester();
21+
22+
return SemesterInfo.builder()
23+
.year(semester.getYear())
24+
.quarter(semester.getQuarter())
25+
.build();
26+
}
27+
}

src/main/java/inu/codin/codin/domain/elasticsearch/indexer/LectureStartupIndexer.java

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88
import lombok.RequiredArgsConstructor;
99
import lombok.extern.slf4j.Slf4j;
1010
import org.springframework.beans.factory.annotation.Value;
11-
import org.springframework.boot.context.event.ApplicationReadyEvent;
12-
import org.springframework.context.event.EventListener;
13-
import org.springframework.data.domain.Page;
1411
import org.springframework.data.domain.PageRequest;
1512
import org.springframework.data.domain.Pageable;
1613
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
1714
import org.springframework.data.elasticsearch.core.IndexOperations;
1815
import org.springframework.stereotype.Component;
19-
import org.springframework.transaction.annotation.Transactional;
2016

2117
import java.util.List;
2218

@@ -35,35 +31,37 @@ public class LectureStartupIndexer {
3531
@Value("${elasticsearch.indexer.enabled:true}")
3632
private boolean indexerEnabled;
3733

38-
@EventListener(ApplicationReadyEvent.class)
39-
@Transactional(readOnly = true)
40-
public void onApplicationEvent(ApplicationReadyEvent event) {
41-
// 환경 변수로 설정 적용
34+
public void lectureIndex() {
4235
if (!indexerEnabled) {
4336
log.info("ElasticSearch indexer 사용할 수 없습니다.");
37+
4438
return;
4539
}
4640

47-
// 인덱스 존재 여부 확인 및 생성
41+
manageIndex();
42+
long totalProcessed = performIndexing();
43+
44+
log.info("ElasticSearch 인덱싱 성공. Total Indexed: {}", totalProcessed);
45+
}
46+
47+
private void manageIndex() {
4848
IndexOperations indexOps = elasticsearchOperations.indexOps(LectureDocument.class);
49-
if (!indexOps.exists()) {
50-
log.info("ElasticSearch lectures 인덱스가 존재하지 않아 직접 생성합니다.");
51-
indexOps.create();
52-
indexOps.putMapping(indexOps.createMapping(LectureDocument.class));
53-
log.info("ElasticSearch lectures 인덱스 및 매핑 생성 완료.");
49+
if (indexOps.exists()) {
50+
log.info("ElasticSearch lectures 문서 전체 재색인을 위해 인덱스를 삭제합니다.");
51+
indexOps.delete();
5452
}
5553

56-
long existingCount = lectureElasticRepository.count();
57-
if (existingCount > 0) {
58-
log.info("ElasticSearch 인덱스가 이미 존재합니다 ({}개), 전체 재인덱싱을 진행합니다.", existingCount);
59-
// 선택적으로 기존 인덱스 삭제 후 재인덱싱
60-
lectureElasticRepository.deleteAll();
61-
log.info("기존 인덱스를 삭제했습니다.");
62-
}
54+
log.info("ElasticSearch lectures 인덱스를 생성합니다.");
55+
indexOps.create();
56+
indexOps.putMapping(indexOps.createMapping(LectureDocument.class));
57+
log.info("ElasticSearch lectures 인덱스 및 매핑 생성 완료.");
58+
}
59+
60+
private long performIndexing() {
61+
log.info("Lecture 문서 색인을 시작합니다.");
6362

64-
log.info("Starting ElasticSearch Indexing, Lecture Data");
6563
int pageNumber = 0;
66-
int totalProcessed = 0;
64+
long totalProcessed = 0L;
6765
List<Lecture> lectures;
6866

6967
do {
@@ -84,6 +82,8 @@ public void onApplicationEvent(ApplicationReadyEvent event) {
8482
pageNumber++;
8583
} while (lectures.size() == CHUNK_SIZE);
8684

87-
log.info("ElasticSearch 인덱싱 성공. Total Indexed: {}", totalProcessed);
85+
elasticsearchOperations.indexOps(LectureDocument.class).refresh();
86+
87+
return totalProcessed;
8888
}
8989
}

0 commit comments

Comments
 (0)