1010import org .springframework .stereotype .Service ;
1111import org .springframework .web .client .HttpClientErrorException ;
1212import org .springframework .web .client .RestTemplate ;
13+ import org .springframework .web .reactive .function .client .WebClient ;
14+ import org .springframework .web .util .UriComponentsBuilder ;
15+
16+ import java .util .ArrayList ;
17+ import java .util .List ;
1318
1419@ Service
1520@ Slf4j
1621@ AllArgsConstructor
1722public class LawWordService {
1823
1924 private final LawWordRepository lawWordRepository ;
20- private final RestTemplate restTemplate = new RestTemplate ();
25+ private final WebClient webClient = WebClient . builder (). build ();
2126 private final ObjectMapper objectMapper = new ObjectMapper ();
2227
2328 private static final String API_BASE_URL = "https://www.law.go.kr/DRF/lawService.do" ;
2429 private static final String API_OC = "noheechul" ;
2530
31+ // 우리말샘 API 설정
32+ private static final String KOREAN_DICT_API_BASE_URL = "https://opendict.korean.go.kr/api/search" ;
33+ private static final String API_KEY = "2A4D1A844C8BAB682B38E5F192D3D42A" ;
34+
2635 public String findDefinition (String word ) {
2736 // 1) DB에서 먼저 조회
2837 return lawWordRepository .findByWord (word )
2938 .map (LawWord ::getDefinition )
3039 .orElseGet (() -> fetchAndSaveDefinition (word ));
3140 }
3241
42+ public String findDefinitionV2 (String word ) {
43+ // 1) DB에서 먼저 조회
44+ return lawWordRepository .findByWord (word )
45+ .map (LawWord ::getDefinition )
46+ .orElseGet (() -> fetchAndSaveDefinitionV2 (word ));
47+ }
48+
3349 private String fetchAndSaveDefinition (String word ) {
3450 try {
3551 String url = buildApiUrl (word );
36- String json = restTemplate .getForObject (url , String .class );
52+ // WebClient 호출 (동기 방식)
53+ String json = webClient .get ()
54+ .uri (url )
55+ .retrieve ()
56+ .bodyToMono (String .class )
57+ .block ();
3758
3859 String definition = extractDefinitionFromJson (json );
3960 saveDefinition (word , definition );
@@ -52,10 +73,54 @@ private String fetchAndSaveDefinition(String word) {
5273 }
5374 }
5475
76+ private String fetchAndSaveDefinitionV2 (String word ) {
77+ try {
78+ String url = buildKoreanDictApiUrl (word );
79+
80+ // WebClient 호출 (동기 방식)
81+ String json = webClient .get ()
82+ .uri (url )
83+ .retrieve ()
84+ .bodyToMono (String .class )
85+ .block ();
86+
87+ String combinedDefinitions = extractTop3DefinitionsFromJson (json );
88+ saveDefinition (word , combinedDefinitions );
89+
90+ return combinedDefinitions ;
91+
92+ } catch (HttpClientErrorException e ) {
93+ log .error ("한국어사전 API 호출 중 클라이언트 오류 발생: {}" , e .getMessage ());
94+ throw new RuntimeException ("한국어사전 API 호출 중 오류가 발생했습니다." );
95+ } catch (JsonProcessingException e ) {
96+ log .error ("JSON 파싱 중 오류 발생: {}" , e .getMessage ());
97+ throw new RuntimeException ("한국어사전 API 응답 처리 중 파싱 오류가 발생했습니다." );
98+ } catch (Exception e ) {
99+ log .error ("정의 조회 실패: " , e );
100+ throw new RuntimeException ("한국어사전 정의 조회 중 알 수 없는 오류가 발생했습니다." );
101+ }
102+ }
103+
55104 private String buildApiUrl (String word ) {
56105 return API_BASE_URL + "?OC=" + API_OC + "&target=lstrm&type=JSON&query=" + word ;
57106 }
58107
108+ private String buildKoreanDictApiUrl (String word ) {
109+ return UriComponentsBuilder .fromHttpUrl (KOREAN_DICT_API_BASE_URL )
110+ .queryParam ("key" , API_KEY )
111+ .queryParam ("req_type" , "json" )
112+ .queryParam ("part" , "word" )
113+ .queryParam ("q" , word )
114+ .queryParam ("sort" , "dict" )
115+ .queryParam ("start" , "1" )
116+ .queryParam ("num" , "10" )
117+ .queryParam ("advanced" , "y" )
118+ .queryParam ("type4" , "all" )
119+ .queryParam ("cat" , "23" )
120+ .build ()
121+ .toUriString ();
122+ }
123+
59124 private String extractDefinitionFromJson (String json ) throws JsonProcessingException {
60125 JsonNode rootNode = objectMapper .readTree (json );
61126 if (rootNode .has ("Law" )) {
@@ -71,6 +136,41 @@ private String extractDefinitionFromJson(String json) throws JsonProcessingExcep
71136 }
72137 }
73138
139+ private String extractTop3DefinitionsFromJson (String json ) throws JsonProcessingException {
140+ JsonNode rootNode = objectMapper .readTree (json );
141+
142+ // channel > item 배열에서 아이템들 추출
143+ JsonNode itemsNode = rootNode .path ("channel" ).path ("item" );
144+
145+ if (!itemsNode .isArray () || itemsNode .size () == 0 ) {
146+ throw new RuntimeException ("검색 결과가 없습니다." );
147+ }
148+
149+ List <String > definitions = new ArrayList <>();
150+
151+ // 최대 3개의 definition 추출
152+ for (int i = 0 ; i < Math .min (itemsNode .size (), 3 ); i ++) {
153+ JsonNode item = itemsNode .get (i );
154+ JsonNode senseNode = item .path ("sense" );
155+
156+ if (senseNode .isArray () && senseNode .size () > 0 ) {
157+ JsonNode firstSense = senseNode .get (0 );
158+ String definition = firstSense .path ("definition" ).asText ();
159+
160+ if (definition != null && !definition .trim ().isEmpty ()) {
161+ definitions .add (definition .trim ());
162+ }
163+ }
164+ }
165+
166+ if (definitions .isEmpty ()) {
167+ throw new RuntimeException ("검색 결과에서 정의를 찾을 수 없습니다." );
168+ }
169+
170+ // 줄바꿈으로 연결하여 하나의 문자열로 만들기
171+ return String .join ("\n " , definitions );
172+ }
173+
74174 private void saveDefinition (String word , String definition ) {
75175 LawWord entity = LawWord .builder ()
76176 .word (word )
0 commit comments