From 8935a5cd8d34322eeb06d73bdd58491b74bfca43 Mon Sep 17 00:00:00 2001 From: passionryu Date: Fri, 25 Jul 2025 20:50:11 +0900 Subject: [PATCH] =?UTF-8?q?=20=F0=9F=9A=91=EF=B8=8F=20[=EA=B8=B4=EA=B8=89?= =?UTF-8?q?=20=EC=88=98=EC=A0=95]=20:=20=EA=B2=80=EC=83=89=EB=90=9C=20?= =?UTF-8?q?=EB=89=B4=EC=8A=A4=EA=B0=80=205=EA=B0=9C=20=EC=9D=B4=ED=95=98?= =?UTF-8?q?=EC=9D=BC=20=EC=8B=9C,=207=EC=9D=BC=EA=B0=84=EC=9D=98=20?= =?UTF-8?q?=EB=89=B4=EC=8A=A4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20Sea?= =?UTF-8?q?rch=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../News_Deliver/Domain/Kakao/DeprecatedCodes | 237 ++++++++++ .../Kakao/Helper/KakaoMessageHelper.java | 70 +++ .../Kakao/Manager/KakaoMessageManager.java | 248 ++++++++++ .../Kakao/controller/KakaoController.java | 3 +- .../Kakao/service/KakaoMessageService.java | 434 +----------------- .../Kakao/service/KakaoNewsService.java | 137 ++++-- .../Scheduler/TaskSchedulerService.java | 22 - 7 files changed, 670 insertions(+), 481 deletions(-) create mode 100644 SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/DeprecatedCodes create mode 100644 SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Helper/KakaoMessageHelper.java create mode 100644 SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Manager/KakaoMessageManager.java diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/DeprecatedCodes b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/DeprecatedCodes new file mode 100644 index 0000000..43b0c3c --- /dev/null +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/DeprecatedCodes @@ -0,0 +1,237 @@ +/* KakaoMessageService */ + + @Value("${spring.security.oauth2.client.registration.kakao.client-id}") + private String kakaoClientId; + @Value("${spring.security.oauth2.client.registration.kakao.client-secret}") + private String kakaoClientSecret; + + /** + * 카카오 메시지 전송 메서드 + * + * @param refreshAccessToken 유저의 리프레시 토큰 + * @param userId 유저의 고유 번호 + * @return T,F + */ + public boolean sendKakaoMessage(String refreshAccessToken, Long userId) { + + /* 유저의 세팅 리스트 반환 */ + log.info("refreshAccessToken 발급 결과 :{}", refreshAccessToken); + String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId); + log.info("accessToken 발급 결과:{}", accessToken); + List settings = settingService.getAllSettingsByUserId(userId); + boolean anySuccess = false; + + /* Setting을 순회하며 뉴스 리스트 저장&전송 */ + for (SettingDTO setting : settings) { + List newsList = newsService.searchNews( + setting.getSettingKeywords(), setting.getBlockKeywords()); + + if (newsList == null || newsList.isEmpty()) { + log.info("세팅 ID {}에 해당하는 뉴스가 없음", setting.getId()); + continue; + } + + if (newsList.size() > 5) { + newsList = newsList.subList(0, 5); + } + + saveHistory(newsList, List.of(setting)); + boolean success = sendSingleKakaoMessage(accessToken, newsList); + anySuccess = anySuccess || success; + } + + return anySuccess; + } + + /** + * 사용자의 키워드에 맞는 뉴스를 검색한 후, 카카오 메시지를 전송합니다. + * + * @param refreshAccessToken 사용자 카카오 Refresh Token + * @param userId 사용자 고유 ID + * @return 메시지 전송 성공 여부 (true: 성공, false: 실패) + */ + public boolean sendKakaoMessage(String refreshAccessToken, Long userId) { + try { + + /** + * 문제 정의 : 세팅 1번에 대해서만 메시지가 전송된다. + */ + + /* 유저에게 맞는 뉴스 리스트 검색*/ + String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId); + List newsList = getNewsEsDocumentList_Fixed(userId); + if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);; + + /* Http 요청 헤더 설정 */ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Authorization", "Bearer " + accessToken); + + /* 템플릿 설정(ES로 검색한 뉴스 리스트 넘겨받음) */ + Map templateArgs = createTemplateData(newsList); + + /* JSON 문자열로 변환 */ + ObjectMapper objectMapper = new ObjectMapper(); + String templateArgsJson = objectMapper.writeValueAsString(templateArgs); + + /* 요청 파라미터 구성 */ + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("template_id", "122080"); + params.add("template_args", templateArgsJson); + + /* 카카오 메시지 전송 */ + HttpEntity> entity = new HttpEntity<>(params, + headers); + ResponseEntity response = + restTemplate.postForEntity(KAKAO_SEND_TOME_URL, entity, String.class); + log.info("카카오 메시지 전송 응답: {}", response.getBody()); + + return response.getStatusCode() == HttpStatus.OK; + + } catch (Exception e) { + log.error("카카오 메시지 전송 실패: ", e); + throw new KakaoException(ErrorCode.MESSAGE_SEND_FAILED); + } + } + + /** + * 사용자의 Setting 정보를 기반으로 키워드에 해당하는 뉴스를 검색하여 반환합니다. + * 뉴스는 히스토리에 저장되며, 최대 5개까지 템플릿으로 전송됩니다. + * + * @param userId 사용자 고유 ID + * @return 뉴스 리스트 {@code List}, 키워드가 없거나 오류 시 {@code null} + */ + private List getNewsEsDocumentList(Long userId) { + + //유저 정보를 기준으로 Settig값 가져오기 + List settings = settingService.getAllSettingsByUserId(userId); + + List keywords = new ArrayList<>(); + List blockKeywords = new ArrayList<>(); + + for (SettingDTO setting : settings) { + log.info("셋팅값 확인용 코드 : " + setting.getSettingKeywords()); + log.info("셋팅 제외 확인용 코드 : " + setting.getBlockKeywords()); + + // 키워드리스트의 null 값 체크 + if (setting.getSettingKeywords() != null) { + keywords.add(setting.getSettingKeywords().toString()); + } + + blockKeywords.add(setting.getBlockKeywords().toString()); + } + + if (keywords.isEmpty()) { + log.error("설정된 키워드가 없습니다."); + throw new KakaoException(ErrorCode.SETTING_NOT_FOUND); + } + + //키워드별 뉴스 검색 + List newsList = newsService.searchNews(keywords, + blockKeywords); + + log.info("검색된 뉴스 수: {}", newsList.size()); + newsList.forEach(n -> log.info("뉴스: {} - {}", n.getPublisher(), + n.getSummary())); + + // 검색된 뉴스를 히스토리로 보내는 코드 + if (saveHistory(newsList, settings)) return null; + return newsList; + } + + +/* KakaoNewsService */ + + /** + * 사용자의 키워드를 리스트로 묶어 뉴스 검색 메서드에 전달 + * + * @param includeKeywords 포함 키워드 + * @param blockKeywords 제외 키워드 + * @return + */ + public List searchNews(List includeKeywords, List blockKeywords) { + try { + + /* 전날 기준으로 시간을 측정 */ + LocalDate yesterday = LocalDate.now().minusDays(1); + + /* 포함 키워드 쿼리 */ + Query includeKeywordQuery = Query.of(q -> q + .bool(b -> b + .should(includeKeywords.stream() + .map(kw -> Query.of(q2 -> q2 + .multiMatch(m -> m + .query(kw) + .fields("title", "summary", "content_url", "publisher") + .type(TextQueryType.BoolPrefix) + ) + )) + .collect(Collectors.toList()) + ) + .minimumShouldMatch("1") + ) + ); + + /* 제외 키워드 쿼리 */ + Query excludeKeywordQuery = Query.of(q -> q + .bool(b -> b + .should(blockKeywords.stream() + .map(kw -> Query.of(q2 -> q2 + .multiMatch(m -> m + .query(kw) + .fields("title", "summary", "content_url", "publisher") + .type(TextQueryType.BoolPrefix) + ) + )) + .collect(Collectors.toList()) + ) + ) + ); + + /* 날짜 필터 쿼리 */ + Query dateFilter = Query.of(q -> q + .range(r -> r + .field("published_at") + .gte(JsonData.of(yesterday.toString())) + .lte(JsonData.of(yesterday.toString())) + .format("yyyy-MM-dd") + ) + ); + + /* 전체 쿼리 조합 */ + Query finalQuery = Query.of(q -> q + .bool(b -> b + .must(includeKeywordQuery) + .must(dateFilter) + .mustNot(excludeKeywordQuery) + ) + ); + + /* 검색 요청 */ + SearchRequest request = SearchRequest.of(s -> s + .index("news-index-nori") + .query(finalQuery) + .size(5) + .sort(sort -> sort + .score(sc -> sc.order(co.elastic.clients.elasticsearch._types.SortOrder.Desc)) + ) + ); + + /* 검색 수행 */ + SearchResponse response = client.search(request, NewsEsDocument.class); + + /* 로그: 스코어 확인 */ + response.hits().hits().forEach(hit -> + log.info("{} | score: {}", hit.source().getTitle(), hit.score()) + ); + + /* 결과 반환 */ + return response.hits().hits().stream() + .map(hit -> hit.source()) + .collect(Collectors.toList()); + + } catch (IOException e) { + log.error("키워드 기반 뉴스 검색 실패: {}", e.getMessage(), e); + return List.of(); + } + } \ No newline at end of file diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Helper/KakaoMessageHelper.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Helper/KakaoMessageHelper.java new file mode 100644 index 0000000..6887da4 --- /dev/null +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Helper/KakaoMessageHelper.java @@ -0,0 +1,70 @@ +package Baemin.News_Deliver.Domain.Kakao.Helper; + +import Baemin.News_Deliver.Domain.Mypage.DTO.SettingDTO; +import Baemin.News_Deliver.Global.News.ElasticSearch.dto.NewsEsDocument; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class KakaoMessageHelper { + + /** + * 뉴스 리스트를 템플릿 전송용 파라미터(Map)로 변환하는 메서드(최대 5개) + * + * @param newsList 뉴스 리스트 + * @return 템플릿에 들어갈 파라미터 Map + */ + public static Map createTemplateData(List newsList) { + + log.info("뉴스 전체 리스트 확인:" + newsList); + Map templateArgs = new HashMap<>(); + + /* 메세지 5개 고정 */ + for (int i = 0; i < Math.min(5, newsList.size()); i++) { + NewsEsDocument news = newsList.get(i); + templateArgs.put("TITLE" + (i + 1), news.getTitle()); + templateArgs.put("SUMMARY" + (i + 1), news.getSummary()); + templateArgs.put("PUBLISHER" + (i + 1), news.getPublisher()); + templateArgs.put("CONTENTURL" + (i + 1), "redirect?target=" + news.getContent_url()); + // templateArgs.put("CONTENTURL" + (i + 1), news.getContent_url()); + } + return templateArgs; + + } + + /** + * 현재 시간에 발송할 세팅이 있는지 확인하는 메서드 + * + * @param currentSettings 현재 검사중인 세팅 + * @param nowTime 지금 시간 + */ + public static void checkCurrentSetting_Exist(List currentSettings, LocalTime nowTime) { + + if (currentSettings.isEmpty()) { + log.info("현재 시간에 발송할 세팅이 없습니다: {}", nowTime); + } + } + + /** + * 현재 시간 기준 뉴스 받아야 할 유저 리스트 필터링하는 메서드 + * + * @param allSettings 모든 세팅값 + * @param nowTime 현재의 시간 + * @return 세팅 DTO + */ + public static List filterCurrentSettings(List allSettings, LocalTime nowTime) { + + return allSettings.stream() + .filter(setting -> { + LocalTime deliveryTime = setting.getDeliveryTime().toLocalTime().truncatedTo(ChronoUnit.MINUTES); + return deliveryTime.equals(nowTime); + }) + .toList(); + } + +} \ No newline at end of file diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Manager/KakaoMessageManager.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Manager/KakaoMessageManager.java new file mode 100644 index 0000000..d9ecc35 --- /dev/null +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/Manager/KakaoMessageManager.java @@ -0,0 +1,248 @@ +package Baemin.News_Deliver.Domain.Kakao.Manager; + +import Baemin.News_Deliver.Domain.Kakao.Exception.KakaoException; +import Baemin.News_Deliver.Domain.Kakao.Helper.KakaoMessageHelper; +import Baemin.News_Deliver.Domain.Kakao.entity.History; +import Baemin.News_Deliver.Domain.Kakao.repository.HistoryRepository; +import Baemin.News_Deliver.Domain.Kakao.repository.NewsRepository; +import Baemin.News_Deliver.Domain.Kakao.service.KakaoNewsService; +import Baemin.News_Deliver.Domain.Mypage.DTO.SettingDTO; +import Baemin.News_Deliver.Domain.Mypage.Entity.Setting; +import Baemin.News_Deliver.Domain.Mypage.Repository.SettingRepository; +import Baemin.News_Deliver.Domain.Mypage.service.SettingService; +import Baemin.News_Deliver.Global.Exception.ErrorCode; +import Baemin.News_Deliver.Global.Kakao.KakaoTokenProvider; +import Baemin.News_Deliver.Global.News.Batch.entity.News; +import Baemin.News_Deliver.Global.News.ElasticSearch.dto.NewsEsDocument; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class KakaoMessageManager { + + private static final String KAKAO_SEND_TOME_URL = "https://kapi.kakao.com/v2/api/talk/memo/send"; + private final RestTemplate restTemplate = new RestTemplate(); + + private final KakaoTokenProvider kakaoTokenProvider; + private final KakaoNewsService kakaoNewsService; + private final SettingService settingService; + private final KakaoNewsService newsService; + + private final NewsRepository newsRepository; + private final SettingRepository settingRepository; + private final HistoryRepository historyRepository; + + /** + * 카카오 사용자 Access Token을 Refresh Token을 이용해 갱신 후 반환하는 메서드 + * + * @param refreshAccessToken 사용자의 카카오 Refresh Token + * @param userId 사용자 고유 ID + * @return 갱신된 Access Token 문자열 + * @throws RuntimeException Access Token을 가져오지 못했을 경우 + */ + public String getKakaoUserAccessToken(String refreshAccessToken, Long userId) { + + // 유저의 refresh 토큰을 통해 access 토큰 추출 + String accessToken = kakaoTokenProvider.refreshAccessToken(refreshAccessToken); + if (accessToken == null || accessToken.isEmpty()) { + throw new KakaoException(ErrorCode.KAKAO_TOKEN_ACCESS_FAILED); + } + + // 서버 로그 기록 + log.info("[카카오 메시지 전송 서비스] : refreshAccessToken({})을 통해, accessToken({}) 추출 완료", refreshAccessToken,accessToken); + + getNewsEsDocumentList_Fixed(userId); + return accessToken; + } + + /* + * 사용자의 키워드를 바탕으로 뉴스를 검색하여 리스트로 반환하는 메서드 + * + * Edited + * What : 히스토리 오류를 제거하도록 수정 + * Why : 히스토리 DB에 2개의 세팅이 합쳐져서 뉴스가 발송되는 오류 발생 + * When : 2025-07-20 + * How : 류성열 + * + * Deprecated된 메서드는 하단에 정리하였습니다. + * + * @param userId 유저의 고유 번호 + * @return 각 세팅에 맞는 뉴스 기사 리스트 + */ + public void getNewsEsDocumentList_Fixed(Long userId) { + + List settings = settingService.getAllSettingsByUserId(userId); + List totalNewsList = new ArrayList<>(); + + for (SettingDTO setting : settings) { + log.info("셋팅값 확인용 코드 : " + setting.getSettingKeywords()); + log.info("셋팅 제외 확인용 코드 : " + setting.getBlockKeywords()); + + List keywords = setting.getSettingKeywords(); // 예: [이재명] + List blockKeywords = setting.getBlockKeywords(); // 예: [한국, 중국] + + if (keywords == null || keywords.isEmpty()) { + log.warn("세팅에 키워드가 없습니다. 스킵합니다."); + continue; + } + + List newsList = newsService.searchNewsWithFallback(keywords, blockKeywords); + //List newsList = newsService.searchNews(keywords, blockKeywords); + + log.info(">> 세팅당 검색된 뉴스 수: {}", newsList.size()); + + // 세팅당 5개만 취하고 싶다면 limit 적용 + if (newsList.size() > 5) { + newsList = newsList.subList(0, 5); + } + + // 뉴스 히스토리 저장 + saveHistory(newsList, List.of(setting)); + + totalNewsList.addAll(newsList); + } + + log.info("✅ 전체 검색된 뉴스 총합: {}", totalNewsList.size()); + } + + /** + * 전송된 뉴스 정보를 히스토리로 저장합니다. 중복 뉴스는 저장하지 않습니다. + * + * @param newsList 뉴스 리스트 + * @param settings 해당 뉴스에 적용된 사용자 설정들 + * @return 저장이 이루어진 경우 true, 아무 것도 저장되지 않았으면 false + */ + public boolean saveHistory(List newsList, List settings) { + if (newsList == null || newsList.isEmpty()) { + log.warn("해당 키워드로 검색된 뉴스가 없습니다."); + throw new KakaoException(ErrorCode.NO_NEWS_DATA); + } + + // 중복 저장 방지용 플래그 + boolean saved = false; + + for (NewsEsDocument newsDoc : newsList) { + + /* DB와 ES 동기화 되어있지 않을 시, 예외 */ + News newsitem = newsRepository.findById(Long.parseLong(newsDoc.getId())) + .orElse(null); + if (newsitem == null) { + log.warn("DB에 존재하지 않는 뉴스입니다. id={}", newsDoc.getId()); + continue; // 이 뉴스는 히스토리 저장 생략 + } + + for (SettingDTO settingDTO : settings) { + Setting setting = settingRepository.findById(settingDTO.getId()) + .orElseThrow(() -> new KakaoException(ErrorCode.SETTING_NOT_FOUND)); + + // 중복 저장 방지용 코드 + boolean exists = historyRepository.existsBySettingAndNews(setting, newsitem); + if (exists) { + log.info("이미 저장된 뉴스입니다. (settingId={}, newsId={})", setting.getId(), newsitem.getId()); + continue; + } + + History history = History.builder() + .publishedAt(LocalDateTime.now()) + .setting(setting) + .news(newsitem) + .settingKeyword(String.join(",", settingDTO.getSettingKeywords())) + .blockKeyword( + settingDTO.getBlockKeywords() != null ? String.join(",", settingDTO.getBlockKeywords()) + : null) + .build(); + + historyRepository.save(history); + saved = true; + } + } + + return saved; + } + + /** + * 개별 카카오톡 전송 메서드 + * + * @param accessToken 유저 엑세스 토큰 + * @param newsList 뉴스 리스트 + * @return T,F + */ + public boolean sendSingleKakaoMessage(String accessToken, List newsList) { + try { + + /* 헤더 설정 */ + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.set("Authorization", "Bearer " + accessToken); + + /* 템플릿 설정 */ + Map templateArgs = KakaoMessageHelper.createTemplateData(newsList); + ObjectMapper objectMapper = new ObjectMapper(); + String templateArgsJson = objectMapper.writeValueAsString(templateArgs); + + MultiValueMap params = new LinkedMultiValueMap<>(); + // params.add("template_id", "122080"); + params.add("template_id", "122693"); + params.add("template_args", templateArgsJson); + + /* 세팅 별 개별 메시지 전송 */ + HttpEntity> entity = new HttpEntity<>(params, headers); + ResponseEntity response = restTemplate.postForEntity(KAKAO_SEND_TOME_URL, entity, String.class); + log.info("카카오 메시지 전송 응답: {}", response.getBody()); + + return response.getStatusCode() == HttpStatus.OK; + } catch (Exception e) { + log.error("카카오 메시지 전송 실패: ", e); + return false; + } + } + + /** + * 메시지 발송을 과정 프로세스 관리 메서드 + * + * @param accessToken 유저의 엑세스 토큰 + * @param setting 유저 세팅 + * @return T,F + */ + public void processSetting(String accessToken, SettingDTO setting) { + + /* deprecated */ +// // 뉴스 검색 +// List newsList = kakaoNewsService.searchNews( +// setting.getSettingKeywords(), setting.getBlockKeywords()); + + // 뉴스 검색 + List newsList = kakaoNewsService.searchNewsWithFallback( + setting.getSettingKeywords(), setting.getBlockKeywords()); + + // 뉴스가 겁색 되지 않을 시의 예외 처리 + if (newsList == null || newsList.isEmpty()) { + log.info("[스킵] : 세팅 ID {}에 해당하는 뉴스가 검색되지 않음", setting.getId()); + } + + // 뉴스는 최대 5개로 제한 + if (newsList.size() > 5) { + newsList = newsList.subList(0, 5); + } + + // 히스토리에 저장 + saveHistory(newsList, List.of(setting)); + + // 유저에게 전송 + sendSingleKakaoMessage(accessToken, newsList); + } + +} diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/controller/KakaoController.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/controller/KakaoController.java index 7c65679..622d9c0 100644 --- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/controller/KakaoController.java +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/controller/KakaoController.java @@ -114,6 +114,7 @@ public ResponseEntity> searchNewsTest() { List keyword = null; List blockKeyword = null; - return ResponseEntity.ok(newsSearchService.searchNews(keyword, blockKeyword)); + // return ResponseEntity.ok(newsSearchService.searchNews(keyword, blockKeyword)); + return ResponseEntity.ok(newsSearchService.searchNewsWithFallback(keyword, blockKeyword)); } } diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoMessageService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoMessageService.java index 1e7598e..0d8ddae 100644 --- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoMessageService.java +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoMessageService.java @@ -1,452 +1,52 @@ package Baemin.News_Deliver.Domain.Kakao.service; -import Baemin.News_Deliver.Domain.Auth.Entity.User; -import Baemin.News_Deliver.Domain.Auth.Repository.UserRepository; -import Baemin.News_Deliver.Domain.Kakao.Exception.KakaoException; -import Baemin.News_Deliver.Domain.Kakao.entity.History; -import Baemin.News_Deliver.Domain.Kakao.repository.HistoryRepository; -import Baemin.News_Deliver.Domain.Kakao.repository.NewsRepository; +import Baemin.News_Deliver.Domain.Kakao.Helper.KakaoMessageHelper; +import Baemin.News_Deliver.Domain.Kakao.Manager.KakaoMessageManager; import Baemin.News_Deliver.Domain.Mypage.DTO.SettingDTO; -import Baemin.News_Deliver.Domain.Mypage.Entity.Setting; -import Baemin.News_Deliver.Domain.Mypage.Repository.SettingRepository; import Baemin.News_Deliver.Domain.Mypage.service.SettingService; -import Baemin.News_Deliver.Global.Exception.ErrorCode; -import Baemin.News_Deliver.Global.Kakao.KakaoTokenProvider; -import Baemin.News_Deliver.Global.News.Batch.entity.News; -import Baemin.News_Deliver.Global.News.ElasticSearch.dto.NewsEsDocument; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; import org.springframework.stereotype.Service; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestTemplate; -import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -/** - * KakaoMessageService는 사용자의 키워드 기반으로 뉴스를 조회하고, - * 해당 뉴스를 카카오톡 메시지로 전송하는 기능을 담당하는 서비스입니다. - * - * 주요 기능: - *
    - *
  • 유저의 AccessToken 갱신 및 조회
  • - *
  • 뉴스 검색 및 사용자 맞춤 필터링
  • - *
  • 카카오톡 템플릿 메시지 전송
  • - *
  • 전송된 뉴스에 대한 히스토리 저장
  • - *
