Skip to content

Commit 7fa270e

Browse files
authored
[BACKEND] 용어사전 자동검색 및 상세 검색 API 구현, ELasticsearch 연동 (#75)
## 📝작업 내용 - Elasticsearch, Kibana 컨테이너 환경 구성 - 용어사전 자동검색 및 상세검색 구현 - GET /glossary/autocomplete?query={query}&size={size}: 사용자가 입력 중인 검색어 기반으로 연관된 용어 후보 반환 - query와 의미상으로 부분 일치하는 용어 반환 및 prefix match - 대소문자 구분X - 동의어 검색 가능 - GET /glossary/detail?query={query}: 입력한 용어와 완전히 일치하는 용어의 이름과 설명 반환 - spring-data-elasticsearch 의존성 추가
1 parent 03f4f15 commit 7fa270e

File tree

13 files changed

+243
-2
lines changed

13 files changed

+243
-2
lines changed

backend/Dockerfile.elastic

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM docker.elastic.co/elasticsearch/elasticsearch:8.17.4
2+
3+
# Nori Analyzer 플러그인 설치
4+
RUN bin/elasticsearch-plugin install analysis-nori

backend/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ dependencies {
4444

4545
implementation 'org.springframework.boot:spring-boot-starter-cache'
4646
implementation 'com.github.ben-manes.caffeine:caffeine'
47+
48+
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
4749
}
4850

4951
tasks.named('test') {

backend/docker-compose.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,23 @@ services:
66
ports:
77
- "8080:8080"
88
env_file: ".env" #EC2에 올려둔 .env 사용
9+
10+
elastic:
11+
build:
12+
context: .
13+
dockerfile: Dockerfile.elastic
14+
container_name: elasticsearch
15+
ports:
16+
- "9200:9200"
17+
environment:
18+
- discovery.type=single-node
19+
- xpack.security.enabled=false
20+
- xpack.security.http.ssl.enabled=false
21+
22+
kibana:
23+
image: docker.elastic.co/kibana/kibana:8.17.4
24+
container_name: kibana
25+
ports:
26+
- "5601:5601"
27+
environment:
28+
- ELASTICSEARCH_HOSTS=http://elastic:9200

backend/src/main/java/com/cmg/comtogether/common/exception/ErrorCode.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ public enum ErrorCode {
2929
NAVER_API_ERROR(502, "NAVER-999", "네이버 서버와 통신 중 오류가 발생했습니다."),
3030

3131
// 가이드
32-
GUIDE_NOT_FOUND(404, "GUIDE-001", "가이드를 찾을 수 없습니다");
32+
GUIDE_NOT_FOUND(404, "GUIDE-001", "가이드를 찾을 수 없습니다"),
33+
34+
// 용어사전
35+
WORD_NOT_FOUND(404, "GLOSSARY-001", "해당 용어를 찾을 수 없습니다.");
3336

3437
private final int status;
3538
private final String code;

backend/src/main/java/com/cmg/comtogether/common/security/config/SecurityConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, CorsConfigurat
4141
"/v3/api-docs/**",
4242
"/products/**",
4343
"/guide/**",
44-
"/users/all"
44+
"/users/all",
45+
"/glossary/**"
4546
).permitAll()
4647
.anyRequest().authenticated()
4748
)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.cmg.comtogether.glossary.controller;
2+
3+
import com.cmg.comtogether.common.response.ApiResponse;
4+
import com.cmg.comtogether.glossary.dto.GlossaryAutoCompleteResponseDto;
5+
import com.cmg.comtogether.glossary.dto.GlossaryDetailResponseDto;
6+
import com.cmg.comtogether.glossary.service.GlossaryService;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
import org.springframework.web.bind.annotation.RequestParam;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
@RestController
15+
@RequestMapping("/glossary")
16+
@RequiredArgsConstructor
17+
public class GlossaryController {
18+
19+
private final GlossaryService glossaryService;
20+
21+
@GetMapping("/autocomplete")
22+
public ResponseEntity<ApiResponse<GlossaryAutoCompleteResponseDto>> autoComplete(
23+
@RequestParam String query,
24+
@RequestParam(defaultValue = "5") int size
25+
) {
26+
GlossaryAutoCompleteResponseDto autoComplete = glossaryService.getAutoComplete(query, size);
27+
return ResponseEntity.ok(ApiResponse.success(autoComplete));
28+
}
29+
30+
@GetMapping("/detail")
31+
public ResponseEntity<ApiResponse<GlossaryDetailResponseDto>> getDetail(
32+
@RequestParam String query
33+
) {
34+
GlossaryDetailResponseDto detail = glossaryService.getDetail(query);
35+
return ResponseEntity.ok(ApiResponse.success(detail));
36+
}
37+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.cmg.comtogether.glossary.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
import java.util.List;
8+
9+
@Getter
10+
@Builder
11+
@AllArgsConstructor
12+
public class GlossaryAutoCompleteResponseDto {
13+
List<String> suggestions;
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.cmg.comtogether.glossary.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
@AllArgsConstructor
10+
public class GlossaryDetailResponseDto {
11+
private String name;
12+
private String description;
13+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.cmg.comtogether.glossary.entity;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import org.springframework.data.annotation.Id;
8+
import org.springframework.data.elasticsearch.annotations.*;
9+
10+
@Getter
11+
@NoArgsConstructor
12+
@AllArgsConstructor
13+
@Builder
14+
@Document(indexName = "glossary")
15+
@Setting(settingPath = "/elasticsearch/glossary-settings.json")
16+
public class GlossaryDocument {
17+
18+
@Id
19+
private String id;
20+
21+
@MultiField(
22+
mainField = @Field(type = FieldType.Text, analyzer = "glossary_name_analyzer"),
23+
otherFields = {
24+
@InnerField(suffix = "auto_complete", type = FieldType.Search_As_You_Type),
25+
@InnerField(suffix = "raw", type = FieldType.Keyword)
26+
}
27+
)
28+
private String name;
29+
30+
@Field(type = FieldType.Keyword)
31+
private String description;
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.cmg.comtogether.glossary.repository;
2+
3+
import com.cmg.comtogether.glossary.entity.GlossaryDocument;
4+
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface GlossaryRepository extends ElasticsearchRepository<GlossaryDocument, String> {
9+
}

0 commit comments

Comments
 (0)