From 73f423baa65e29e36f4a115e44d58b0303f02f11 Mon Sep 17 00:00:00 2001 From: Willy Date: Fri, 2 May 2025 19:08:59 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[CHORE]=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=B6=94=EC=A0=81=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../writer/NotificationWriterImpl.java | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java b/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java index e3bb3d8..179b210 100644 --- a/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java +++ b/src/main/java/org/terning/notification/application/writer/NotificationWriterImpl.java @@ -1,6 +1,7 @@ package org.terning.notification.application.writer; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.terning.message.domain.Message; @@ -20,6 +21,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class NotificationWriterImpl implements NotificationWriter { private static final String USERNAME = "username"; @@ -31,24 +33,72 @@ public class NotificationWriterImpl implements NotificationWriter { @Override @Transactional public void write(MessageTemplateType template, ScheduledTime sendTime) { + log.info("πŸ”” [NotificationWriter] μ•Œλ¦Ό 생성 μ‹œμž‘ - ν…œν”Œλ¦Ώ: {}", template); List targetUsers = fetchTargetUsers(template.targetType()); + log.info("πŸ‘₯ [NotificationWriter] λŒ€μƒ μœ μ € 수 (ν‘Έμ‹œ μˆ˜μ‹  ν—ˆμš© + 토큰 보유): {}", targetUsers.size()); +// List filteredUsers = targetUsers.stream() +// .filter(user -> user.getToken() != null) +// .filter(user -> user.getToken().value() != null) +// .filter(user -> !user.getToken().value().trim().isEmpty()) +// .toList(); List filteredUsers = targetUsers.stream() - .filter(user -> user.getToken() != null) - .filter(user -> user.getToken().value() != null) - .filter(user -> !user.getToken().value().trim().isEmpty()) + .filter(user -> { + boolean valid = user.getToken() != null && user.getToken().value() != null && !user.getToken().value().trim().isEmpty(); + if (!valid) { + log.warn("⚠️ [NotificationWriter] FCM 토큰 λˆ„λ½ - user(oUserId={}, name={})", user.getOUserId(), user.getName() != null ? user.getName().value() : "null"); + } + return valid; + }) .toList(); + log.info("βœ… [NotificationWriter] μ΅œμ’… μ•Œλ¦Ό λŒ€μƒ μœ μ € 수: {}", filteredUsers.size()); + List notifications = createNotifications(filteredUsers, template, sendTime); + log.info("πŸ’Ύ [NotificationWriter] μƒμ„±λœ μ•Œλ¦Ό 수: {}", notifications.size()); + saveNotifications(notifications); + log.info("πŸŽ‰ [NotificationWriter] μ•Œλ¦Ό μ €μž₯ μ™„λ£Œ"); } private List fetchTargetUsers(MessageTargetType targetType) { - List users = switch (targetType) { - case SCRAPPED_USER -> scrapRepository.findDistinctScrappedUsers(); - case ALL_USERS -> userRepository.findAll(); - default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); - }; + log.info("πŸ” [fetchTargetUsers] λŒ€μƒ νƒ€μž…: {}", targetType); +// List users = switch (targetType) { +// case SCRAPPED_USER -> scrapRepository.findDistinctScrappedUsers(); +// case ALL_USERS -> userRepository.findAll(); +// default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); +// }; +// return users.stream() +// .filter(User::canReceivePushNotification) +// .toList(); + List users; + + try { + users = switch (targetType) { + case SCRAPPED_USER -> { + List scrappedUsers = scrapRepository.findDistinctScrappedUsers(); + log.info("πŸ“¦ [fetchTargetUsers] 슀크랩 μœ μ € 수: {}", scrappedUsers.size()); + yield scrappedUsers; + } + case ALL_USERS -> { + List allUsers = userRepository.findAll(); + log.info("πŸ“¦ [fetchTargetUsers] 전체 μœ μ € 수: {}", allUsers.size()); + yield allUsers; + } + default -> throw new NotificationException(NotificationErrorCode.INVALID_TARGET_TYPE); + }; + } catch (Exception e) { + log.error("πŸ”₯ [fetchTargetUsers] μœ μ € 쑰회 쀑 μ˜ˆμ™Έ λ°œμƒ", e); + throw e; + } return users.stream() + .peek(user -> { + if (user.getPushStatus() == null) { + log.warn("⚠️ [fetchTargetUsers] μœ μ €(oUserId={})의 pushStatusκ°€ null", user.getOUserId()); + } + if (user.getToken() == null) { + log.warn("⚠️ [fetchTargetUsers] μœ μ €(oUserId={})의 FCM 토큰이 null", user.getOUserId()); + } + }) .filter(User::canReceivePushNotification) .toList(); } From 72bda5afa2ddc9d3ef82100ab80e3a3a7549310b Mon Sep 17 00:00:00 2001 From: Willy Date: Fri, 2 May 2025 19:09:56 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[FIX]=20=ED=95=B4=EB=8B=B9=20join=EC=9D=98?= =?UTF-8?q?=20owner=20=EC=97=94=ED=8B=B0=ED=8B=B0=EA=B0=80=20select?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9C=BC=EB=AF=80=EB=A1=9C=20fetchJoin=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java b/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java index 289ab24..5bad55c 100644 --- a/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java +++ b/src/main/java/org/terning/scrap/domain/ScrapRepositoryImpl.java @@ -31,7 +31,7 @@ public List findDistinctScrappedUsers() { return queryFactory .selectDistinct(scrap.user) .from(scrap) - .join(scrap.user, user).fetchJoin() + .join(scrap.user, user) .where(scrap.status.eq(ScrapStatus.SCRAPPED)) .fetch(); } From af4c3c941132962b0a33300c57f041652a10f4b8 Mon Sep 17 00:00:00 2001 From: Willy Date: Fri, 2 May 2025 19:10:39 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[REFAC]=20oUserId=EB=A5=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=8A=A4=ED=81=AC=EB=9E=A9=20?= =?UTF-8?q?=EC=97=AC=EB=B6=80=EB=A5=BC=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scrap/application/ScrapSyncManager.java | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/terning/scrap/application/ScrapSyncManager.java b/src/main/java/org/terning/scrap/application/ScrapSyncManager.java index 7f3e36d..5d439d2 100644 --- a/src/main/java/org/terning/scrap/application/ScrapSyncManager.java +++ b/src/main/java/org/terning/scrap/application/ScrapSyncManager.java @@ -18,15 +18,30 @@ public class ScrapSyncManager { private final UserRepository userRepository; private final ScrapRepository scrapRepository; - public void sync(List userIds) { - List distinctUserIds = userIds.stream().distinct().toList(); + public void sync(List oUserIds) { + List distinctOUserIds = oUserIds.stream().distinct().toList(); + Map usersByOUserId = userRepository.findUsersByOUserIds(distinctOUserIds); - Map usersById = userRepository.findUsersByIds(distinctUserIds); - Map scrapsByUserId = scrapRepository.findScrapsByUserIds(distinctUserIds).stream() + List internalUserIds = usersByOUserId.values().stream() + .map(User::getId) + .toList(); + + Map scrapsByUserId = scrapRepository.findScrapsByUserIds(internalUserIds).stream() .collect(Collectors.toMap(scrap -> scrap.getUser().getId(), Function.identity())); - List scrapsToPersist = distinctUserIds.stream() - .map(userId -> createOrUpdateScrap(userId, usersById, scrapsByUserId)) + List scrapsToPersist = distinctOUserIds.stream() + .map(oUserId -> { + User user = usersByOUserId.get(oUserId); + if (user == null) return null; + + Scrap existingScrap = scrapsByUserId.get(user.getId()); + if (existingScrap == null) return Scrap.of(user); + if (existingScrap.isUnscrapped()) { + existingScrap.scrap(); + return existingScrap; + } + return null; + }) .filter(Objects::nonNull) .toList(); @@ -34,27 +49,4 @@ public void sync(List userIds) { scrapRepository.saveAll(scrapsToPersist); } } - - private Scrap createOrUpdateScrap( - Long userId, - Map usersById, - Map scrapsByUserId - ) { - User user = usersById.get(userId); - if (user == null) { - return null; - } - - Scrap existingScrap = scrapsByUserId.get(userId); - if (existingScrap == null) { - return Scrap.of(user); - } - - if (existingScrap.isUnscrapped()) { - existingScrap.scrap(); - return existingScrap; - } - - return null; - } } \ No newline at end of file From 5037944215ef2f8ad46bbc4eacd5f2fea4886627 Mon Sep 17 00:00:00 2001 From: Willy Date: Fri, 2 May 2025 19:11:02 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[FEAT]=20oUserId=EB=A5=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=9C=A0=EC=A0=80=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/terning/user/domain/UserRepositoryImpl.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/terning/user/domain/UserRepositoryImpl.java b/src/main/java/org/terning/user/domain/UserRepositoryImpl.java index d7b2dd7..80f7cb9 100644 --- a/src/main/java/org/terning/user/domain/UserRepositoryImpl.java +++ b/src/main/java/org/terning/user/domain/UserRepositoryImpl.java @@ -26,6 +26,16 @@ public Map findUsersByIds(List userIds) { .collect(Collectors.toMap(User::getId, Function.identity())); } + @Override + public Map findUsersByOUserIds(List oUserIds) { + return queryFactory + .selectFrom(QUser.user) + .where(QUser.user.oUserId.in(oUserIds)) + .fetch() + .stream() + .collect(Collectors.toMap(User::getOUserId, Function.identity())); + } + @Override public Optional findByOUserId(Long oUserId) { QUser user = QUser.user; From 6d7f6b8b948a73ac94c721302e6a2892803bb306 Mon Sep 17 00:00:00 2001 From: Willy Date: Fri, 2 May 2025 19:11:12 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[FEAT]=20oUserId=EB=A5=BC=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EC=9C=A0=EC=A0=80=EB=A5=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/terning/user/domain/UserRepositoryCustom.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/terning/user/domain/UserRepositoryCustom.java b/src/main/java/org/terning/user/domain/UserRepositoryCustom.java index 64dd543..52b1cc8 100644 --- a/src/main/java/org/terning/user/domain/UserRepositoryCustom.java +++ b/src/main/java/org/terning/user/domain/UserRepositoryCustom.java @@ -7,6 +7,8 @@ public interface UserRepositoryCustom { Map findUsersByIds(List userIds); + Map findUsersByOUserIds(List oUserIds); + Optional findByOUserId(Long oUserId); }