Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
6f477cc
Merge branch 'dev' of https://github.com/News-Deliver/Server into fea…
passionryu Jul 17, 2025
f5c983f
Merge branch 'dev' of https://github.com/News-Deliver/Server into fea…
passionryu Jul 17, 2025
0c9a89a
✏️ [κΈ°λŠ₯ μˆ˜μ •] : λ‚΄ νžˆμŠ€ν† λ¦¬ 쑰회 μ‹œ, ν”Όλ“œλ°±μ— μ‹«μ–΄μš”, μ’‹μ•„μš” 기둝 λ˜ν•œ ν•¨κ»˜ λ³΄λ‚΄κ²Œ API μˆ˜μ •
passionryu Jul 17, 2025
a60e69e
Merge branch 'dev' of https://github.com/News-Deliver/Server into fea…
passionryu Jul 18, 2025
64d4b83
Merge branch 'dev' of https://github.com/News-Deliver/Server into fea…
passionryu Jul 18, 2025
f0800b9
Merge branch 'dev' of https://github.com/News-Deliver/Server into fea…
passionryu Jul 18, 2025
bb06245
πŸ› [버그 μˆ˜μ •] : λ©”μ‹œμ§€ 전솑 1μ°¨ 버그 μˆ˜μ • μ™„λ£Œ - 2개 μ΄μƒμ˜ μ„ΈνŒ… 쑴재 μ‹œ, 섀정을 ν˜Όν•©ν•˜μ—¬ λ‰΄μŠ€λ₯Ό κ²€μƒ‰ν•˜λŠ” 문…
passionryu Jul 19, 2025
a64cfca
πŸ”€ 병합
passionryu Jul 21, 2025
3c29fa2
πŸ› [버그 μˆ˜μ •] : μ„ΈνŒ… 없을 μ‹œ, μ—λŸ¬ λ°˜ν™˜ μ½”λ“œ μ‚­μ œ
passionryu Jul 21, 2025
5ea1d9c
πŸ› [버그 μˆ˜μ •] : μ‚­μ œλœ μ„ΈνŒ…κ°’μ„ λΆˆλŸ¬μ˜€λŠ” 였λ₯˜ ν•΄κ²°
passionryu Jul 21, 2025
ed6b77c
πŸ› [버그 μˆ˜μ •] : μ„ΈνŒ…μ΄ 2개 이상 쑴재 ν•  μ‹œ, 데이터 검색 μ‹œ 2개의 μ„ΈνŒ… 값이 μ μš©λ˜μ–΄μ„œ "전솑 λ©”μ‹œμ§€" μ˜€μ—Όλ˜λŠ”β€¦
passionryu Jul 21, 2025
e3c30a4
[chore]
passionryu Jul 21, 2025
758891e
[chore]
passionryu Jul 21, 2025
dd7505b
[chore]
passionryu Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions '
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public HotTopicService(
public List<HotTopicResponseDTO> getHotTopicList() {
long start = System.nanoTime();
String cacheKey = "hottopic:daily";

// 캐싱 λ•Œλ¬Έμ— λ°œμƒν•˜λŠ” 문제λ₯Ό μž„μ‹œμ μœΌλ‘œ ν•΄κ²°ν•˜κΈ° μœ„ν•œ μž„μ‹œ 주석 : μ„±μ—΄ 7μ›” 19일 ν† μš”μΌ

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

μž„μ‹œ ν•΄κ²°μ±… λŒ€μ‹  κ·Όλ³Έ 원인 ν•΄κ²° ν•„μš”

μž„μ‹œ μ£Όμ„μœΌλ‘œ 캐싱 문제λ₯Ό ν•΄κ²°ν•˜λŠ” κ²ƒλ³΄λ‹€λŠ” κ·Όλ³Έ 원인을 νŒŒμ•…ν•˜κ³  μ μ ˆν•œ 해결책을 μ μš©ν•˜λŠ” 것이 μ’‹κ² μŠ΅λ‹ˆλ‹€. ν˜„μž¬ μ£Όμ„λ§ŒμœΌλ‘œλŠ” μ–΄λ–€ 캐싱 λ¬Έμ œκ°€ λ°œμƒν–ˆλŠ”μ§€ λͺ…ν™•ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 쑰치λ₯Ό ꢌμž₯ν•©λ‹ˆλ‹€:

  • 캐싱 문제의 ꡬ체적인 원인 뢄석
  • 이슈 νŠΈλž˜μ»€μ— ν•΄λ‹Ή 문제 등둝
  • μž„μ‹œ 해결책에 λŒ€ν•œ λͺ…ν™•ν•œ 제거 일정 수립

이 μž„μ‹œ 해결책을 μ μ ˆν•œ μˆ˜μ •μœΌλ‘œ κ΅μ²΄ν•˜λŠ” μ½”λ“œλ₯Ό μƒμ„±ν•΄λ“œλ¦΄κΉŒμš”?

πŸ€– Prompt for AI Agents
In
SpringBoot/src/main/java/Baemin/News_Deliver/Domain/HotTopic/service/HotTopicService.java
at line 74, there is a temporary comment disabling caching to avoid an issue.
Instead of leaving this temporary comment, investigate and identify the root
cause of the caching problem. Remove the temporary comment and implement a
proper fix for the caching issue. Additionally, document the problem and fix in
the issue tracker and establish a clear timeline for removing any temporary
workarounds.

List<HotTopicResponseDTO> cached = (List<HotTopicResponseDTO>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
long end = System.nanoTime();
Expand All @@ -82,6 +82,8 @@ public List<HotTopicResponseDTO> 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<HotTopicResponseDTO> result = hotTopicRepository.findTop10ByTopicDateBetweenOrderByTopicRankAsc(startOfYesterday, endOfYesterday)
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -96,7 +96,7 @@ public boolean sendKakaoMessage(String refreshAccessToken, Long userId) {
try {
String accessToken = getKakaoUserAccessToken(refreshAccessToken, userId);

List<NewsEsDocument> newsList = getNewsEsDocumentList(userId);
List<NewsEsDocument> newsList = getNewsEsDocumentList_Fixed(userId);

//λ‰΄μŠ€κ°€ 없을 λ•Œ λ°˜ν™˜ν•  값을 κ³ λ―Ό ν•΄λ³Ό 것.
if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

문법 였λ₯˜ μˆ˜μ • ν•„μš”

μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•Šκ³  μ„Έλ―Έμ½œλ‘ μ΄ μ€‘λ³΅λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

-if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);;
+if (newsList == null) throw new KakaoException(ErrorCode.NO_NEWS_DATA);
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (newsList == null) new KakaoException(ErrorCode.NO_NEWS_DATA);;
if (newsList == null) throw new KakaoException(ErrorCode.NO_NEWS_DATA);
πŸ€– Prompt for AI Agents
In
SpringBoot/src/main/java/Baemin/News_Deliver/Domain/Kakao/service/KakaoMessageService.java
at line 102, the code incorrectly creates a new KakaoException without throwing
it and has an extra semicolon. Fix this by adding the 'throw' keyword before
'new KakaoException(ErrorCode.NO_NEWS_DATA)' and remove the redundant semicolon
to properly throw the exception.

Expand Down Expand Up @@ -130,47 +130,51 @@ public boolean sendKakaoMessage(String refreshAccessToken, Long userId) {
}
}

/**
* μ‚¬μš©μžμ˜ Setting 정보λ₯Ό 기반으둜 ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” λ‰΄μŠ€λ₯Ό κ²€μƒ‰ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€.
* λ‰΄μŠ€λŠ” νžˆμŠ€ν† λ¦¬μ— μ €μž₯되며, μ΅œλŒ€ 5κ°œκΉŒμ§€ ν…œν”Œλ¦ΏμœΌλ‘œ μ „μ†‘λ©λ‹ˆλ‹€.
/*
* μ‚¬μš©μžμ˜ ν‚€μ›Œλ“œλ₯Ό λ°”νƒ•μœΌλ‘œ λ‰΄μŠ€λ₯Ό κ²€μƒ‰ν•˜μ—¬ 리슀트둜 λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ
*
* @param userId μ‚¬μš©μž 고유 ID
* @return λ‰΄μŠ€ 리슀트 {@code List<NewsEsDocument>}, ν‚€μ›Œλ“œκ°€ μ—†κ±°λ‚˜ 였λ₯˜ μ‹œ {@code null}
* Edit By : μ„±μ—΄
* When : 2025-07-20
* Why : νžˆμŠ€ν† λ¦¬ DB에 2개의 μ„ΈνŒ…μ΄ ν•©μ³μ Έμ„œ λ‰΄μŠ€κ°€ λ°œμ†‘λ˜λŠ” 였λ₯˜ ν•΄κ²°
*
* Deprecated된 λ©”μ„œλ“œλŠ” ν•˜λ‹¨μ— μ •λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
*
* @param userId μœ μ €μ˜ 고유 번호
* @return 각 μ„ΈνŒ…μ— λ§žλŠ” λ‰΄μŠ€ 기사 리슀4트
*/
private List<NewsEsDocument> getNewsEsDocumentList(Long userId) {

//μœ μ € 정보λ₯Ό κΈ°μ€€μœΌλ‘œ Settigκ°’ κ°€μ Έμ˜€κΈ°
private List<NewsEsDocument> getNewsEsDocumentList_Fixed(Long userId) {
List<SettingDTO> settings = settingService.getAllSettingsByUserId(userId);

List<String> keywords = new ArrayList<>();
List<String> blockKeywords = new ArrayList<>();
List<NewsEsDocument> 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<String> keywords = setting.getSettingKeywords(); // 예: [이재λͺ…]
List<String> blockKeywords = setting.getBlockKeywords(); // 예: [ν•œκ΅­, 쀑ꡭ]

if (keywords == null || keywords.isEmpty()) {
log.warn("μ„ΈνŒ…μ— ν‚€μ›Œλ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€. μŠ€ν‚΅ν•©λ‹ˆλ‹€.");
continue;
}

blockKeywords.add(setting.getBlockKeywords().toString());
}
List<NewsEsDocument> newsList = newsService.searchNews(keywords, blockKeywords);

if (keywords.isEmpty()) {
log.error("μ„€μ •λœ ν‚€μ›Œλ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€.");
throw new KakaoException(ErrorCode.SETTING_NOT_FOUND);
}
log.info(">> μ„ΈνŒ…λ‹Ή κ²€μƒ‰λœ λ‰΄μŠ€ 수: {}", newsList.size());

//ν‚€μ›Œλ“œλ³„ λ‰΄μŠ€ 검색
List<NewsEsDocument> 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;
}

/**
Expand Down Expand Up @@ -251,4 +255,50 @@ private boolean saveHistory(List<NewsEsDocument> newsList, List<SettingDTO> sett
return saved;
}

// ======================= Deprecated =========================
/* 였λ₯˜ λ°œμƒ */

/**
* μ‚¬μš©μžμ˜ Setting 정보λ₯Ό 기반으둜 ν‚€μ›Œλ“œμ— ν•΄λ‹Ήν•˜λŠ” λ‰΄μŠ€λ₯Ό κ²€μƒ‰ν•˜μ—¬ λ°˜ν™˜ν•©λ‹ˆλ‹€.
* λ‰΄μŠ€λŠ” νžˆμŠ€ν† λ¦¬μ— μ €μž₯되며, μ΅œλŒ€ 5κ°œκΉŒμ§€ ν…œν”Œλ¦ΏμœΌλ‘œ μ „μ†‘λ©λ‹ˆλ‹€.
*
* @param userId μ‚¬μš©μž 고유 ID
* @return λ‰΄μŠ€ 리슀트 {@code List<NewsEsDocument>}, ν‚€μ›Œλ“œκ°€ μ—†κ±°λ‚˜ 였λ₯˜ μ‹œ {@code null}
*/
// private List<NewsEsDocument> getNewsEsDocumentList(Long userId) {
//
// //μœ μ € 정보λ₯Ό κΈ°μ€€μœΌλ‘œ Settigκ°’ κ°€μ Έμ˜€κΈ°
// List<SettingDTO> settings = settingService.getAllSettingsByUserId(userId);
//
// List<String> keywords = new ArrayList<>();
// List<String> 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<NewsEsDocument> 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;
// }

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,21 @@ public interface SettingRepository extends JpaRepository<Setting, Long> {
""")
Optional<Setting> findByIdWithDays(@Param("id") Long id);

}
/**
* 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<Setting> findAllValidSettingsWithDays(@Param("now") LocalDateTime now);

}
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,23 @@ private SettingDTO convertToDTO(Setting setting) {
}

public List<Setting> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ public ResponseEntity<ApiResponseWrapper<List<NewsEsDocument>>> getMoreNews(@Pat
@GetMapping("")
public ResponseEntity<ApiResponseWrapper<List<GroupedNewsHistoryResponse>>> getNewsHistoryList(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size
@RequestParam(defaultValue = "3") int size
) {
// λ‚΄ νžˆμŠ€ν† λ¦¬ μ‘°νšŒν•˜κΈ° μ„œλΉ„μŠ€ λ ˆμ΄μ–΄ 호좜
List<GroupedNewsHistoryResponse> groupedList = moreNewsService.getGroupedNewsHistory(page, size);

return ResponseEntity.ok(new ApiResponseWrapper<>(groupedList, "νžˆμŠ€ν† λ¦¬κ°€ μ„±κ³΅μ μœΌλ‘œ μ‘°νšŒλ˜μ—ˆμŠ΅λ‹ˆλ‹€."));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -30,6 +32,7 @@
public class MoreNewsService {

private final HistoryRepository historyRepository;
private final FeedbackRepository feedbackRepository;
private final ElasticsearchClient client;

// ======================= λ‰΄μŠ€ μΆ”κ°€ 검색 λ©”μ„œλ“œ =========================
Expand Down Expand Up @@ -161,37 +164,51 @@ public List<NewsEsDocument> getMoreNews(Long historyId) throws IOException {
public List<GroupedNewsHistoryResponse> getGroupedNewsHistory(int page, int size) {
Long userId = 1L;

// 1. λͺ¨λ“  νžˆμŠ€ν† λ¦¬ 쑰회
List<History> allHistories = historyRepository.findAllBySetting_User_Id(userId);

/* 1. κ·Έλ£Ήν•‘: settingId + publishedAt (μ‹œ λ‹¨μœ„λ‘œ 자λ₯΄κΈ°) */
// 2. νžˆμŠ€ν† λ¦¬ ID μˆ˜μ§‘ β†’ Feedback 일괄 쑰회
List<Long> historyIds = allHistories.stream()
.map(History::getId)
.collect(Collectors.toList());

Map<Long, Feedback> feedbackMap = feedbackRepository.findAllById(historyIds)
.stream()
.collect(Collectors.toMap(fb -> fb.getHistory().getId(), fb -> fb));

// 3. κ·Έλ£Ήν•‘: settingId + publishedAt(HOUR)
Map<String, List<History>> grouped = allHistories.stream()
.collect(Collectors.groupingBy(h -> {
Long settingId = h.getSetting().getId();
LocalDateTime truncatedPublishedAt = h.getPublishedAt().truncatedTo(ChronoUnit.HOURS);
return settingId + "_" + truncatedPublishedAt;
}));

/* 2. GroupedNewsHistoryResponse 리슀트둜 λ³€ν™˜ */
// 4. DTO λ³€ν™˜
List<GroupedNewsHistoryResponse> groupedList = grouped.entrySet().stream()
.map(entry -> {
List<History> histories = entry.getValue();
History any = histories.get(0); // κ·Έλ£Ή λ‚΄ λŒ€ν‘œ λ‰΄μŠ€
History any = histories.get(0);

List<NewsHistoryResponse> 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());

Expand All @@ -202,6 +219,4 @@ public List<GroupedNewsHistoryResponse> getGroupedNewsHistory(int page, int size
return groupedList.subList(fromIndex, toIndex);
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public ElasticsearchClient elasticsearchClient() {
// HTTP 기반 Low-level ν΄λΌμ΄μ–ΈνŠΈ ꡬ성
RestClient restClient = RestClient.builder(
new HttpHost("elasticsearch", 9200)
// μ„±μ—΄ 둜컬용 μž„μ‹œ Host
//new HttpHost("localhost", 9200)
).build();
Comment on lines 50 to 53

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Verification agent

❓ Verification inconclusive

호슀트/포트 ν•˜λ“œμ½”λ”© λŒ€μ‹  ν™˜κ²½ μ„€μ •μœΌλ‘œ μ™ΈλΆ€ν™”ν•˜μ„Έμš”
개발(둜컬)β€§μš΄μ˜ ν™˜κ²½λ§ˆλ‹€ 주석을 켜고 λ„λŠ” 방식은 μ‹€μˆ˜ μœ„ν—˜μ΄ λ†’κ³  CI νŒŒμ΄ν”„λΌμΈμ—μ„œ μžλ™ν™”κ°€ μ–΄λ ΅μŠ΅λ‹ˆλ‹€. application-*.yml ν”„λ‘œνŒŒμΌμ΄λ‚˜ @Value μ£Όμž…μœΌλ‘œ 호슀트·포트λ₯Ό μ™ΈλΆ€ν™”ν•˜λ©΄ κΉ”λ”ν•˜κ²Œ ν•΄κ²°λ©λ‹ˆλ‹€.

μ˜ˆμ‹œ diff:

+@Value("${elasticsearch.host:elasticsearch}")
+private String esHost;
+
+@Value("${elasticsearch.port:9200}")
+private int esPort;
...
-RestClient restClient = RestClient.builder(
-        new HttpHost("elasticsearch", 9200)
-        // μ„±μ—΄ 둜컬용 μž„μ‹œ Host
-        //new HttpHost("localhost", 9200)
-).build();
+RestClient restClient = RestClient.builder(
+        new HttpHost(esHost, esPort)
+).build();

μ΄λ ‡κ²Œ ν•˜λ©΄ ν”„λ‘œνŒŒμΌλ³„ application-dev.yml, application-prod.ymlμ—μ„œ κ°’λ§Œ λ°”κΏ”μ£Όλ©΄ λ˜λ―€λ‘œ μ†ŒμŠ€ μ½”λ“œ 변경·컀밋 없이 ν™˜κ²½ μ „ν™˜μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.


호슀트/포트 ν•˜λ“œμ½”λ”©μ„ μ œκ±°ν•˜κ³  μ„€μ • 파일둜 μ™ΈλΆ€ν™”ν•˜μ„Έμš”

ν˜„μž¬ ElasticsearchClientConfig.java에 new HttpHost("elasticsearch", 9200)처럼 ν•˜λ“œμ½”λ”©λœ ν˜ΈμŠ€νŠΈμ™€ 포트λ₯Ό μ‚¬μš© μ€‘μž…λ‹ˆλ‹€. 개발(둜컬)·운영 ν™˜κ²½ μ „ν™˜ μ‹œλ§ˆλ‹€ 주석을 켜고 λ„λŠ” 방식은 였λ₯˜λ₯Ό μœ λ°œν•˜κΈ° 쉽고, CI/CD μžλ™ν™”μ—λ„ μ ν•©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 @Valueλ₯Ό μ΄μš©ν•΄ application-*.yml ν”„λ‘œνŒŒμΌμ—μ„œ 값을 μ£Όμž… 받도둝 λ³€κ²½ν•˜μ„Έμš”:

// 클래슀 ν•„λ“œμ— μΆ”κ°€
+    @Value("${elasticsearch.host:elasticsearch}")
+    private String esHost;
+
+    @Value("${elasticsearch.port:9200}")
+    private int esPort;

    @Bean
    public RestClient elasticsearchClient() {
-       RestClient restClient = RestClient.builder(
-           new HttpHost("elasticsearch", 9200)
-           // μ„±μ—΄ 둜컬용 μž„μ‹œ Host
-           //new HttpHost("localhost", 9200)
-       ).build();
+       RestClient restClient = RestClient.builder(
+           new HttpHost(esHost, esPort)
+       ).build();
        return restClient;
    }

– application-dev.yml, application-prod.yml λ“± ν”„λ‘œνŒŒμΌλ³„ μ„€μ • νŒŒμΌμ— elasticsearch.host와 elasticsearch.port만 μ •μ˜ν•˜λ©΄ μ½”λ“œ λ³€κ²½ 없이 ν™˜κ²½ μ „ν™˜μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.
– CI/CD νŒŒμ΄ν”„λΌμΈμ—μ„œλ„ 별도 슀크립트 μˆ˜μ • 없이 μžλ™ 배포가 μš©μ΄ν•΄μ§‘λ‹ˆλ‹€.

πŸ€– Prompt for AI Agents
In
SpringBoot/src/main/java/Baemin/News_Deliver/Global/News/ElasticSearch/configuration/ElasticsearchClientConfig.java
around lines 50 to 53, the Elasticsearch host and port are hardcoded, which is
error-prone and not suitable for environment switching or CI/CD automation.
Refactor the code to use @Value annotations to inject the host and port values
from the application-*.yml configuration files (e.g., application-dev.yml,
application-prod.yml). Define elasticsearch.host and elasticsearch.port in these
profile-specific YAML files so the code reads these values dynamically without
needing manual changes for different environments.


// LocalDateTime 직렬화 지원 및 ISO 포맷 μ§€μ •
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading