From 1950557a2e3ffe824a967f82e90ee219d7cf5ae7 Mon Sep 17 00:00:00 2001 From: PicturePark1101 Date: Sat, 12 Jul 2025 23:53:15 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat=20:=20=EC=A1=B0=ED=9A=8C=EC=88=98=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20#434?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/th/juinjang/JuinjangApplication.java | 2 +- .../service/ViewCountSyncScheduler.java | 76 ------------------- 2 files changed, 1 insertion(+), 77 deletions(-) delete mode 100644 src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountSyncScheduler.java diff --git a/src/main/java/umc/th/juinjang/JuinjangApplication.java b/src/main/java/umc/th/juinjang/JuinjangApplication.java index e1fccace..848e5fcd 100644 --- a/src/main/java/umc/th/juinjang/JuinjangApplication.java +++ b/src/main/java/umc/th/juinjang/JuinjangApplication.java @@ -9,7 +9,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication -@EnableAsync +// @EnableAsync @ImportAutoConfiguration({FeignAutoConfiguration.class}) @EnableScheduling @EnableRetry diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountSyncScheduler.java b/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountSyncScheduler.java deleted file mode 100644 index 3f126ac2..00000000 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountSyncScheduler.java +++ /dev/null @@ -1,76 +0,0 @@ -package umc.th.juinjang.api.note.shared.service; - -import static umc.th.juinjang.common.redis.RedisKeyFactory.*; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RequiredArgsConstructor -@Component -public class ViewCountSyncScheduler { - - private final RedisTemplate redisTemplate; - private final SharedNoteUpdater sharedNoteUpdater; - private final SharedNoteFinder sharedNoteFinder; - - @Scheduled(cron = "0 0 */6 * * *") - @Transactional - public void syncRedisViewCountsToRDB() { - Set keys = redisTemplate.keys(VIEW_COUNT + "*"); - if (keys == null || keys.isEmpty()) - return; - - List sharedNoteIds = getSharedNoteIdsInRedis(keys); - Map dbViewCounts = sharedNoteFinder.findAllIdAndViewCountById(sharedNoteIds); - - for (String key : keys) { - try { - long sharedNoteId = Long.parseLong(key.split(":")[2]); - Object value = redisTemplate.opsForValue().get(key); - if (value == null) { - log.warn("조회수 값 없음 - key={}", key); - continue; - } - - long redisViewCount = Long.parseLong(value.toString()); - long dbViewCount = dbViewCounts.getOrDefault(sharedNoteId, 0L); - - if (redisViewCount > dbViewCount) { - sharedNoteUpdater.updateViewCount(sharedNoteId, redisViewCount); - log.info("조회수 동기화: sharedNoteId={}, Redis={}, DB={}", sharedNoteId, redisViewCount, - dbViewCount); - } else { - log.info("동기화 생략: sharedNoteId={}, Redis={}, DB={}", sharedNoteId, redisViewCount, - dbViewCount); - } - } catch (Exception e) { - log.error("동기화 실패: key={}, error={}", key, e.getMessage(), e); - } - } - } - - private List getSharedNoteIdsInRedis(Set keys) { - return keys.stream() - .map(k -> { - try { - return Long.parseLong(k.split(":")[2]); - } catch (Exception e) { - log.warn("잘못된 키 형식: {}", k); - return null; - } - }) - .filter(Objects::nonNull) - .toList(); - } -} From 82baf4bc523bf43a49863c6f77984637b1f48be6 Mon Sep 17 00:00:00 2001 From: PicturePark1101 Date: Sat, 12 Jul 2025 23:53:58 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix=20:=20error=20fix=20-=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=88=98=20=EC=A7=80=EA=B8=89=EC=8B=9C=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EC=83=88=EB=A1=9C=20=EC=97=B4?= =?UTF-8?q?=EB=A6=AC=EB=8F=84=EB=A1=9D=20=EA=B5=AC=EC=84=B1=20#434?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/th/juinjang/api/reward/service/RewardService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java b/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java index ae20e173..a11c8f3e 100644 --- a/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java +++ b/src/main/java/umc/th/juinjang/api/reward/service/RewardService.java @@ -1,9 +1,11 @@ package umc.th.juinjang.api.reward.service; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import umc.th.juinjang.api.pencil.service.AcquiredPencilUpdater; import umc.th.juinjang.api.pencilAccount.service.PencilAccountFinder; import umc.th.juinjang.domain.member.model.Member; @@ -16,6 +18,7 @@ @Component @RequiredArgsConstructor +@Slf4j public class RewardService { private final AcquiredPencilUpdater acquiredPencilUpdater; @@ -24,7 +27,7 @@ public class RewardService { private final RewardFinder rewardFinder; private final RewardUpdater rewardUpdater; - @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) public void giveViewCountReward(Member member, Long sharedNoteId, Long milestone, Long rewardPencil) { if (alreadyViewCountRewardEarned(RewardType.VIEWCOUNT, sharedNoteId, milestone)) { @@ -37,6 +40,9 @@ public void giveViewCountReward(Member member, Long sharedNoteId, Long milestone acquiredPencilUpdater.save( createAcquiredPencil(member, sharedNoteId, milestone, rewardPencil)); rewardUpdater.save(createReward(member, sharedNoteId, milestone, rewardPencil)); + + log.info("유저에게 조회수 리워드 지급 완료: memberId={}, sharedNoteId={}, milestone={}, rewardPencil={}", + member.getMemberId(), sharedNoteId, milestone, rewardPencil); } private AcquiredPencil createAcquiredPencil(Member member, Long sharedNoteId, Long milestone, Long rewardPencil) { From e213dbf6bbec053b61a5a2edcc967e9f615f9f9c Mon Sep 17 00:00:00 2001 From: PicturePark1101 Date: Sat, 12 Jul 2025 23:55:22 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat=20:=20=EC=A1=B0=ED=9A=8C=EC=88=98=20?= =?UTF-8?q?=EC=9D=BD=EC=96=B4=EC=98=A4=EB=8A=94=20=EA=B2=83=20RDB=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=ED=96=89=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20#434?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SharedNoteQueryService.java | 21 ++------- .../note/shared/service/ViewCountService.java | 43 ++++++------------- .../domain/note/shared/model/SharedNote.java | 5 ++- .../RewardViewCountEventListener.java | 5 +-- 4 files changed, 23 insertions(+), 51 deletions(-) diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteQueryService.java b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteQueryService.java index 2fc918ff..9f380a85 100644 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteQueryService.java +++ b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteQueryService.java @@ -39,7 +39,6 @@ import umc.th.juinjang.domain.note.shared.model.SharedNote; import umc.th.juinjang.domain.pencil.used.model.UsedPencil; import umc.th.juinjang.domain.report.model.Report; -import umc.th.juinjang.event.publisher.ApplicationRewardViewCountPublisherAdapter; @Service @Slf4j @@ -51,17 +50,16 @@ public class SharedNoteQueryService { private final LikedNoteFinder likedNoteFinder; private final ChecklistAnswerFinder checklistAnswerFinder; private final ViewCountService viewCountService; - private final ApplicationRewardViewCountPublisherAdapter applicationRewardViewCountPublisherAdapter; private final ReportFinder reportFinder; - @Transactional(readOnly = true) + @Transactional public SharedNoteGetResponse findSharedNote(Member member, Long sharedNoteId) { SharedNote sharedNote = sharedNoteFinder.findByIdWithNoteAndAddress(sharedNoteId); Limjang limjang = sharedNote.getLimjang(); boolean isBuyerOrOwner = getIsBuyerOrOwner(member, sharedNote); - long viewCount = getViewCountAndCheckReward(member, sharedNoteId, sharedNote); + long viewCount = viewCountService.getViewCount(member, sharedNote); Integer countBuyer = makeBuyerCount(usedPencilFinder.countBySharedNoteId(sharedNoteId)); boolean isLiked = likedNoteFinder.existsByMemberAndSharedNote(member, sharedNote); @@ -80,19 +78,6 @@ private boolean getIsBuyerOrOwner(Member requestMember, SharedNote sharedNote) { sharedNote.getMember().getMemberId().equals(requestMember.getMemberId()); } - private long getViewCountAndCheckReward(Member member, Long sharedNoteId, SharedNote sharedNote) { - long viewCount = viewCountService.getRedisViewCount(sharedNote.getSharedNoteId()); - if (!viewCountService.isDuplicate(member.getMemberId(), sharedNoteId)) { - viewCountService.increaseViewCount(sharedNoteId); - viewCount++; - viewCountService.recordViewerHistory(member.getMemberId(), sharedNoteId); - - applicationRewardViewCountPublisherAdapter.checkViewCountRewardPolicy(sharedNote.getMember(), - sharedNote.getSharedNoteId(), viewCount); - } - return viewCount; - } - private Integer makeBuyerCount(int count) { if (count >= 100) { return 100; @@ -128,7 +113,7 @@ public SharedNoteExploreGetResponse findExploreSharedNote(Member member, List mapIdsAndViewcount(List sharedNotes) { return sharedNotes.stream().collect(Collectors.toMap( SharedNote::getSharedNoteId, - it -> viewCountService.getRedisViewCount(it.getSharedNoteId()) + SharedNote::getViewCount )); } diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java b/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java index 80238f67..1ce6d582 100644 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java +++ b/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java @@ -10,6 +10,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import umc.th.juinjang.common.redis.RedisKeyFactory; +import umc.th.juinjang.domain.member.model.Member; +import umc.th.juinjang.domain.note.shared.model.SharedNote; +import umc.th.juinjang.event.publisher.ApplicationRewardViewCountPublisherAdapter; @Component @RequiredArgsConstructor @@ -18,6 +21,7 @@ public class ViewCountService { private final RedisTemplate redisTemplate; private final SharedNoteFinder sharedNoteFinder; + private final ApplicationRewardViewCountPublisherAdapter applicationRewardViewCountPublisherAdapter; public void recordViewerHistory(long memberId, long sharedNoteId) { try { @@ -28,14 +32,6 @@ public void recordViewerHistory(long memberId, long sharedNoteId) { } } - public void increaseViewCount(long sharedNoteId) { - try { - redisTemplate.opsForValue().increment(RedisKeyFactory.viewCountKey(sharedNoteId)); - } catch (RedisConnectionFailureException | RedisSystemException e) { - log.error("Redis 조회수 증가 실패, sharedNoteId={}", sharedNoteId, e); - } - } - public boolean isDuplicate(long memberId, long sharedNoteId) { try { return Boolean.TRUE.equals(redisTemplate.hasKey(RedisKeyFactory.viewHistoryKey(sharedNoteId, memberId))); @@ -45,29 +41,18 @@ public boolean isDuplicate(long memberId, long sharedNoteId) { } } - public Long getRedisViewCount(long sharedNoteId) { + public long getViewCount(Member member, SharedNote sharedNote) { + long sharedNoteId = sharedNote.getSharedNoteId(); + long viewCount = sharedNoteFinder.findViewCountById(sharedNoteId); - String key = RedisKeyFactory.viewCountKey(sharedNoteId); + if (!isDuplicate(member.getMemberId(), sharedNoteId) && sharedNote.getDeletedAt() == null) { + sharedNote.increaseViewCount(); + viewCount++; + recordViewerHistory(member.getMemberId(), sharedNoteId); - try { - Object value = redisTemplate.opsForValue().get(key); - - if (value == null) { - Long viewCountFromDb = sharedNoteFinder.findViewCountById(sharedNoteId); - - if (viewCountFromDb == null) { - log.warn("DB의 sharedNote 조회수가 null sharedNoteId={}", sharedNoteId); - viewCountFromDb = 0L; - } - - redisTemplate.opsForValue().set(key, viewCountFromDb.toString()); - return viewCountFromDb; - } - - return Long.parseLong(value.toString()); - } catch (RedisConnectionFailureException | RedisSystemException e) { - log.error("Redis 장애 발생, 기본값 반환 sharedNoteID={}", sharedNoteId, e); - return 0L; + applicationRewardViewCountPublisherAdapter.checkViewCountRewardPolicy(sharedNote.getMember(), + sharedNote.getSharedNoteId(), viewCount); } + return viewCount; } } diff --git a/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java b/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java index 920f07f6..53b84b03 100644 --- a/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java +++ b/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java @@ -73,6 +73,10 @@ public Long increaseLikedCount() { return this.likeCount = (likeCount == null ? 1L : likeCount + 1); } + public void increaseViewCount() { + this.viewCount++; + } + public static SharedNote toSharedNote(Member member, Limjang limjang, SharedNotePostRequest dto, Long price) { return SharedNote.builder() .member(member) @@ -97,7 +101,6 @@ public void updateDeletedAt(Timestamp deletedAt) { this.deletedAt = deletedAt; } - // 23년 12월 초반 임장 public String getPullPeriod() { String shortYear = String.valueOf(this.year).substring(2); return shortYear + "년 " + this.month + "월 " + this.period + " 임장"; diff --git a/src/main/java/umc/th/juinjang/event/subscriber/RewardViewCountEventListener.java b/src/main/java/umc/th/juinjang/event/subscriber/RewardViewCountEventListener.java index 40342e99..be91db3f 100644 --- a/src/main/java/umc/th/juinjang/event/subscriber/RewardViewCountEventListener.java +++ b/src/main/java/umc/th/juinjang/event/subscriber/RewardViewCountEventListener.java @@ -19,12 +19,11 @@ public class RewardViewCountEventListener { private final ViewCountPolicy viewCountPolicy; private final RewardService rewardService; - @Async @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + @Async public void handleRewardViewCountEvent(RewardViewCountEvent rewardViewCountEvent) { - Long reward = viewCountPolicy.getRewardForExactMilestone( - rewardViewCountEvent.viewCount()); + Long reward = viewCountPolicy.getRewardForExactMilestone(rewardViewCountEvent.viewCount()); if (reward == null) { return; From 0f7cbb39016f654c927f7f401fd4497e88d28452 Mon Sep 17 00:00:00 2001 From: PicturePark1101 Date: Sun, 13 Jul 2025 00:16:28 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat=20:=20=EC=A1=B0=ED=9A=8C=EC=88=98=20?= =?UTF-8?q?=EC=A6=9D=EA=B0=80=20jpql=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20#434?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../juinjang/api/note/shared/service/SharedNoteUpdater.java | 4 ++-- .../th/juinjang/api/note/shared/service/ViewCountService.java | 3 ++- .../domain/note/shared/repository/SharedNoteRepository.java | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteUpdater.java b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteUpdater.java index 53d014f4..461fe797 100644 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteUpdater.java +++ b/src/main/java/umc/th/juinjang/api/note/shared/service/SharedNoteUpdater.java @@ -12,8 +12,8 @@ public class SharedNoteUpdater { private final SharedNoteRepository sharedNoteRepository; - void updateViewCount(long sharedNoteId, long addAmount) { - sharedNoteRepository.incrementViewCount(sharedNoteId, addAmount); + public void updateViewCount(long sharedNoteId) { + sharedNoteRepository.incrementViewCount(sharedNoteId); } public void incrementLikedCountById(Long sharedNoteId) { diff --git a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java b/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java index 1ce6d582..1bd744a6 100644 --- a/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java +++ b/src/main/java/umc/th/juinjang/api/note/shared/service/ViewCountService.java @@ -21,6 +21,7 @@ public class ViewCountService { private final RedisTemplate redisTemplate; private final SharedNoteFinder sharedNoteFinder; + private final SharedNoteUpdater sharedNoteUpdater; private final ApplicationRewardViewCountPublisherAdapter applicationRewardViewCountPublisherAdapter; public void recordViewerHistory(long memberId, long sharedNoteId) { @@ -46,7 +47,7 @@ public long getViewCount(Member member, SharedNote sharedNote) { long viewCount = sharedNoteFinder.findViewCountById(sharedNoteId); if (!isDuplicate(member.getMemberId(), sharedNoteId) && sharedNote.getDeletedAt() == null) { - sharedNote.increaseViewCount(); + sharedNoteUpdater.updateViewCount(sharedNoteId); viewCount++; recordViewerHistory(member.getMemberId(), sharedNoteId); diff --git a/src/main/java/umc/th/juinjang/domain/note/shared/repository/SharedNoteRepository.java b/src/main/java/umc/th/juinjang/domain/note/shared/repository/SharedNoteRepository.java index 13d435e7..0ee87ced 100644 --- a/src/main/java/umc/th/juinjang/domain/note/shared/repository/SharedNoteRepository.java +++ b/src/main/java/umc/th/juinjang/domain/note/shared/repository/SharedNoteRepository.java @@ -19,8 +19,8 @@ public interface SharedNoteRepository extends JpaRepository, S Optional findByIdWithNoteAndAddress(@Param("sharedNoteId") Long sharedNoteId); @Modifying - @Query("UPDATE SharedNote s SET s.viewCount = :updateViewCount WHERE s.sharedNoteId = :sharedNoteId") - void incrementViewCount(@Param("sharedNoteId") Long sharedNoteId, @Param("updateViewCount") Long updateViewCount); + @Query("UPDATE SharedNote s SET s.viewCount = s.viewCount + 1 WHERE s.sharedNoteId = :sharedNoteId") + void incrementViewCount(@Param("sharedNoteId") Long sharedNoteId); @Modifying @Query("UPDATE SharedNote sn SET sn.likeCount = sn.likeCount + 1 WHERE sn.sharedNoteId = :sharedNoteId") From 7fefaced2710c385d5fcec00d67ecdfabd34d955 Mon Sep 17 00:00:00 2001 From: PicturePark1101 Date: Sun, 13 Jul 2025 00:19:07 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor=20:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?#434?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/th/juinjang/domain/note/shared/model/SharedNote.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java b/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java index 53b84b03..0761580c 100644 --- a/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java +++ b/src/main/java/umc/th/juinjang/domain/note/shared/model/SharedNote.java @@ -73,10 +73,6 @@ public Long increaseLikedCount() { return this.likeCount = (likeCount == null ? 1L : likeCount + 1); } - public void increaseViewCount() { - this.viewCount++; - } - public static SharedNote toSharedNote(Member member, Limjang limjang, SharedNotePostRequest dto, Long price) { return SharedNote.builder() .member(member)