- */ @Service @RequiredArgsConstructor @Slf4j public class KakaoMessageService { - private final KakaoTokenProvider provider; - private final RestTemplate restTemplate = new RestTemplate(); - private final KakaoNewsService newsService; private final SettingService settingService; - private final NewsRepository newsRepository; - private final SettingRepository settingRepository; - private final HistoryRepository historyRepository; - - private static final String KAKAO_SEND_TOME_URL = "https://kapi.kakao.com/v2/api/talk/memo/send"; - - @Value("${spring.security.oauth2.client.registration.kakao.client-id}") - private String kakaoClientId; - @Value("${spring.security.oauth2.client.registration.kakao.client-secret}") - private String kakaoClientSecret; - - /** - * 카카오 사용자 Access Token을 Refresh Token을 이용해 갱신 후 반환합니다. - * - * @param refreshAccessToken 사용자의 카카오 Refresh Token - * @param userId 사용자 고유 ID - * @return 갱신된 Access Token 문자열 - * @throws RuntimeException Access Token을 가져오지 못했을 경우 - */ - public String getKakaoUserAccessToken(String refreshAccessToken, Long userId) { - - // 유저의 accesstoken을 가져 올 것 - String accessToken = provider.refreshAccessToken(refreshAccessToken); - if (accessToken == null || accessToken.isEmpty()) { - throw new KakaoException(ErrorCode.KAKAO_TOKEN_ACCESS_FAILED); - } - - getNewsEsDocumentList_Fixed(userId); - return accessToken; - } + private final KakaoMessageManager kakaoMessageManager; /** - * 카카오 메시지 전송 메서드 + * 유저의 세팅값에 맞는 뉴스를 추출 후, 카카오 메시지로 전송하는 메서드 * * @param refreshAccessToken 유저의 리프레시 토큰 - * @param userId 유저의 고유 번호 - * @return T,F + * @param userId 유저의 고유 번호 */ - public boolean sendKakaoMessage(String refreshAccessToken, Long userId) { - log.info("refreshAccessToken 발급 결과 :{}", refreshAccessToken); - String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId); - log.info("accessToken 발급 결과:{}", accessToken); + public void sendKakaoMessage(String refreshAccessToken, Long userId) { - // 1. 유저의 모든 유효한 세팅 조회 - List settings = settingService.getAllSettingsByUserId(userId); + // 유저의 리프레시 토큰에서 엑세스 토큰을 발급 + String accessToken = kakaoMessageManager.getKakaoUserAccessToken(refreshAccessToken, userId); - // LocalTime nowTime = LocalTime.now(); - // // 2. 현재 시간과 일치하는 세팅만 필터링 - // List currentSettings = settings.stream() - // .filter(setting -> - // setting.getDeliveryTime().equals(nowTime.truncatedTo(ChronoUnit.MINUTES))) - // .toList(); + // 유저의 모든 세팅(유효한) 조회 + List settings = settingService.getAllSettingsByUserId(userId); + // 현재 시간 측정(분단위 측정) LocalTime nowTime = LocalTime.now().truncatedTo(ChronoUnit.MINUTES); - List currentSettings = settings.stream() - .filter(setting -> { - LocalTime deliveryTime = setting.getDeliveryTime().toLocalTime().truncatedTo(ChronoUnit.MINUTES); - return deliveryTime.equals(nowTime); - }) - .toList(); - - // todo : 계속 여기서 문제가 생김 - if (currentSettings.isEmpty()) { - log.info("현재 시간에 발송할 세팅이 없습니다: {}", nowTime); - return false; - } + // 현재 시간 기준 뉴스 받아야 할 유저 리스트 필터링 + List currentSettings = KakaoMessageHelper.filterCurrentSettings(settings, nowTime); - boolean anySuccess = false; + // 현재 시간에 유저에게 발송할 세팅이 있는지 확인 + KakaoMessageHelper.checkCurrentSetting_Exist(currentSettings,nowTime); + // 뉴스 검색 → 뉴스 저장 → 메시지 발송까지의 전 과정을 처리 for (SettingDTO setting : currentSettings) { - List newsList = newsService.searchNews( - setting.getSettingKeywords(), setting.getBlockKeywords()); - - if (newsList == null || newsList.isEmpty()) { - log.info("세팅 ID {}에 해당하는 뉴스가 없음", setting.getId()); - continue; - } - - if (newsList.size() > 5) { - newsList = newsList.subList(0, 5); - } - - saveHistory(newsList, List.of(setting)); - boolean success = sendSingleKakaoMessage(accessToken, newsList); - anySuccess = anySuccess || success; + kakaoMessageManager.processSetting(accessToken, setting); } - return anySuccess; } - - // /** - // * 카카오 메시지 전송 메서드 - // * - // * @param refreshAccessToken 유저의 리프레시 토큰 - // * @param userId 유저의 고유 번호 - // * @return T,F - // */ - // public boolean sendKakaoMessage(String refreshAccessToken, Long userId) { - // - // /* 유저의 세팅 리스트 반환 */ - // log.info("refreshAccessToken 발급 결과 :{}", refreshAccessToken); - // String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId); - // log.info("accessToken 발급 결과:{}", accessToken); - // List settings = settingService.getAllSettingsByUserId(userId); - // boolean anySuccess = false; - // - // /* Setting을 순회하며 뉴스 리스트 저장&전송 */ - // for (SettingDTO setting : settings) { - // List newsList = newsService.searchNews( - // setting.getSettingKeywords(), setting.getBlockKeywords()); - // - // if (newsList == null || newsList.isEmpty()) { - // log.info("세팅 ID {}에 해당하는 뉴스가 없음", setting.getId()); - // continue; - // } - // - // if (newsList.size() > 5) { - // newsList = newsList.subList(0, 5); - // } - // - // saveHistory(newsList, List.of(setting)); - // boolean success = sendSingleKakaoMessage(accessToken, newsList); - // anySuccess = anySuccess || success; - // } - // - // return anySuccess; - // } - - /** - * 개별 카카오톡 전송 메섣, - * - * @param accessToken 유저 엑세스 토큰 - * @param newsList 뉴스 리스트 - * @return T,F - */ - private boolean sendSingleKakaoMessage(String accessToken, List newsList) { - try { - - /* 헤더 설정 */ - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.set("Authorization", "Bearer " + accessToken); - - /* 템플릿 설정 */ - Map templateArgs = createTemplateData(newsList); - ObjectMapper objectMapper = new ObjectMapper(); - String templateArgsJson = objectMapper.writeValueAsString(templateArgs); - - MultiValueMap params = new LinkedMultiValueMap<>(); - // params.add("template_id", "122080"); - params.add("template_id", "122693"); - params.add("template_args", templateArgsJson); - - /* 세팅 별 개별 메시지 전송 */ - HttpEntity> entity = new HttpEntity<>(params, headers); - ResponseEntity response = restTemplate.postForEntity(KAKAO_SEND_TOME_URL, entity, String.class); - log.info("카카오 메시지 전송 응답: {}", response.getBody()); - - return response.getStatusCode() == HttpStatus.OK; - } catch (Exception e) { - log.error("카카오 메시지 전송 실패: ", e); - return false; - } - } - - /* - * 사용자의 키워드를 바탕으로 뉴스를 검색하여 리스트로 반환하는 메서드 - * - * Edit By : 성열 - * When : 2025-07-20 - * Why : 히스토리 DB에 2개의 세팅이 합쳐져서 뉴스가 발송되는 오류 해결 - * - * Deprecated된 메서드는 하단에 정리하였습니다. - * - * @param userId 유저의 고유 번호 - * - * @return 각 세팅에 맞는 뉴스 기사 리스트 - */ - private List getNewsEsDocumentList_Fixed(Long userId) { - List settings = settingService.getAllSettingsByUserId(userId); - List totalNewsList = new ArrayList<>(); - - for (SettingDTO setting : settings) { - log.info("셋팅값 확인용 코드 : " + setting.getSettingKeywords()); - log.info("셋팅 제외 확인용 코드 : " + setting.getBlockKeywords()); - - List keywords = setting.getSettingKeywords(); // 예: [이재명] - List blockKeywords = setting.getBlockKeywords(); // 예: [한국, 중국] - - if (keywords == null || keywords.isEmpty()) { - log.warn("세팅에 키워드가 없습니다. 스킵합니다."); - continue; - } - - List newsList = newsService.searchNews(keywords, blockKeywords); - - log.info(">> 세팅당 검색된 뉴스 수: {}", newsList.size()); - - // 세팅당 5개만 취하고 싶다면 limit 적용 - if (newsList.size() > 5) { - newsList = newsList.subList(0, 5); - } - - // 뉴스 히스토리 저장 - saveHistory(newsList, List.of(setting)); // 단일 setting 기준 - - totalNewsList.addAll(newsList); - } - - log.info("✅ 전체 검색된 뉴스 총합: {}", totalNewsList.size()); - return totalNewsList; - } - - /** - * 뉴스 리스트를 템플릿 전송용 파라미터(Map)로 변환합니다. - * 최대 5개의 뉴스만 포함됩니다. - * - * @param newsList 뉴스 리스트 - * @return 템플릿에 들어갈 파라미터 Map - */ - private static Map createTemplateData(List newsList) { - - log.info("뉴스 전체 리스트 확인:" + newsList); - Map templateArgs = new HashMap<>(); - - // 메세지 5개 고정 - for (int i = 0; i < Math.min(5, newsList.size()); i++) { - NewsEsDocument news = newsList.get(i); - templateArgs.put("TITLE" + (i + 1), news.getTitle()); - templateArgs.put("SUMMARY" + (i + 1), news.getSummary()); - templateArgs.put("PUBLISHER" + (i + 1), news.getPublisher()); - templateArgs.put("CONTENTURL" + (i + 1), "redirect?target=" + news.getContent_url()); - // templateArgs.put("CONTENTURL" + (i + 1), news.getContent_url()); - } - return templateArgs; - - } - - /** - * 전송된 뉴스 정보를 히스토리로 저장합니다. 중복 뉴스는 저장하지 않습니다. - * - * @param newsList 뉴스 리스트 - * @param settings 해당 뉴스에 적용된 사용자 설정들 - * @return 저장이 이루어진 경우 true, 아무 것도 저장되지 않았으면 false - */ - private boolean saveHistory(List newsList, List settings) { - if (newsList == null || newsList.isEmpty()) { - log.warn("해당 키워드로 검색된 뉴스가 없습니다."); - throw new KakaoException(ErrorCode.NO_NEWS_DATA); - } - - // 중복 저장 방지용 플래그 - boolean saved = false; - - for (NewsEsDocument newsDoc : newsList) { - // News newsitem = newsRepository.findById(Long.parseLong(newsDoc.getId())) - // .orElseThrow(() -> new RuntimeException("뉴스가 존재하지 않습니다: " + - // newsDoc.getId())); - - /* DB와 ES 동기화 되어있지 않을 시, 예외 */ - News newsitem = newsRepository.findById(Long.parseLong(newsDoc.getId())) - .orElse(null); - if (newsitem == null) { - log.warn("DB에 존재하지 않는 뉴스입니다. id={}", newsDoc.getId()); - continue; // 이 뉴스는 히스토리 저장 생략 - } - - for (SettingDTO settingDTO : settings) { - Setting setting = settingRepository.findById(settingDTO.getId()) - .orElseThrow(() -> new KakaoException(ErrorCode.SETTING_NOT_FOUND)); - - // 중복 저장 방지용 코드 - boolean exists = historyRepository.existsBySettingAndNews(setting, newsitem); - if (exists) { - log.info("이미 저장된 뉴스입니다. (settingId={}, newsId={})", setting.getId(), newsitem.getId()); - continue; - } - - History history = History.builder() - .publishedAt(LocalDateTime.now()) - .setting(setting) - .news(newsitem) - .settingKeyword(String.join(",", settingDTO.getSettingKeywords())) - .blockKeyword( - settingDTO.getBlockKeywords() != null ? String.join(",", settingDTO.getBlockKeywords()) - : null) - .build(); - - historyRepository.save(history); - saved = true; - } - } - - return saved; - } - - // ======================= Deprecated ========================= - - // /** - // * 사용자의 키워드에 맞는 뉴스를 검색한 후, 카카오 메시지를 전송합니다. - // * - // * @param refreshAccessToken 사용자 카카오 Refresh Token - // * @param userId 사용자 고유 ID - // * @return 메시지 전송 성공 여부 (true: 성공, false: 실패) - // */ - // public boolean sendKakaoMessage(String refreshAccessToken, Long userId) { - // try { - // - // /** - // * 문제 정의 : 세팅 1번에 대해서만 메시지가 전송된다. - // */ - // - // /* 유저에게 맞는 뉴스 리스트 검색*/ - // String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId); - // List newsList = getNewsEsDocumentList_Fixed(userId); - // if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);; - // - // /* Http 요청 헤더 설정 */ - // HttpHeaders headers = new HttpHeaders(); - // headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - // headers.set("Authorization", "Bearer " + accessToken); - // - // /* 템플릿 설정(ES로 검색한 뉴스 리스트 넘겨받음) */ - // Map templateArgs = createTemplateData(newsList); - // - // /* JSON 문자열로 변환 */ - // ObjectMapper objectMapper = new ObjectMapper(); - // String templateArgsJson = objectMapper.writeValueAsString(templateArgs); - // - // /* 요청 파라미터 구성 */ - // MultiValueMap params = new LinkedMultiValueMap<>(); - // params.add("template_id", "122080"); - // params.add("template_args", templateArgsJson); - // - // /* 카카오 메시지 전송 */ - // HttpEntity> entity = new HttpEntity<>(params, - // headers); - // ResponseEntity response = - // restTemplate.postForEntity(KAKAO_SEND_TOME_URL, entity, String.class); - // log.info("카카오 메시지 전송 응답: {}", response.getBody()); - // - // return response.getStatusCode() == HttpStatus.OK; - // - // } catch (Exception e) { - // log.error("카카오 메시지 전송 실패: ", e); - // throw new KakaoException(ErrorCode.MESSAGE_SEND_FAILED); - // } - // } - - // /** - // * 사용자의 Setting 정보를 기반으로 키워드에 해당하는 뉴스를 검색하여 반환합니다. - // * 뉴스는 히스토리에 저장되며, 최대 5개까지 템플릿으로 전송됩니다. - // * - // * @param userId 사용자 고유 ID - // * @return 뉴스 리스트 {@code List}, 키워드가 없거나 오류 시 {@code null} - // */ - // private List getNewsEsDocumentList(Long userId) { - // - // //유저 정보를 기준으로 Settig값 가져오기 - // List settings = settingService.getAllSettingsByUserId(userId); - // - // List keywords = new ArrayList<>(); - // List blockKeywords = new ArrayList<>(); - // - // for (SettingDTO setting : settings) { - // log.info("셋팅값 확인용 코드 : " + setting.getSettingKeywords()); - // log.info("셋팅 제외 확인용 코드 : " + setting.getBlockKeywords()); - // - // // 키워드리스트의 null 값 체크 - // if (setting.getSettingKeywords() != null) { - // keywords.add(setting.getSettingKeywords().toString()); - // } - // - // blockKeywords.add(setting.getBlockKeywords().toString()); - // } - // - // if (keywords.isEmpty()) { - // log.error("설정된 키워드가 없습니다."); - // throw new KakaoException(ErrorCode.SETTING_NOT_FOUND); - // } - // - // //키워드별 뉴스 검색 - // List newsList = newsService.searchNews(keywords, - // blockKeywords); - // - // log.info("검색된 뉴스 수: {}", newsList.size()); - // newsList.forEach(n -> log.info("뉴스: {} - {}", n.getPublisher(), - // n.getSummary())); - // - // // 검색된 뉴스를 히스토리로 보내는 코드 - // if (saveHistory(newsList, settings)) return null; - // return newsList; - // } - } \ No newline at end of file diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoNewsService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoNewsService.java index bdbd96f..757a068 100644 --- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoNewsService.java +++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoNewsService.java @@ -2,6 +2,7 @@ import Baemin.News_Deliver.Global.News.ElasticSearch.dto.NewsEsDocument; import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.SortOrder; import co.elastic.clients.elasticsearch._types.query_dsl.Query; import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType; import co.elastic.clients.elasticsearch.core.SearchRequest; @@ -26,17 +27,51 @@ public class KakaoNewsService { private final ElasticsearchClient client; /** - * Hot Fixed + * 뉴스가 5개 미만일 경우 fallback 로직 적용하는 메서드 * + * @param includeKeywords 포함 키워드 + * @param blockKeywords 제외 키워드 + * @return 뉴스 리스트 + */ + public List searchNewsWithFallback(List includeKeywords, List blockKeywords) { + List result = searchNewsByDateRange(includeKeywords, blockKeywords, 1); // 어제 기준 + + if (result.size() < 5) { + log.info("⚠뉴스 부족 → 최근 7일간으로 fallback"); + result = searchNewsByDateRange(includeKeywords, blockKeywords, 7); // fallback + } + + // 최대 5개 제한 + return result.size() > 5 ? result.subList(0, 5) : result; + } + + /** + * 어제의 뉴스만 검색하는 메서드 * * @param includeKeywords 포함 키워드 * @param blockKeywords 제외 키워드 * @return 뉴스 리스트 */ public List searchNews(List includeKeywords, List blockKeywords) { + + // 어제의 뉴스만 검색하기에 { 검색 기간 : 1 }로 설정 + return searchNewsByDateRange(includeKeywords, blockKeywords, 1); + } + + /** + * 날짜 범위를 받는 뉴스 검색 메서드 + * + * @param includeKeywords 포함 키워드 + * @param blockKeywords 제외 키워드 + * @param fromDaysAgo 검색할 기간 + * @return 뉴스 리스트 + */ + public List searchNewsByDateRange(List includeKeywords, List blockKeywords, int fromDaysAgo) { try { - LocalDate yesterday = LocalDate.now().minusDays(1); + LocalDate now = LocalDate.now(); + LocalDate fromDate = now.minusDays(fromDaysAgo); + // 포함 키워드 쿼리 Query includeKeywordQuery = Query.of(q -> q .bool(b -> b .should(includeKeywords.stream() @@ -53,21 +88,21 @@ public List searchNews(List includeKeywords, List q .range(r -> r .field("published_at") - .gte(JsonData.of(yesterday.toString())) - .lte(JsonData.of(yesterday.toString())) + .gte(JsonData.of(fromDate.toString())) + .lte(JsonData.of(now.toString())) .format("yyyy-MM-dd") ) ); - // 쿼리 리스트 조합 List mustQueries = new ArrayList<>(); mustQueries.add(includeKeywordQuery); mustQueries.add(dateFilter); - // 제외 키워드가 있는 경우에만 must_not 추가 + // 제외 키워드 처리 Query finalQuery; if (blockKeywords != null && !blockKeywords.isEmpty()) { Query excludeKeywordQuery = Query.of(q -> q @@ -99,12 +134,13 @@ public List searchNews(List includeKeywords, List s .index("news-index-nori") .query(finalQuery) .size(5) .sort(sort -> sort - .score(sc -> sc.order(co.elastic.clients.elasticsearch._types.SortOrder.Desc)) + .score(sc -> sc.order(SortOrder.Desc)) ) ); @@ -124,22 +160,33 @@ public List searchNews(List includeKeywords, List searchNews(List includeKeywords, List blockKeywords) { // try { -// -// /* 전날 기준으로 시간을 측정 */ // LocalDate yesterday = LocalDate.now().minusDays(1); // -// /* 포함 키워드 쿼리 */ // Query includeKeywordQuery = Query.of(q -> q // .bool(b -> b // .should(includeKeywords.stream() @@ -156,23 +203,6 @@ public List searchNews(List includeKeywords, List q -// .bool(b -> b -// .should(blockKeywords.stream() -// .map(kw -> Query.of(q2 -> q2 -// .multiMatch(m -> m -// .query(kw) -// .fields("title", "summary", "content_url", "publisher") -// .type(TextQueryType.BoolPrefix) -// ) -// )) -// .collect(Collectors.toList()) -// ) -// ) -// ); -// -// /* 날짜 필터 쿼리 */ // Query dateFilter = Query.of(q -> q // .range(r -> r // .field("published_at") @@ -182,16 +212,43 @@ public List searchNews(List includeKeywords, List q -// .bool(b -> b -// .must(includeKeywordQuery) -// .must(dateFilter) -// .mustNot(excludeKeywordQuery) -// ) -// ); +// // 쿼리 리스트 조합 +// List mustQueries = new ArrayList<>(); +// mustQueries.add(includeKeywordQuery); +// mustQueries.add(dateFilter); +// +// // 제외 키워드가 있는 경우에만 must_not 추가 +// Query finalQuery; +// if (blockKeywords != null && !blockKeywords.isEmpty()) { +// Query excludeKeywordQuery = Query.of(q -> q +// .bool(b -> b +// .should(blockKeywords.stream() +// .map(kw -> Query.of(q2 -> q2 +// .multiMatch(m -> m +// .query(kw) +// .fields("title", "summary", "content_url", "publisher") +// .type(TextQueryType.BoolPrefix) +// ) +// )) +// .collect(Collectors.toList()) +// ) +// ) +// ); +// +// finalQuery = Query.of(q -> q +// .bool(b -> b +// .must(mustQueries) +// .mustNot(excludeKeywordQuery) +// ) +// ); +// } else { +// finalQuery = Query.of(q -> q +// .bool(b -> b +// .must(mustQueries) +// ) +// ); +// } // -// /* 검색 요청 */ // SearchRequest request = SearchRequest.of(s -> s // .index("news-index-nori") // .query(finalQuery) @@ -201,15 +258,12 @@ public List searchNews(List includeKeywords, List response = client.search(request, NewsEsDocument.class); // -// /* 로그: 스코어 확인 */ // response.hits().hits().forEach(hit -> // log.info("{} | score: {}", hit.source().getTitle(), hit.score()) // ); // -// /* 결과 반환 */ // return response.hits().hits().stream() // .map(hit -> hit.source()) // .collect(Collectors.toList()); @@ -219,4 +273,5 @@ public List searchNews(List includeKeywords, List new KakaoException(ErrorCode.SETTING_NOT_FOUND)); - // Fetch Join으로 days 미리 가져와서 LazyInitializationException 방지 setting = settingRepository.findByIdWithDays(settingId) .orElseThrow(() -> new KakaoException(ErrorCode.SETTING_NOT_FOUND)); @@ -109,31 +105,13 @@ public void scheduleUser(Setting setting) { try { - /** - * - * Hot Fix : Setting Keyword Feth Join 문제 해결 시도 - * - * - */ Optional optionalSetting = settingRepository.findByIdWithDays(settingId); - // Optional optionalSetting = - // settingRepository.findByIdWithAllAssociations(settingId); //안됨됨 - // Optional optionalSetting = settingRepository.findById(settingId); if (optionalSetting.isEmpty()) { log.warn("[Scheduler] 유저 {} / setting {} 설정 정보가 없음", userId, settingId); throw new KakaoException(ErrorCode.SETTING_NOT_FOUND); } - Setting settings = optionalSetting.get(); - - // blockKeywords, keywords가 꼭 필요하다면 강제 초기화 - // Hibernate.initialize(settings.getBlockKeywords()); - // Hibernate.initialize(settings.getKeywords()); - - // log.info("[Scheduler] 유저 {} / setting {} 키워드: {}, 제외 키워드: {}", - // userId, settingId, settings.getKeywords(), settings.getBlockKeywords()); - kakaoMessageService.sendKakaoMessage(refreshAccessToken, userId); } catch (Exception e) {