diff --git a/backend/src/main/java/com/ai/lawyer/domain/law/service/LawService.java b/backend/src/main/java/com/ai/lawyer/domain/law/service/LawService.java index 9591bb4d..e3de4f5e 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/law/service/LawService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/law/service/LawService.java @@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; @@ -36,8 +37,8 @@ public class LawService { private final JoRepository joRepository; private final HangRepository hangRepository; private final HoRepository hoRepository; - private final RestTemplate restTemplate = new RestTemplate(); private final ObjectMapper objectMapper = new ObjectMapper(); + private final WebClient webClient = WebClient.builder().build(); // 상수 정의 private static final String BASE_URL = "http://www.law.go.kr/DRF"; @@ -149,7 +150,11 @@ private String getLawSearchResponse(String query) { .build() .toUriString(); - return restTemplate.getForObject(url, String.class); + return webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); } /** @@ -167,7 +172,11 @@ private String getLawDetailResponse(String lawId) { .build() .toUriString(); - return restTemplate.getForObject(url, String.class); + return webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); } /** diff --git a/backend/src/main/java/com/ai/lawyer/domain/lawWord/controller/LawWordController.java b/backend/src/main/java/com/ai/lawyer/domain/lawWord/controller/LawWordController.java index 2a5f258c..6d20c59d 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/lawWord/controller/LawWordController.java +++ b/backend/src/main/java/com/ai/lawyer/domain/lawWord/controller/LawWordController.java @@ -19,15 +19,25 @@ public class LawWordController { private final LawWordService lawWordService; - @GetMapping("/{word}") - @Operation(summary = "법령 용어 검색", description = "법령 용어에 대한 정의를 반환합니다. \n" + - "예시: /api/law-word/선박") - public ResponseEntity getPrecedent(@PathVariable String word) { + @GetMapping("/v1/{word}") + @Operation(summary = "법령 용어 검색 version 1", description = "법령 용어에 대한 정의를 반환합니다. \n" + + "예시: /api/law-word/승소") + public ResponseEntity getPrecedentV1(@PathVariable String word) { try { return ResponseEntity.ok(lawWordService.findDefinition(word)); }catch (Exception e){ return ResponseEntity.badRequest().body(e.getMessage()); } + } + @GetMapping("/v2/{word}") + @Operation(summary = "법령 용어 검색 version 2", description = "법령 용어에 대한 정의를 반환합니다. \n" + + "예시: /api/law-word/승소") + public ResponseEntity getPrecedentV2(@PathVariable String word) { + try { + return ResponseEntity.ok(lawWordService.findDefinitionV2(word)); + }catch (Exception e){ + return ResponseEntity.badRequest().body(e.getMessage()); + } } } diff --git a/backend/src/main/java/com/ai/lawyer/domain/lawWord/service/LawWordService.java b/backend/src/main/java/com/ai/lawyer/domain/lawWord/service/LawWordService.java index e5009dde..d07ea9e8 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/lawWord/service/LawWordService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/lawWord/service/LawWordService.java @@ -10,6 +10,11 @@ import org.springframework.stereotype.Service; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.List; @Service @Slf4j @@ -17,12 +22,16 @@ public class LawWordService { private final LawWordRepository lawWordRepository; - private final RestTemplate restTemplate = new RestTemplate(); + private final WebClient webClient = WebClient.builder().build(); private final ObjectMapper objectMapper = new ObjectMapper(); private static final String API_BASE_URL = "https://www.law.go.kr/DRF/lawService.do"; private static final String API_OC = "noheechul"; + // 우리말샘 API 설정 + private static final String KOREAN_DICT_API_BASE_URL = "https://opendict.korean.go.kr/api/search"; + private static final String API_KEY = "2A4D1A844C8BAB682B38E5F192D3D42A"; + public String findDefinition(String word) { // 1) DB에서 먼저 조회 return lawWordRepository.findByWord(word) @@ -30,10 +39,22 @@ public String findDefinition(String word) { .orElseGet(() -> fetchAndSaveDefinition(word)); } + public String findDefinitionV2(String word) { + // 1) DB에서 먼저 조회 + return lawWordRepository.findByWord(word) + .map(LawWord::getDefinition) + .orElseGet(() -> fetchAndSaveDefinitionV2(word)); + } + private String fetchAndSaveDefinition(String word) { try { String url = buildApiUrl(word); - String json = restTemplate.getForObject(url, String.class); + // WebClient 호출 (동기 방식) + String json = webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); String definition = extractDefinitionFromJson(json); saveDefinition(word, definition); @@ -52,10 +73,54 @@ private String fetchAndSaveDefinition(String word) { } } + private String fetchAndSaveDefinitionV2(String word) { + try { + String url = buildKoreanDictApiUrl(word); + + // WebClient 호출 (동기 방식) + String json = webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); + + String combinedDefinitions = extractTop3DefinitionsFromJson(json); + saveDefinition(word, combinedDefinitions); + + return combinedDefinitions; + + } catch (HttpClientErrorException e) { + log.error("한국어사전 API 호출 중 클라이언트 오류 발생: {}", e.getMessage()); + throw new RuntimeException("한국어사전 API 호출 중 오류가 발생했습니다."); + } catch (JsonProcessingException e) { + log.error("JSON 파싱 중 오류 발생: {}", e.getMessage()); + throw new RuntimeException("한국어사전 API 응답 처리 중 파싱 오류가 발생했습니다."); + } catch (Exception e) { + log.error("정의 조회 실패: ", e); + throw new RuntimeException("한국어사전 정의 조회 중 알 수 없는 오류가 발생했습니다."); + } + } + private String buildApiUrl(String word) { return API_BASE_URL + "?OC=" + API_OC + "&target=lstrm&type=JSON&query=" + word; } + private String buildKoreanDictApiUrl(String word) { + return UriComponentsBuilder.fromHttpUrl(KOREAN_DICT_API_BASE_URL) + .queryParam("key", API_KEY) + .queryParam("req_type", "json") + .queryParam("part", "word") + .queryParam("q", word) + .queryParam("sort", "dict") + .queryParam("start", "1") + .queryParam("num", "10") + .queryParam("advanced", "y") + .queryParam("type4", "all") + .queryParam("cat", "23") + .build() + .toUriString(); + } + private String extractDefinitionFromJson(String json) throws JsonProcessingException { JsonNode rootNode = objectMapper.readTree(json); if (rootNode.has("Law")) { @@ -71,6 +136,41 @@ private String extractDefinitionFromJson(String json) throws JsonProcessingExcep } } + private String extractTop3DefinitionsFromJson(String json) throws JsonProcessingException { + JsonNode rootNode = objectMapper.readTree(json); + + // channel > item 배열에서 아이템들 추출 + JsonNode itemsNode = rootNode.path("channel").path("item"); + + if (!itemsNode.isArray() || itemsNode.size() == 0) { + throw new RuntimeException("검색 결과가 없습니다."); + } + + List definitions = new ArrayList<>(); + + // 최대 3개의 definition 추출 + for (int i = 0; i < Math.min(itemsNode.size(), 3); i++) { + JsonNode item = itemsNode.get(i); + JsonNode senseNode = item.path("sense"); + + if (senseNode.isArray() && senseNode.size() > 0) { + JsonNode firstSense = senseNode.get(0); + String definition = firstSense.path("definition").asText(); + + if (definition != null && !definition.trim().isEmpty()) { + definitions.add(definition.trim()); + } + } + } + + if (definitions.isEmpty()) { + throw new RuntimeException("검색 결과에서 정의를 찾을 수 없습니다."); + } + + // 줄바꿈으로 연결하여 하나의 문자열로 만들기 + return String.join("\n", definitions); + } + private void saveDefinition(String word, String definition) { LawWord entity = LawWord.builder() .word(word) diff --git a/backend/src/main/java/com/ai/lawyer/domain/precedent/dto/PrecedentSummaryListDto.java b/backend/src/main/java/com/ai/lawyer/domain/precedent/dto/PrecedentSummaryListDto.java index d88982bc..9d9b977b 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/precedent/dto/PrecedentSummaryListDto.java +++ b/backend/src/main/java/com/ai/lawyer/domain/precedent/dto/PrecedentSummaryListDto.java @@ -2,11 +2,13 @@ import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import java.time.LocalDate; @Data @AllArgsConstructor +@NoArgsConstructor public class PrecedentSummaryListDto { private Long id; private String caseName; // 사건명 diff --git a/backend/src/main/java/com/ai/lawyer/domain/precedent/service/PrecedentService.java b/backend/src/main/java/com/ai/lawyer/domain/precedent/service/PrecedentService.java index 056474ab..cdd1d885 100644 --- a/backend/src/main/java/com/ai/lawyer/domain/precedent/service/PrecedentService.java +++ b/backend/src/main/java/com/ai/lawyer/domain/precedent/service/PrecedentService.java @@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; import java.time.LocalDate; @@ -31,7 +32,7 @@ public class PrecedentService { private final PrecedentRepository precedentRepository; private final EntityManager entityManager; - private final RestTemplate restTemplate = new RestTemplate(); + private final WebClient webClient = WebClient.builder().build(); private final ObjectMapper objectMapper = new ObjectMapper(); // 상수 정의 @@ -78,7 +79,11 @@ public List getPrecedentNumbers(String query) { do { String url = buildSearchUrl(query, page); - String json = restTemplate.getForObject(url, String.class); + String json = webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); JsonNode root = objectMapper.readTree(json); JsonNode precSearch = root.path("PrecSearch"); @@ -114,7 +119,11 @@ public List getPrecedentDetails(List precedentIds) { for (String precedentId : precedentIds) { try { String url = buildDetailUrl(precedentId); - String json = restTemplate.getForObject(url, String.class); + String json = webClient.get() + .uri(url) + .retrieve() + .bodyToMono(String.class) + .block(); Precedent precedent = parseJsonToPrecedent(json); if (precedent != null) {