diff --git a/' b/'
new file mode 100644
index 0000000..5da97ce
--- /dev/null
+++ b/'
@@ -0,0 +1,6 @@
+Merge branch 'dev' of https://github.com/News-Deliver/Server into feat/sub/more
+# Please enter a commit message to explain why this merge is necessary,
+# especially if it merges an updated upstream into a topic branch.
+#
+# Lines starting with '#' will be ignored, and an empty message aborts
+# the commit.
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 4557c82..5fc2a96 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -7,7 +7,7 @@
-
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 2a5ead6..78bb34d 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,8 +2,7 @@
-
-
+
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/HotTopic/service/HotTopicService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/HotTopic/service/HotTopicService.java
index c877b9c..16cde68 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/HotTopic/service/HotTopicService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/HotTopic/service/HotTopicService.java
@@ -71,7 +71,7 @@ public HotTopicService(
public List getHotTopicList() {
long start = System.nanoTime();
String cacheKey = "hottopic:daily";
-
+// 캐싱 때문에 발생하는 문제를 임시적으로 해결하기 위한 임시 주석 : 성열 7월 19일 토요일
List cached = (List) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
long end = System.nanoTime();
@@ -82,6 +82,8 @@ public List getHotTopicList() {
LocalDate yesterday = LocalDate.now().minusDays(1);
LocalDateTime startOfYesterday = yesterday.atStartOfDay();
LocalDateTime endOfYesterday = yesterday.atTime(LocalTime.MAX);
+ log.info("startOfYesterday : {}", startOfYesterday);
+ log.info("endOfYesterday : {}", endOfYesterday);
List result = hotTopicRepository.findTop10ByTopicDateBetweenOrderByTopicRankAsc(startOfYesterday, endOfYesterday)
.stream()
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 baa3046..0e4a476 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
@@ -81,7 +81,7 @@ public String getKakaoUserAccessToken(String refreshAccessToken, Long userId) {
throw new KakaoException(ErrorCode.KAKAO_TOKEN_ACCESS_FAILED);
}
- getNewsEsDocumentList(userId);
+ getNewsEsDocumentList_Fixed(userId);
return accessToken;
}
@@ -96,7 +96,7 @@ public boolean sendKakaoMessage(String refreshAccessToken, Long userId) {
try {
String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId);
- List newsList = getNewsEsDocumentList(userId);
+ List newsList = getNewsEsDocumentList_Fixed(userId);
//뉴스가 없을 때 반환할 값을 고민 해볼 것.
if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);;
@@ -130,47 +130,51 @@ public boolean sendKakaoMessage(String refreshAccessToken, Long userId) {
}
}
- /**
- * 사용자의 Setting 정보를 기반으로 키워드에 해당하는 뉴스를 검색하여 반환합니다.
- * 뉴스는 히스토리에 저장되며, 최대 5개까지 템플릿으로 전송됩니다.
+ /*
+ * 사용자의 키워드를 바탕으로 뉴스를 검색하여 리스트로 반환하는 메서드
*
- * @param userId 사용자 고유 ID
- * @return 뉴스 리스트 {@code List}, 키워드가 없거나 오류 시 {@code null}
+ * Edit By : 성열
+ * When : 2025-07-20
+ * Why : 히스토리 DB에 2개의 세팅이 합쳐져서 뉴스가 발송되는 오류 해결
+ *
+ * Deprecated된 메서드는 하단에 정리하였습니다.
+ *
+ * @param userId 유저의 고유 번호
+ * @return 각 세팅에 맞는 뉴스 기사 리스4트
*/
- private List getNewsEsDocumentList(Long userId) {
-
- //유저 정보를 기준으로 Settig값 가져오기
+ private List getNewsEsDocumentList_Fixed(Long userId) {
List settings = settingService.getAllSettingsByUserId(userId);
-
- List keywords = new ArrayList<>();
- List blockKeywords = new ArrayList<>();
+ List totalNewsList = new ArrayList<>();
for (SettingDTO setting : settings) {
log.info("셋팅값 확인용 코드 : " + setting.getSettingKeywords());
log.info("셋팅 제외 확인용 코드 : " + setting.getBlockKeywords());
- // 키워드리스트의 null 값 체크
- if (setting.getSettingKeywords() != null) {
- keywords.add(setting.getSettingKeywords().toString());
+ List keywords = setting.getSettingKeywords(); // 예: [이재명]
+ List blockKeywords = setting.getBlockKeywords(); // 예: [한국, 중국]
+
+ if (keywords == null || keywords.isEmpty()) {
+ log.warn("세팅에 키워드가 없습니다. 스킵합니다.");
+ continue;
}
- blockKeywords.add(setting.getBlockKeywords().toString());
- }
+ List newsList = newsService.searchNews(keywords, blockKeywords);
- if (keywords.isEmpty()) {
- log.error("설정된 키워드가 없습니다.");
- throw new KakaoException(ErrorCode.SETTING_NOT_FOUND);
- }
+ log.info(">> 세팅당 검색된 뉴스 수: {}", newsList.size());
- //키워드별 뉴스 검색
- List newsList = newsService.searchNews(keywords, blockKeywords);
+ // 세팅당 5개만 취하고 싶다면 limit 적용
+ if (newsList.size() > 5) {
+ newsList = newsList.subList(0, 5);
+ }
+
+ // 뉴스 히스토리 저장
+ saveHistory(newsList, List.of(setting)); // 단일 setting 기준
- log.info("검색된 뉴스 수: {}", newsList.size());
- newsList.forEach(n -> log.info("뉴스: {} - {}", n.getPublisher(), n.getSummary()));
+ totalNewsList.addAll(newsList);
+ }
- // 검색된 뉴스를 히스토리로 보내는 코드
- if (saveHistory(newsList, settings)) return null;
- return newsList;
+ log.info("✅ 전체 검색된 뉴스 총합: {}", totalNewsList.size());
+ return totalNewsList;
}
/**
@@ -251,4 +255,50 @@ private boolean saveHistory(List newsList, List sett
return saved;
}
+ // ======================= Deprecated =========================
+ /* 오류 발생 */
+
+ /**
+ * 사용자의 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/Mypage/Repository/SettingRepository.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/Repository/SettingRepository.java
index 2b67fb1..7e59765 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/Repository/SettingRepository.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/Repository/SettingRepository.java
@@ -48,4 +48,21 @@ public interface SettingRepository extends JpaRepository {
""")
Optional findByIdWithDays(@Param("id") Long id);
-}
\ No newline at end of file
+ /**
+ * Setting Service의 getAllSettings 메서드에 쓰일 메서드
+ * 현재 시간 기준으로 유효한 모든 세팅을 가져옴
+ * - 시작일이 현재보다 이전이거나 같고
+ * - 종료일이 없거나, 현재보다 이후이며
+ * - 삭제되지 않은 세팅만 조회
+ *
+ * @param now 현재 시간
+ * @return 유효한 모든 세팅 리스트
+ */
+ @Query("SELECT DISTINCT s FROM Setting s " +
+ "JOIN FETCH s.days d " +
+ "WHERE s.startDate <= :now " +
+ "AND (s.endDate IS NULL OR s.endDate >= :now) " +
+ "AND (s.isDeleted IS NULL OR s.isDeleted = false)")
+ List findAllValidSettingsWithDays(@Param("now") LocalDateTime now);
+
+}
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/service/SettingService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/service/SettingService.java
index 2905c8b..2766aed 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/service/SettingService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Mypage/service/SettingService.java
@@ -252,7 +252,23 @@ private SettingDTO convertToDTO(Setting setting) {
}
public List getAllSettings() {
- return settingRepository.findAll();
+
+ /**
+ * What : 다음님 오류 코드에 대한 대응
+ * How : 세팅값에 적용된 기간 + 요일을 가져옴
+ * Who : 류성열
+ * When : 2025-07-21
+ *
+ */
+ return settingRepository.findAllValidSettingsWithDays(LocalDateTime.now());
+
+ /**
+ * What :다음님 코드 오류
+ * Why : 삭제된 세팅 값까지 가져와서, 오류를 발생
+ * -> 삭제된 세팅은 기록은 존재하지만, 요일 값은 삭제 되어 NP 발생
+ *
+ */
+ //return settingRepository.findAll();
}
public Setting getById(Long settingId) {
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Controller/MoreNewsController.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Controller/MoreNewsController.java
index 0850e28..f772a09 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Controller/MoreNewsController.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Controller/MoreNewsController.java
@@ -62,8 +62,9 @@ public ResponseEntity>> getMoreNews(@Pat
@GetMapping("")
public ResponseEntity>> getNewsHistoryList(
@RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "5") int size
+ @RequestParam(defaultValue = "3") int size
) {
+ // 내 히스토리 조회하기 서비스 레이어 호출
List groupedList = moreNewsService.getGroupedNewsHistory(page, size);
return ResponseEntity.ok(new ApiResponseWrapper<>(groupedList, "히스토리가 성공적으로 조회되었습니다."));
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/DTO/NewsHistoryResponse.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/DTO/NewsHistoryResponse.java
index 6dc9e4a..c63c292 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/DTO/NewsHistoryResponse.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/DTO/NewsHistoryResponse.java
@@ -3,6 +3,7 @@
import Baemin.News_Deliver.Domain.Kakao.entity.History;
import Baemin.News_Deliver.Domain.Mypage.Entity.Setting;
+import Baemin.News_Deliver.Domain.SubServices.FeedBack.Entity.Feedback;
import lombok.*;
import java.time.LocalDateTime;
@@ -15,24 +16,20 @@
public class NewsHistoryResponse {
private Long id;
-// private String settingKeyword;
-// private String blockKeyword;
-// private LocalDateTime publishedAt;
private String newsTitle;
private String summary;
private String content_url;
- private Long setting_id;
+ private Long keywordReflection;
+ private Long contentQuality;
- public static NewsHistoryResponse from(History history) {
+ public static NewsHistoryResponse from(History history, Feedback feedback) {
return NewsHistoryResponse.builder()
.id(history.getId())
-// .settingKeyword(history.getSettingKeyword())
-// .blockKeyword(history.getBlockKeyword())
-// .publishedAt(history.getPublishedAt())
.newsTitle(history.getNews().getTitle()) // News 엔티티의 title
.summary(history.getNews().getSummary()) // News 엔티티의 summary
.content_url(history.getNews().getContentUrl()) // News 엔티티의 URL
- .setting_id(history.getSetting().getId())
+ .keywordReflection(feedback != null ? feedback.getKeywordReflection() : null)
+ .contentQuality(feedback != null ? feedback.getContentQuality() : null)
.build();
}
}
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Service/MoreNewsService.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Service/MoreNewsService.java
index ff59d35..f277146 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Service/MoreNewsService.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Domain/SubServices/MoreNews/Service/MoreNewsService.java
@@ -3,6 +3,8 @@
import Baemin.News_Deliver.Domain.Kakao.entity.History;
import Baemin.News_Deliver.Domain.Kakao.repository.HistoryRepository;
import Baemin.News_Deliver.Domain.SubServices.Exception.SubServicesException;
+import Baemin.News_Deliver.Domain.SubServices.FeedBack.Entity.Feedback;
+import Baemin.News_Deliver.Domain.SubServices.FeedBack.Repository.FeedbackRepository;
import Baemin.News_Deliver.Domain.SubServices.MoreNews.DTO.GroupedNewsHistoryResponse;
import Baemin.News_Deliver.Domain.SubServices.MoreNews.DTO.NewsHistoryResponse;
import Baemin.News_Deliver.Global.Exception.ErrorCode;
@@ -30,6 +32,7 @@
public class MoreNewsService {
private final HistoryRepository historyRepository;
+ private final FeedbackRepository feedbackRepository;
private final ElasticsearchClient client;
// ======================= 뉴스 추가 검색 메서드 =========================
@@ -161,9 +164,19 @@ public List getMoreNews(Long historyId) throws IOException {
public List getGroupedNewsHistory(int page, int size) {
Long userId = 1L;
+ // 1. 모든 히스토리 조회
List allHistories = historyRepository.findAllBySetting_User_Id(userId);
- /* 1. 그룹핑: settingId + publishedAt (시 단위로 자르기) */
+ // 2. 히스토리 ID 수집 → Feedback 일괄 조회
+ List historyIds = allHistories.stream()
+ .map(History::getId)
+ .collect(Collectors.toList());
+
+ Map feedbackMap = feedbackRepository.findAllById(historyIds)
+ .stream()
+ .collect(Collectors.toMap(fb -> fb.getHistory().getId(), fb -> fb));
+
+ // 3. 그룹핑: settingId + publishedAt(HOUR)
Map> grouped = allHistories.stream()
.collect(Collectors.groupingBy(h -> {
Long settingId = h.getSetting().getId();
@@ -171,27 +184,31 @@ public List getGroupedNewsHistory(int page, int size
return settingId + "_" + truncatedPublishedAt;
}));
- /* 2. GroupedNewsHistoryResponse 리스트로 변환 */
+ // 4. DTO 변환
List groupedList = grouped.entrySet().stream()
.map(entry -> {
List histories = entry.getValue();
- History any = histories.get(0); // 그룹 내 대표 뉴스
+ History any = histories.get(0);
+
+ List newsResponses = histories.stream()
+ .map(history -> {
+ Feedback feedback = feedbackMap.get(history.getId());
+ return NewsHistoryResponse.from(history, feedback);
+ })
+ .toList();
return GroupedNewsHistoryResponse.builder()
.settingId(any.getSetting().getId())
- .publishedAt(any.getPublishedAt().truncatedTo(ChronoUnit.HOURS)) // 대표 시간
- .settingKeyword(any.getSettingKeyword()) // 대표 키워드
- .blockKeyword(any.getBlockKeyword()) // 대표 제외 키워드
- .newsList(histories.stream()
- .map(NewsHistoryResponse::from)
- .collect(Collectors.toList()))
+ .publishedAt(any.getPublishedAt().truncatedTo(ChronoUnit.HOURS))
+ .settingKeyword(any.getSettingKeyword())
+ .blockKeyword(any.getBlockKeyword())
+ .newsList(newsResponses)
.build();
})
-
- .sorted(Comparator.comparing(GroupedNewsHistoryResponse::getPublishedAt).reversed()) // 최신순
+ .sorted(Comparator.comparing(GroupedNewsHistoryResponse::getPublishedAt).reversed())
.collect(Collectors.toList());
- /* 3. 페이지네이션 */
+ // 5. 페이지네이션
int fromIndex = page * size;
int toIndex = Math.min(fromIndex + size, groupedList.size());
@@ -202,6 +219,4 @@ public List getGroupedNewsHistory(int page, int size
return groupedList.subList(fromIndex, toIndex);
}
-
-
}
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/configuration/ElasticsearchClientConfig.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/configuration/ElasticsearchClientConfig.java
index 6178b43..0821db3 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/configuration/ElasticsearchClientConfig.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/configuration/ElasticsearchClientConfig.java
@@ -48,6 +48,8 @@ public ElasticsearchClient elasticsearchClient() {
// HTTP 기반 Low-level 클라이언트 구성
RestClient restClient = RestClient.builder(
new HttpHost("elasticsearch", 9200)
+ // 성열 로컬용 임시 Host
+ //new HttpHost("localhost", 9200)
).build();
// LocalDateTime 직렬화 지원 및 ISO 포맷 지정
diff --git a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/Scheduler/SchedulerInitializer.java b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/Scheduler/SchedulerInitializer.java
index f1c2dc9..ee36159 100644
--- a/SpringBoot/src/main/java/Baemin/News_Deliver/Global/Scheduler/SchedulerInitializer.java
+++ b/SpringBoot/src/main/java/Baemin/News_Deliver/Global/Scheduler/SchedulerInitializer.java
@@ -64,7 +64,7 @@ public void scheduleAllUserSettings() {
//DB에 settings값이 없을 때 스케쥴러 취소 코드
if (settings == null || settings.isEmpty()) {
log.warn("[SchedulerInit] 등록할 Setting이 없어 스케줄러를 실행하지 않습니다.");
- throw new KakaoException(ErrorCode.SETTING_NOT_FOUND);
+ // throw new KakaoException(ErrorCode.SETTING_NOT_FOUND);
}
for (Setting setting : settings) {
diff --git a/docker-compose.yml b/docker-compose.yml
index d18870a..a4a560c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -152,17 +152,17 @@ services:
volumes:
- grafana-data:/var/lib/grafana
-# mysqld-exporter:
-# image: prom/mysqld-exporter:latest
-# container_name: mysqld-exporter
-# environment:
-# - DATA_SOURCE_NAME=root:1234@(mysql:3306)/
-# ports:
-# - "9104:9104"
-# depends_on:
-# - mysql
-# networks:
-# - backend
+ # mysqld-exporter:
+ # image: prom/mysqld-exporter:latest
+ # container_name: mysqld-exporter
+ # environment:
+ # - DATA_SOURCE_NAME=root:1234@(mysql:3306)/
+ # ports:
+ # - "9104:9104"
+ # depends_on:
+ # - mysql
+ # networks:
+ # - backend
redis-exporter:
image: oliver006/redis_exporter